Sharing npm Dependencies
The integrator might want to share commonly used libraries with Feature Apps for two reasons.
- Reduce the bundle size of the Feature Apps by omitting shared dependencies from their bundle.
- Ensure that a library that can only exist once on a page is provided as a singleton.
How dependencies are shared depends on the provided module loaders of the integrator.
AMD
The feature described in this section is also demonstrated in the "AMD Module Loader" demo.
Integrator
When using the @feature-hub/module-loader-amd
package, the integrator can provide shared npm dependencies to Feature Apps
using the defineExternals function:
import {createFeatureHub} from '@feature-hub/core';
import {defineExternals, loadAmdModule} from '@feature-hub/module-loader-amd';
import * as React from 'react';
defineExternals({react: React});
const {featureAppManager} = createFeatureHub('acme:integrator', {
moduleLoader: loadAmdModule,
});
Feature Apps
Feature Apps should define these externals in their build config. For example,
defining react as external in a Webpack config would look like this:
module.exports = {
// ...
externals: {
react: 'react',
},
};
Webpack Module Federation
The feature described in this section is also demonstrated in the "Webpack Module Federation Loader" demo.
Integrator
When using the
@feature-hub/module-loader-federation package,
the integrator's client module must be bundled with Webpack. The integrator can
share npm dependencies to Feature Apps using the shared property of its
ModuleFederationPlugin:
module.exports = {
// ...
plugins: [
new webpack.container.ModuleFederationPlugin({
shared: {
react: {singleton: true, eager: true},
},
}),
],
};
If the integrator wants to provide a shared module:
- It must be
eagerif the integrator itself consumes it synchronously. - It does not need to be
eagerif the integrator consumes it asynchronously (dynamic import). - It must be a
singletonif the module is stateful. - It must be a
singletonif the module can not exist more than once on a page.
There is no need for the integrator to share a module that it does not consume itself. In this case, those Feature Apps that need it can provide the shared module instead (see below).
Feature Apps
If Feature Apps want to use shared dependencies, they must define them in the
shared property of their ModuleFederationPlugin.
module.exports = {
// ...
plugins: [
new webpack.container.ModuleFederationPlugin({
// ...
shared: {
react: {singleton: true},
},
}),
],
};
A dependency that the integrator has provided as singleton must also be a
singleton in the Feature App's Webpack config. But here, it should not be
eager, otherwise it will always be loaded, regardless of whether the
integrator has already provided it. Version mismatches of singletons are
generally ignored, and a warning is logged, unless strictVersion is set to
true, in which case a runtime error is thrown.
The biggest benefit of using the Module Federation Loader over the AMD Module Loader (or in addition), is that Feature Apps can also share npm dependencies with each other, without the need to involve the integrator.
module.exports = {
// ...
plugins: [
new webpack.container.ModuleFederationPlugin({
// ...
shared: {
'@apollo/client': {},
},
}),
],
};
If a Feature App wants to provide a shared module, it should not be eager.
In case of a version mismatch (defined by the semver ranges in the
package.json files of the Feature Apps) the module is loaded multiple times,
and available to other Feature Apps in each loaded version.
Note:
A large library of which only a small portion is used, might not be a good candidate to provide as a shared module. In this case it might be more efficient to include the library in each Feature App bundle, while making use of Webpack's tree shaking capabilities. Furthermore, the loading of a library can also be deferred by using dynamic code splitting.
CommonJS
The feature described in this section is also demonstrated in the "CommonJS Module Loader" demo.
Integrator
On the server, when Feature Apps are bundled as CommonJS modules, the integrator either
needs to make sure that the externals are provided as node modules that can be loaded with
require(), orif the integrator code is bundled and no node modules are available during runtime, the integrator can provide shared npm dependencies to Feature Apps using the
createCommonJsModuleLoaderfunction of the@feature-hub/module-loader-commonjspackage:import {createFeatureHub} from '@feature-hub/core'; import {createCommonJsModuleLoader} from '@feature-hub/module-loader-commonjs'; import * as React from 'react';const {featureAppManager} = createFeatureHub('acme:integrator', { moduleLoader: createCommonJsModuleLoader({react: React}), });
Feature Apps
Feature Apps should define these externals in their build config. For example,
defining react as external in a Webpack config would look like this:
module.exports = {
// ...
externals: {
react: 'react',
},
};
Validating Externals
To validate the external dependencies that are required by Feature
Apps against the shared npm dependencies that are
provided by the integrator, the ExternalsValidator can be
used.