Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 55 additions & 13 deletions docs/developer/module-federation.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,47 @@ For a general introduction see: https://webpack.js.org/concepts/module-federatio

Module Federation allows to share dependencies between bundles.
Each bundle includes the whole set of dependencies.
However, if multiple bundles have the same dependencies they are only loaded once.
If multiple bundles have the same dependencies they are only loaded once.

For example, if bundle A and B both depend on jQuery and bundle A has already loaded it, bundle B can just reuse the already loaded jQuery file.
But if only bundle B is loaded, it uses its own bundled version of the jQuery library.

There is a host bundle - in the fictional example above our bundle "A".
Other bundles are "remotes" which are initialized for module federation by the host bundle "A"
There needs to be __one host bundle__ - the main bundle of your application.
This one is included by using the generated webpack entry point, normally named ``bundle.min.js``.

All other bundles are __remote bundles__.
Remotes are included using the file generated by the ``WebpackModuleFederation`` plugin, normally named ``remote.min.js``.

## How to use it

- Create a new entry point ``index.js`` which only imports the normal entry point.
This is an exmple with a host bundle ``bundle.min.js`` for which also a remote bundle ``remote.min.js`` is created.
The name ``remote.min.js`` is the default from the Patternslib ``webpack.mf.js`` config factory.

We also create a second bundle for the jQuery library ``jquery.min.js`` which has the only purpose to make jQuery available via module federation.
For jQuery the remote bundle is named ``jquery-remote.min.js``.

1) For module federation we need new entry points. For the main bundle ``bundle.min.js`` we create the entry point ``index.js``. It imports the module federation initalization code from Patternslib and imports the normal entry point which initialized the app:

```
import("./patterns");
import "@patternslib/patternslib/webpack/module_federation";
// Please not the parentheses for the next import.
// Kepp them, otherwise we get this error:
// "Shared module is not available for eager consumption."
import("./src/patterns");
```

If you are creating a host bundle - one which is always present and initializes add-on bundles - then also import the ``module_federation`` module.
Importing is enough.
Mockup is such a host bundle.
Your ``index.js`` will then look like:
For the jQuery bundle which we will use as remote we create: ``index-jquery.js``:

```
import "@patternslib/patternslib/webpack/module_federation";
import("./patterns");
// Please not the parentheses for the next import.
// Kepp them, otherwise we get this error:
// "Shared module is not available for eager consumption."
import("@patternslib/patternslib/src/globals");
```

- Add the module federation plugin in webpack. There is a configuration factory which you can use for that like so:

2) Add the module federation plugin in webpack using the configuration factory method from Patternslib:

```
const package_json = require("./package.json");
Expand All @@ -45,24 +58,53 @@ module.exports = (env, argv) => {
let config = {
entry: {
"bundle.min": path.resolve(__dirname, "src/index.js"),
"jquery.min": path.resolve(__dirname, "src/index-jquery.js"),
},
};

config = patternslib_config(env, argv, config, ["mockup"]);
config = patternslib_config(env, argv, config);

// ...

config.plugins.push(
mf_config({
mf_config({ // name is used from package.json, filename is the default ``remote.min.js``.
package_json: package_json,
remote_entry: config.entry["bundle.min"],
})
);

config.plugins.push(
mf_config({
name: "jquery",
filename: "jquery-remote.min.js",
remote_entry: config.entry["jquery.min"],
shared: { // shared dependencies are normally automatically used from the package.json. Here we are explicitly defining them.
jquery: {
singleton: true,
requiredVersion: package_json.dependencies["jquery"],
},
},
})
);

return config;
};
```

3) You now can include the generated host and remote entry points:

```
<!DOCTYPE html>
<html>
<head>
<!-- ... -->
<script src="/dist/bundle.min.js"></script>
<script src="/dist/jquery-remote.min.js"></script>
</head>
<body class="component-index">
<!-- ... -->
```

That's basically it.

For more information look at the source code at ``@patternslib/webpack/module_federation`` and ``@patternslib/webpack/webpack.mf.js``.
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,10 @@
},
"resolutions": {},
"scripts": {
"start": "NODE_ENV=development webpack serve --config webpack/webpack.config.js",
"build": "NODE_ENV=production webpack --config webpack/webpack.config.js",
"build:stats": "NODE_ENV=production webpack --config webpack/webpack.config.js --json > stats.json",
"start": "NODE_ENV=development webpack serve --config webpack/webpack.dist.js",
"build": "NODE_ENV=production webpack --config webpack/webpack.dist.js",
"build:dev": "NODE_ENV=development webpack --config webpack/webpack.dist.js",
"build:stats": "NODE_ENV=production webpack --config webpack/webpack.dist.js --json > stats.json",
"test": "TEST_ENV=patternslib jest --watch",
"testonce": "TEST_ENV=patternslib jest"
},
Expand Down
3 changes: 3 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
// Webpack entry point for module federation.
import "../webpack/module_federation";
// The next import needs to be kept with parentheses, otherwise we get this error:
// "Shared module is not available for eager consumption."
import("./patterns");
1 change: 0 additions & 1 deletion src/patterns.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
*/

