fix: consumption of eager shared modules for module federation

This commit is contained in:
Alexander Akait 2024-06-21 17:22:29 +03:00 committed by GitHub
commit dd44b206a9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 374 additions and 151 deletions

View File

@ -207,26 +207,31 @@ class ConsumeSharedModule extends Module {
});
}
}
let fn = "load";
const args = [JSON.stringify(shareScope), JSON.stringify(shareKey)];
const args = [
JSON.stringify(shareScope),
JSON.stringify(shareKey),
JSON.stringify(eager)
];
if (requiredVersion) {
if (strictVersion) {
fn += "Strict";
}
if (singleton) {
fn += "Singleton";
}
args.push(stringifyHoley(requiredVersion));
fn += "VersionCheck";
} else {
if (singleton) {
fn += "Singleton";
}
}
if (fallbackCode) {
fn += "Fallback";
args.push(fallbackCode);
}
let fn;
if (requiredVersion) {
if (strictVersion) {
fn = singleton ? "loadStrictSingletonVersion" : "loadStrictVersion";
} else {
fn = singleton ? "loadSingletonVersion" : "loadVersion";
}
} else {
fn = singleton ? "loadSingleton" : "load";
}
const code = runtimeTemplate.returningFunction(`${fn}(${args.join(", ")})`);
const sources = new Map();
sources.set("consume-shared", new RawSource(code));

View File

