Skip to content

Commit 3962d88

Browse files
authored
Merge pull request #923 from Patternslib/webpack5-module-federation
Webpack5 module federation
2 parents fd5a609 + 7e7a2af commit 3962d88

14 files changed

+334
-22
lines changed

docs/developer/module-federation.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Patternslib Module Federation
2+
3+
## Introduction
4+
5+
For a general introduction see: https://webpack.js.org/concepts/module-federation/
6+
7+
Module Federation allows to share dependencies between bundles.
8+
Each bundle includes the whole set of dependencies.
9+
However, if multiple bundles have the same dependencies they are only loaded once.
10+
11+
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.
12+
But if only bundle B is loaded, it uses its own bundled version of the jQuery library.
13+
14+
There is a host bundle - in the fictional example above our bundle "A".
15+
Other bundles are "remotes" which are initialized for module federation by the host bundle "A"
16+
17+
18+
## How to use it
19+
20+
- Create a new entry point ``index.js`` which only imports the normal entry point.
21+
If your bundle is the host bundle, also import the ``module_federation`` module - importing is enough.
22+
23+
```
24+
import "@patternslib/patternslib/webpack/module_federation";
25+
import("./patterns");
26+
```
27+
28+
- Add the module federation plugin in webpack. There is a configuration factory which you can use for that like so:
29+
30+
```
31+
const package_json = require("./package.json");
32+
const path = require("path");
33+
const patternslib_config = require("@patternslib/patternslib/webpack/webpack.config");
34+
const mf_config = require("@patternslib/patternslib/webpack/webpack.mf");
35+
36+
module.exports = (env, argv) => {
37+
let config = {
38+
entry: {
39+
"bundle.min": path.resolve(__dirname, "src/index.js"),
40+
},
41+
};
42+
43+
config = patternslib_config(env, argv, config, ["mockup"]);
44+
45+
// ...
46+
47+
config.plugins.push(
48+
mf_config({
49+
package_json: package_json,
50+
remote_entry: config.entry["bundle.min"],
51+
})
52+
);
53+
54+
return config;
55+
};
56+
```
57+
58+
That's basically it.
59+
60+
For more information look at the source code at ``@patternslib/webpack/module_federation`` and ``@patternslib/webpack/webpack.mf.js``.
61+

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@
5252
"css-loader": "^6.7.1",
5353
"eslint": "^8.15.0",
5454
"eslint-config-prettier": "^8.5.0",
55-
"expose-loader": "^3.0.0",
5655
"husky": "^8.0.1",
5756
"identity-obj-proxy": "^3.0.0",
5857
"imports-loader": "^3.1.0",

src/core/dom.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,26 @@ const set_data = (el, name, value) => {
230230
el[`${DATA_PREFIX}${name}`] = value;
231231
};
232232

233+
/**
234+
* Simple template engine, based on JS template literal
235+
*
236+
* Please note: You cannot pass a template literal as template_string.
237+
* JavaScript itself would try to expand it and would fail.
238+
*
239+
* See: https://stackoverflow.com/a/37217166/1337474
240+
*
241+
* @param {String} template_string - The template string as a JavaScript template literal.
242+
* For each variable in the template you have to use ``this``.
243+
* E.g. if you pass ``{message: "ok"}`` as template_variables, you can use it like so:
244+
* `<h1>${this.message}</h1>`
245+
* @param {Object} template_variables - Object literal with all the variables which should be used in the template.
246+
*
247+
* @returns {String} - Returns the a string as template expanded with the template_variables.
248+
*/
249+
const template = (template_string, template_variables = {}) => {
250+
return new Function("return `" + template_string + "`;").call(template_variables);
251+
};
252+
233253
const dom = {
234254
toNodeArray: toNodeArray,
235255
querySelectorAllAndMe: querySelectorAllAndMe,
@@ -246,6 +266,7 @@ const dom = {
246266
find_scroll_container: find_scroll_container,
247267
get_data: get_data,
248268
set_data: set_data,
269+
template: template,
249270
add_event_listener: events.add_event_listener, // BBB export. TODO: Remove in an upcoming version.
250271
remove_event_listener: events.remove_event_listener, // BBB export. TODO: Remove in an upcoming version.
251272
};

src/core/dom.test.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,4 +704,17 @@ describe("core.dom tests", () => {
704704
expect(dom.get_data(el, "test_data")).toBe(undefined);
705705
});
706706
});
707+
708+
describe("template", () => {
709+
it("Expands a template with template variables.", (done) => {
710+
const res = dom.template("<h1>${this.message}</h1>", { message: "ok" });
711+
expect(res).toBe("<h1>ok</h1>");
712+
done();
713+
});
714+
it("Returns the string when no template variables are given.", (done) => {
715+
const res = dom.template(`<h1>hello</h1>`);
716+
expect(res).toBe(`<h1>hello</h1>`);
717+
done();
718+
});
719+
});
707720
});