// Import base
import "./public_path"; // first import
import "./globals";
import registry from "./core/registry";
import "modernizr";
Expand Down
19 changes: 16 additions & 3 deletions webpack/module_federation.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,26 @@ import get_container from "./module_federation--dynamic-federation";
// NOTE: This is also defined in ``webpack.mf.js``.
export const MF_NAME_PREFIX = "__patternslib_mf__";

if (typeof window.__patternslib_container_map === "undefined") {
window.__patternslib_container_map = {};
}
const container_map = window.__patternslib_container_map;

export async function initialize_remote({ remote_name, exposed_module = "./main" }) {
if (container_map[`${remote_name}-${exposed_module}`]) {
// already initialized, return.
return;
}
const container = await get_container(remote_name);
const factory = await container.get(exposed_module);
const module = factory();

container_map[`${remote_name}-${exposed_module}`] = true;

console.debug(
`Patternslib Module Federation: Loaded and initialized bundle "${remote_name}".`
);

return module;
}

Expand All @@ -34,8 +50,5 @@ document_ready(function () {
for (const bundle_name of bundles) {
// Now load + initialize each bundle.
initialize_remote({ remote_name: bundle_name });
console.debug(
`Patternslib Module Federation: Loaded and initialized bundle "${bundle_name}".`
);
}
});
20 changes: 8 additions & 12 deletions webpack/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,7 @@ module.exports = (env, argv, config, babel_include = []) => {
babel_exclude = `node_modules/${babel_exclude}.*`;

const base_config = {
entry: {
"bundle.min": path.resolve(__dirname, "../src/patterns.js"),
"bundle-polyfills.min": path.resolve(__dirname, "../src/polyfills.js"),
},
entry: {}, // Entry point files for your JavaScript application.
externals: [
{
window: "window",
Expand All @@ -35,7 +32,11 @@ module.exports = (env, argv, config, babel_include = []) => {
output: {
filename: "[name].js",
chunkFilename: "chunks/[name].[contenthash].min.js",
path: path.resolve(__dirname, "../dist/"),
// output.path: Set default to "dist/" which probably will work in
// projects which extend this webpack config.
// Note the extendee's webpack config should be in the top-level that
// "dist" can be found in the top level too.
path: "./dist",
clean: true, // Clean directory before compiling
publicPath: "auto",
},
Expand Down Expand Up @@ -78,18 +79,13 @@ module.exports = (env, argv, config, babel_include = []) => {
test: /\.(eot|woff|woff2|ttf|png|jpe?g|gif)$/i,
type: "asset/resource",
},
{
test: /\.modernizrrc\.js$/,
loader: "webpack-modernizr-loader",
},
],
},
resolve: {
alias: {
modernizr$: path.resolve(__dirname, "../.modernizrrc.js"),
},
alias: {},
},
plugins: [
// Copy polyfills loader to the output path.
new CopyPlugin({
patterns: [
{ from: path.resolve(__dirname, "../src/polyfills-loader.js"), }, // prettier-ignore
Expand Down
40 changes: 40 additions & 0 deletions webpack/webpack.dist.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Webpack configuration for the Patternslib bundle distribution.
process.traceDeprecation = true;
const mf_config = require("./webpack.mf");
const package_json = require("../package.json");
const path = require("path");
const patternslib_config = require("./webpack.config");

module.exports = (env, argv) => {
let config = {
entry: {
"bundle.min": path.resolve(__dirname, "../src/index.js"),
"bundle-polyfills.min": path.resolve(__dirname, "../src/polyfills.js"),
},
};

config = patternslib_config(env, argv, config);

config.output.path = path.resolve(__dirname, "../dist/");

// Modernizr
config.module.rules.push({
test: /\.modernizrrc\.js$/,
loader: "webpack-modernizr-loader",
});
config.resolve.alias = {
modernizr$: path.resolve(__dirname, "../.modernizrrc.js"),
};

config.plugins.push(
mf_config({
name: "patternslib",
package_json: package_json,
remote_entry: config.entry["bundle.min"],
})
);

//console.log(JSON.stringify(config, null, 4));

return config;
};