@ -95,63 +95,39 @@ class ConsumeSharedRuntimeModule extends RuntimeModule {
versionLtRuntimeCode(runtimeTemplate),
rangeToStringRuntimeCode(runtimeTemplate),
satisfyRuntimeCode(runtimeTemplate),
`var ensureExistence = ${runtimeTemplate.basicFunction("scopeName, key", [
`var scope = ${RuntimeGlobals.shareScopeMap}[scopeName];`,
`if(!scope || !${RuntimeGlobals.hasOwnProperty}(scope, key)) throw new Error("Shared module " + key + " doesn't exist in shared scope " + scopeName);`,
"return scope;"
`var exists = ${runtimeTemplate.basicFunction("scope, key", [
`return scope && ${RuntimeGlobals.hasOwnProperty}(scope, key);`
])}`,
`var get = ${runtimeTemplate.basicFunction("entry", [
"entry.loaded = 1;",
"return entry.get()"
])};`,
`var findVersion = ${runtimeTemplate.basicFunction("scope, key", [
"var versions = scope[key];",
`var eagerOnly = ${runtimeTemplate.basicFunction("versions", [
`return Object.keys(versions).reduce(${runtimeTemplate.basicFunction(
"filtered, version",
Template.indent([
"if (versions[version].eager) {",
Template.indent(["filtered[version] = versions[version];"]),
"}",
"return filtered;"
])
)}, {});`
])};`,
`var findLatestVersion = ${runtimeTemplate.basicFunction(
"scope, key, eager",
[
"var versions = eager ? eagerOnly(scope[key]) : scope[key];",
`var key = Object.keys(versions).reduce(${runtimeTemplate.basicFunction(
"a, b",
["return !a || versionLt(a, b) ? b : a;"]
)}, 0);`,
"return key && versions[key]"
])};`,
`var findSingletonVersionKey = ${runtimeTemplate.basicFunction(
"scope, key",
[
"var versions = scope[key];",
`return Object.keys(versions).reduce(${runtimeTemplate.basicFunction(
"a, b",
["return !a || (!versions[a].loaded && versionLt(a, b)) ? b : a;"]
)}, 0);`
"return key && versions[key];"
]
)};`,
`var getInvalidSingletonVersionMessage = ${runtimeTemplate.basicFunction(
"scope, key, version, requiredVersion",
`var findSatisfyingVersion = ${runtimeTemplate.basicFunction(
"scope, key, requiredVersion, eager",
[
`return "Unsatisfied version " + version + " from " + (version && scope[key][version].from) + " of shared singleton module " + key + " (required " + rangeToString(requiredVersion) + ")"`
]
)};`,
`var getSingleton = ${runtimeTemplate.basicFunction(
"scope, scopeName, key, requiredVersion",
[
"var version = findSingletonVersionKey(scope, key);",
"return get(scope[key][version]);"
]
)};`,
`var getSingletonVersion = ${runtimeTemplate.basicFunction(
"scope, scopeName, key, requiredVersion",
[
"var version = findSingletonVersionKey(scope, key);",
"if (!satisfy(requiredVersion, version)) warn(getInvalidSingletonVersionMessage(scope, key, version, requiredVersion));",
"return get(scope[key][version]);"
]
)};`,
`var getStrictSingletonVersion = ${runtimeTemplate.basicFunction(
"scope, scopeName, key, requiredVersion",
[
"var version = findSingletonVersionKey(scope, key);",
"if (!satisfy(requiredVersion, version)) " +
"throw new Error(getInvalidSingletonVersionMessage(scope, key, version, requiredVersion));",
"return get(scope[key][version]);"
]
)};`,
`var findValidVersion = ${runtimeTemplate.basicFunction(
"scope, key, requiredVersion",
[
"var versions = scope[key];",
"var versions = eager ? eagerOnly(scope[key]) : scope[key];",
`var key = Object.keys(versions).reduce(${runtimeTemplate.basicFunction(
"a, b",
[
@ -162,136 +138,127 @@ class ConsumeSharedRuntimeModule extends RuntimeModule {
"return key && versions[key]"
]
)};`,
`var findSingletonVersionKey = ${runtimeTemplate.basicFunction(
"scope, key, eager",
[
"var versions = eager ? eagerOnly(scope[key]) : scope[key];",
`return Object.keys(versions).reduce(${runtimeTemplate.basicFunction(
"a, b",
["return !a || (!versions[a].loaded && versionLt(a, b)) ? b : a;"]
)}, 0);`
]
)};`,
`var getInvalidSingletonVersionMessage = ${runtimeTemplate.basicFunction(
"scope, key, version, requiredVersion",
[
'return "Unsatisfied version " + version + " from " + (version && scope[key][version].from) + " of shared singleton module " + key + " (required " + rangeToString(requiredVersion) + ")"'
]
)};`,
`var getInvalidVersionMessage = ${runtimeTemplate.basicFunction(
"scope, scopeName, key, requiredVersion",
"scope, scopeName, key, requiredVersion, eager",
[
"var versions = scope[key];",
'return "No satisfying version (" + rangeToString(requiredVersion) + ") of shared module " + key + " found in shared scope " + scopeName + ".\\n" +',
'return "No satisfying version (" + rangeToString(requiredVersion) + ")" + (eager ? " for eager consumption" : "") + " of shared module " + key + " found in shared scope " + scopeName + ".\\n" +',
`\t"Available versions: " + Object.keys(versions).map(${runtimeTemplate.basicFunction(
"key",
['return key + " from " + versions[key].from;']
)}).join(", ");`
]
)};`,
`var getValidVersion = ${runtimeTemplate.basicFunction(
"scope, scopeName, key, requiredVersion",
[
"var entry = findValidVersion(scope, key, requiredVersion);",
"if(entry) return get(entry);",
"throw new Error(getInvalidVersionMessage(scope, scopeName, key, requiredVersion));"
]
)};`,
`var warn = ${
`var fail = ${runtimeTemplate.basicFunction("msg", [
"throw new Error(msg);"
])}`,
`var failAsNotExist = ${runtimeTemplate.basicFunction("scopeName, key", [
'return fail("Shared module " + key + " doesn\'t exist in shared scope " + scopeName);'
])}`,
`var warn = /*#__PURE__*/ ${
compilation.outputOptions.ignoreBrowserWarnings
? runtimeTemplate.basicFunction("", "")
: runtimeTemplate.basicFunction("msg", [
'if (typeof console !== "undefined" && console.warn) console.warn(msg);'
])
};`,
`var warnInvalidVersion = ${runtimeTemplate.basicFunction(
"scope, scopeName, key, requiredVersion",
[
"warn(getInvalidVersionMessage(scope, scopeName, key, requiredVersion));"
]
)};`,
`var get = ${runtimeTemplate.basicFunction("entry", [
"entry.loaded = 1;",
"return entry.get()"
])};`,
`var init = ${runtimeTemplate.returningFunction(
Template.asString([
"function(scopeName, a, b, c) {",
"function(scopeName, key, eager, c, d) {",
Template.indent([
`var promise = ${RuntimeGlobals.initializeSharing}(scopeName);`,
`if (promise && promise.then) return promise.then(fn.bind(fn, scopeName, ${RuntimeGlobals.shareScopeMap}[scopeName], a, b, c));`,
`return fn(scopeName, ${RuntimeGlobals.shareScopeMap}[scopeName], a, b, c);`
// if we require eager shared, we expect it to be already loaded before it requested, no need to wait the whole scope loaded.
"if (promise && promise.then && !eager) { ",
Template.indent([
`return promise.then(fn.bind(fn, scopeName, ${RuntimeGlobals.shareScopeMap}[scopeName], key, false, c, d));`
]),
"}",
`return fn(scopeName, ${RuntimeGlobals.shareScopeMap}[scopeName], key, eager, c, d);`
]),
"}"
]),
"fn"
)};`,
"",
`var useFallback = ${runtimeTemplate.basicFunction(
"scopeName, key, fallback",
["return fallback ? fallback() : failAsNotExist(scopeName, key);"]
)}`,
`var load = /*#__PURE__*/ init(${runtimeTemplate.basicFunction(
"scopeName, scope, key",
"scopeName, scope, key, eager, fallback",
[
"ensureExistence(scopeName, key);",
"return get(findVersion(scope, key));"
"if (!exists(scope, key)) return useFallback(scopeName, key, fallback);",
"return get(findLatestVersion(scope, key, eager));"
]
)});`,
`var loadFallback = /*#__PURE__*/ init(${runtimeTemplate.basicFunction(
"scopeName, scope, key, fallback",
`var loadVersion = /*#__PURE__*/ init(${runtimeTemplate.basicFunction(
"scopeName, scope, key, eager, requiredVersion, fallback",
[
`return scope && ${RuntimeGlobals.hasOwnProperty}(scope, key) ? get(findVersion(scope, key)) : fallback();`
"if (!exists(scope, key)) return useFallback(scopeName, key, fallback);",
"var satisfyingVersion = findSatisfyingVersion(scope, key, requiredVersion, eager);",
"if (satisfyingVersion) return get(satisfyingVersion);",
"warn(getInvalidVersionMessage(scope, scopeName, key, requiredVersion, eager))",
"return get(findLatestVersion(scope, key, eager));"
]
)});`,
`var loadVersionCheck = /*#__PURE__*/ init(${runtimeTemplate.basicFunction(
"scopeName, scope, key, version",
`var loadStrictVersion = /*#__PURE__*/ init(${runtimeTemplate.basicFunction(
"scopeName, scope, key, eager, requiredVersion, fallback",
[
"ensureExistence(scopeName, key);",
"return get(findValidVersion(scope, key, version) || warnInvalidVersion(scope, scopeName, key, version) || findVersion(scope, key));"
"if (!exists(scope, key)) return useFallback(scopeName, key, fallback);",
"var satisfyingVersion = findSatisfyingVersion(scope, key, requiredVersion, eager);",
"if (satisfyingVersion) return get(satisfyingVersion);",
"if (fallback) return fallback();",
"fail(getInvalidVersionMessage(scope, scopeName, key, requiredVersion, eager));"
]
)});`,
`var loadSingleton = /*#__PURE__*/ init(${runtimeTemplate.basicFunction(
"scopeName, scope, key",
"scopeName, scope, key, eager, fallback",
[
"ensureExistence(scopeName, key);",
"return getSingleton(scope, scopeName, key);"
"if (!exists(scope, key)) return useFallback(scopeName, key, fallback);",
"var version = findSingletonVersionKey(scope, key, eager);",
"return get(scope[key][version]);"
]
)});`,
`var loadSingletonVersionCheck = /*#__PURE__*/ init(${runtimeTemplate.basicFunction(
"scopeName, scope, key, version",
`var loadSingletonVersion = /*#__PURE__*/ init(${runtimeTemplate.basicFunction(
"scopeName, scope, key, eager, requiredVersion, fallback",
[
"ensureExistence(scopeName, key);",
"return getSingletonVersion(scope, scopeName, key, version);"
"if (!exists(scope, key)) return useFallback(scopeName, key, fallback);",
"var version = findSingletonVersionKey(scope, key, eager);",
"if (!satisfy(requiredVersion, version)) {",
Template.indent([
"warn(getInvalidSingletonVersionMessage(scope, key, version, requiredVersion));"
]),
"}",
"return get(scope[key][version]);"
]
)});`,
`var loadStrictVersionCheck = /*#__PURE__*/ init(${runtimeTemplate.basicFunction(
"scopeName, scope, key, version",
`var loadStrictSingletonVersion = /*#__PURE__*/ init(${runtimeTemplate.basicFunction(
"scopeName, scope, key, eager, requiredVersion, fallback",
[
"ensureExistence(scopeName, key);",
"return getValidVersion(scope, scopeName, key, version);"
]
)});`,
`var loadStrictSingletonVersionCheck = /*#__PURE__*/ init(${runtimeTemplate.basicFunction(
"scopeName, scope, key, version",
[
"ensureExistence(scopeName, key);",
"return getStrictSingletonVersion(scope, scopeName, key, version);"
]
)});`,
`var loadVersionCheckFallback = /*#__PURE__*/ init(${runtimeTemplate.basicFunction(
"scopeName, scope, key, version, fallback",
[
`if(!scope || !${RuntimeGlobals.hasOwnProperty}(scope, key)) return fallback();`,
"return get(findValidVersion(scope, key, version) || warnInvalidVersion(scope, scopeName, key, version) || findVersion(scope, key));"
]
)});`,
`var loadSingletonFallback = /*#__PURE__*/ init(${runtimeTemplate.basicFunction(
"scopeName, scope, key, fallback",
[
`if(!scope || !${RuntimeGlobals.hasOwnProperty}(scope, key)) return fallback();`,
"return getSingleton(scope, scopeName, key);"
]
)});`,
`var loadSingletonVersionCheckFallback = /*#__PURE__*/ init(${runtimeTemplate.basicFunction(
"scopeName, scope, key, version, fallback",
[
`if(!scope || !${RuntimeGlobals.hasOwnProperty}(scope, key)) return fallback();`,
"return getSingletonVersion(scope, scopeName, key, version);"
]
)});`,
`var loadStrictVersionCheckFallback = /*#__PURE__*/ init(${runtimeTemplate.basicFunction(
"scopeName, scope, key, version, fallback",
[
`var entry = scope && ${RuntimeGlobals.hasOwnProperty}(scope, key) && findValidVersion(scope, key, version);`,
`return entry ? get(entry) : fallback();`
]
)});`,
`var loadStrictSingletonVersionCheckFallback = /*#__PURE__*/ init(${runtimeTemplate.basicFunction(
"scopeName, scope, key, version, fallback",
[
`if(!scope || !${RuntimeGlobals.hasOwnProperty}(scope, key)) return fallback();`,
"return getStrictSingletonVersion(scope, scopeName, key, version);"
"if (!exists(scope, key)) return useFallback(scopeName, key, fallback);",
"var version = findSingletonVersionKey(scope, key, eager);",
"if (!satisfy(requiredVersion, version)) {",
Template.indent([
"fail(getInvalidSingletonVersionMessage(scope, key, version, requiredVersion));"
]),
"}",
"return get(scope[key][version]);"
]
)});`,
"var installedModules = {};",

View File

@ -0,0 +1,7 @@
import { emitter } from "./emitter.js";
function App() {
return emitter;
}
export default App;

View File

@ -0,0 +1,9 @@
import { TinyEmitter } from 'tiny-emitter'
const emitter = new TinyEmitter()
emitter.on('hello', () => console.log('hello[service]'))
export {
emitter,
}

View File

@ -0,0 +1,6 @@
it("should allow to import exposed modules sync", () => {
return import("./App").then(({ default: App }) => {
expect(App().e.hello).toBeDefined();
});
});

View File

@ -0,0 +1,67 @@
function E () {
// Keep this empty so it's easier to inherit from
// (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)
}
E.prototype = {
on: function (name, callback, ctx) {
var e = this.e || (this.e = {});
(e[name] || (e[name] = [])).push({
fn: callback,
ctx: ctx
});
return this;
},
once: function (name, callback, ctx) {
var self = this;
function listener () {
self.off(name, listener);
callback.apply(ctx, arguments);
};
listener._ = callback
return this.on(name, listener, ctx);
},
emit: function (name) {
var data = [].slice.call(arguments, 1);
var evtArr = ((this.e || (this.e = {}))[name] || []).slice();
var i = 0;
var len = evtArr.length;
for (i; i < len; i++) {
evtArr[i].fn.apply(evtArr[i].ctx, data);
}
return this;
},
off: function (name, callback) {
var e = this.e || (this.e = {});
var evts = e[name];
var liveEvents = [];
if (evts && callback) {
for (var i = 0, len = evts.length; i < len; i++) {
if (evts[i].fn !== callback && evts[i].fn._ !== callback)
liveEvents.push(evts[i]);
}
}
// Remove event from queue to prevent memory leak
// Suggested by https://github.com/lazd
// Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910
(liveEvents.length)
? e[name] = liveEvents
: delete e[name];
return this;
}
};
module.exports = E;
module.exports.TinyEmitter = E;

View File

@ -0,0 +1,7 @@
{
"name": "tiny-emitter",
"version": "2.1.0",
"description": "A tiny (less than 1k) event emitter library",
"main": "index.js",
"license": "MIT"
}

View File

@ -0,0 +1,9 @@
{
"private": true,
"engines": {
"node": ">=10.13.0"
},
"dependencies": {
"tiny-emitter": "^2.1.0"
}
}

View File

@ -0,0 +1,26 @@
const { dependencies } = require("./package.json");
const { ModuleFederationPlugin } = require("../../../../").container;
/** @type {import("../../../../").Configuration} */
module.exports = {
optimization: {
chunkIds: "named",
moduleIds: "named"
},
plugins: [
new ModuleFederationPlugin({
name: "container",
filename: "container.js",
library: { type: "commonjs-module" },
exposes: {
"./emitter": {
name: "emitter",
import: "./emitter.js"
}
},
shared: {
...dependencies
}
})
]
};

View File

@ -0,0 +1,13 @@
import TinyEmitter from 'tiny-emitter'
it("should load the component from container", () => {
const emitter = new TinyEmitter()
emitter.on('hello', () => {})
expect(emitter.e.hello).toBeDefined();
return import('service/emitter').then(({ emitter }) => {
expect(emitter.e.hello).toBeDefined();
})
});

View File

@ -0,0 +1,66 @@
function E () {
// Keep this empty so it's easier to inherit from
// (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)
}
E.prototype = {
on: function (name, callback, ctx) {
var e = this.e || (this.e = {});
(e[name] || (e[name] = [])).push({
fn: callback,
ctx: ctx
});
return this;
},
once: function (name, callback, ctx) {
var self = this;
function listener () {
self.off(name, listener);
callback.apply(ctx, arguments);
};
listener._ = callback
return this.on(name, listener, ctx);
},
emit: function (name) {
var data = [].slice.call(arguments, 1);
var evtArr = ((this.e || (this.e = {}))[name] || []).slice();
var i = 0;
var len = evtArr.length;
for (i; i < len; i++) {
evtArr[i].fn.apply(evtArr[i].ctx, data);
}
return this;
},
off: function (name, callback) {
var e = this.e || (this.e = {});
var evts = e[name];
var liveEvents = [];
if (evts && callback) {
for (var i = 0, len = evts.length; i < len; i++) {
if (evts[i].fn !== callback && evts[i].fn._ !== callback)
liveEvents.push(evts[i]);
}
}
// Remove event from queue to prevent memory leak
// Suggested by https://github.com/lazd
// Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910
(liveEvents.length)
? e[name] = liveEvents
: delete e[name];
return this;
}
};
module.exports = E;

View File

@ -0,0 +1,7 @@
{
"name": "tiny-emitter",
"version": "2.0.0",
"description": "A tiny (less than 1k) event emitter library",
"main": "index.js",
"license": "MIT"
}

View File

@ -0,0 +1,9 @@
{
"private": true,
"engines": {
"node": ">=10.13.0"
},
"dependencies": {
"tiny-emitter": "=2.0.0"
}
}

View File

@ -0,0 +1,25 @@
const { dependencies } = require("./package.json");
const { ModuleFederationPlugin } = require("../../../../").container;
/** @type {import("../../../../").Configuration} */
module.exports = {
optimization: {
chunkIds: "named",
moduleIds: "named"
},
plugins: [
new ModuleFederationPlugin({
remoteType: "commonjs-module",
remotes: {
service: "../0-eager-shared/container.js"
},
shared: {
"tiny-emitter": {
eager: true,
singleton: true,
requiredVersion: dependencies["tiny-emitter"]
}
}
})
]
};