src/core/registry.js

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,21 @@ while ((match = dont_catch_re.exec(window.location.search)) !== null) {
3838
log.info("I will not catch init exceptions");
3939
}
4040

41+
/**
42+
* Global pattern registry.
43+
*
44+
* This is a singleton and shared among any instance of the Patternslib
45+
* registry since Patternslib version 8.
46+
*
47+
* You normally don't need this as the registry handles it for you.
48+
*/
49+
if (typeof window.__patternslib_registry === "undefined") {
50+
window.__patternslib_registry = {};
51+
}
52+
export const PATTERN_REGISTRY = window.__patternslib_registry;
53+
4154
const registry = {
42-
patterns: {},
55+
patterns: PATTERN_REGISTRY, // reference to global patterns registry
4356
// as long as the registry is not initialized, pattern
4457
// registration just registers a pattern. Once init is called,
4558
// the DOM is scanned. After that registering a new pattern
@@ -57,7 +70,9 @@ const registry = {
5770
clear() {
5871
// Removes all patterns from the registry. Currently only being
5972
// used in tests.
60-
this.patterns = {};
73+
for (const name in registry.patterns) {
74+
delete registry.patterns[name];
75+
}
6176
},
6277

6378
transformPattern(name, content) {
@@ -176,7 +191,7 @@ const registry = {
176191
return false;
177192
}
178193
if (registry.patterns[name]) {
179-
log.error("Already have a pattern called: " + name);
194+
log.info("Already have a pattern called: " + name);
180195
return false;
181196
}
182197

src/globals.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import jquery from "jquery";
2+
// Register jQuery globally
3+
window.jQuery = jquery;
4+
window.$ = jquery;

src/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// Webpack entry point for module federation.
2+
import("./patterns");

src/pat/validation/validation.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -320,10 +320,7 @@ export default Base.extend({
320320

321321
// Create the validation error DOM node from the template
322322
const validation_message = input.validationMessage || input[KEY_ERROR_MSG];
323-
// See: https://stackoverflow.com/a/37217166/1337474
324-
const error_template = new Function(
325-
"return `" + options.errorTemplate + "`;"
326-
).call({
323+
const error_template = dom.template(options.errorTemplate, {
327324
message: validation_message,
328325
});
329326
const error_node = dom.create_from_string(error_template).firstChild;

src/patterns.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
// Import base
66
import "./public_path"; // first import
7+
import "./globals";
78
import registry from "./core/registry";
8-
import jquery from "jquery";
99
import "modernizr";
1010

1111
// Import all used patterns for the bundle to be generated
@@ -67,5 +67,4 @@ import "./pat/minimalpattern/minimalpattern";
6767
// Set to ``true`` to include core styles via JavaScript
6868
window.__patternslib_import_styles = false;
6969

70-
window.jQuery = jquery;
7170
registry.init();
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Author: Manfred Steyer <manfred.steyer@gmx.net>
2+
// Author: Johannes Raggam <thetetet@gmail.com>
3+
4+
// From:
5+
// https://github.com/manfredsteyer/plugin-demo.git
6+
// https://github.com/thet/module-federation-minimaltest.git
7+
8+
/**
9+
* Load remote module / bundle.
10+
*
11+
* Wrapper around webpack runtime API
12+
*
13+
* Usage: get_container("bundle-name-xyz")
14+
*
15+
* @param {string} remote - the remote global name
16+
* @returns {Promise<object>} - Federated Module Container
17+
*/
18+
const container_map = {};
19+
let is_default_scope_initialized = false;
20+
21+
export default async function get_container(remote) {
22+
const container = window[remote];
23+
24+
// Do we still need to initialize the remote?
25+
if (container_map[remote]) {
26+
return container;
27+
}
28+
29+
// Do we still need to initialize the shared scope?
30+
if (!is_default_scope_initialized) {
31+
await __webpack_init_sharing__("default"); // eslint-disable-line no-undef
32+
is_default_scope_initialized = true;
33+
}
34+
35+
await container.init(__webpack_share_scopes__.default); // eslint-disable-line no-undef
36+
37+
// Remember that the container has been initialized.
38+
container_map[remote] = true;
39+
return container;
40+
}

0 commit comments

Comments
 (0)