From 2f5bc7503d914dc6f500fd29be8b66b165c2ecee Mon Sep 17 00:00:00 2001 From: 2heal1 Date: Fri, 20 Dec 2024 18:26:39 +0800 Subject: [PATCH] chore: add provideExternalRuntime field --- .changeset/config.json | 3 +- .changeset/nervous-starfishes-whisper.md | 2 +- .../webpack-host/webpack.config.js | 5 +- .../docs/en/configure/experiments.mdx | 18 ++- .../docs/zh/configure/experiments.mdx | 18 ++- .../lib/container/ModuleFederationPlugin.ts | 11 +- .../container/ContainerPlugin.check.ts | 149 +++++++++--------- packages/rspack/src/ModuleFederationPlugin.ts | 12 +- .../rollup.config.js | 8 + .../src/index.ts | 23 +++ .../types/plugins/ModuleFederationPlugin.ts | 3 +- 11 files changed, 153 insertions(+), 99 deletions(-) diff --git a/.changeset/config.json b/.changeset/config.json index 9dd29034192..49f63eeabba 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -23,7 +23,8 @@ "@module-federation/retry-plugin", "@module-federation/data-prefetch", "@module-federation/rsbuild-plugin", - "@module-federation/error-codes" + "@module-federation/error-codes", + "@module-federation/inject-external-runtime-core-plugin" ] ], "ignorePatterns": ["^alpha|^beta"], diff --git a/.changeset/nervous-starfishes-whisper.md b/.changeset/nervous-starfishes-whisper.md index d3daa43075f..396299c5c8a 100644 --- a/.changeset/nervous-starfishes-whisper.md +++ b/.changeset/nervous-starfishes-whisper.md @@ -5,4 +5,4 @@ '@module-federation/runtime': patch --- -feat: add externalRuntime option to help optimize assets size +feat: add externalRuntime and provideExternalRuntime fields to help optimize assets size diff --git a/apps/manifest-demo/webpack-host/webpack.config.js b/apps/manifest-demo/webpack-host/webpack.config.js index 594cffabee5..b9efd43734a 100644 --- a/apps/manifest-demo/webpack-host/webpack.config.js +++ b/apps/manifest-demo/webpack-host/webpack.config.js @@ -23,9 +23,6 @@ module.exports = composePlugins(withNx(), withReact(), (config, context) => { 'modern-js-provider': 'app1@http://127.0.0.1:4001/mf-manifest.json', }, filename: 'remoteEntry.js', - exposes: { - './Button': './src/Button.tsx', - }, shared: { lodash: {}, antd: {}, @@ -50,7 +47,7 @@ module.exports = composePlugins(withNx(), withReact(), (config, context) => { // experiments: { federationRuntime: 'hoisted' }, runtimePlugins: [path.join(__dirname, './runtimePlugin.ts')], experiments: { - externalRuntime: 'provide', + provideExternalRuntime: true, federationRuntime: 'hoisted', }, }), diff --git a/apps/website-new/docs/en/configure/experiments.mdx b/apps/website-new/docs/en/configure/experiments.mdx index 5ded3e683d6..dc4c1fad936 100644 --- a/apps/website-new/docs/en/configure/experiments.mdx +++ b/apps/website-new/docs/en/configure/experiments.mdx @@ -65,14 +65,20 @@ This allows module federation to be avaliable ahead of time, thus enabling "Asyn ## externalRuntime -- Type: `boolean | 'provide'` +- Type: `boolean` - Required: No - Default: `false` -Used to control whether external mf runtime or mf runtime is provided. +After setting `true`, the external MF runtime will be used and the runtime provided by the consumer will be used. (Please make sure your consumer has `provideExternalRuntime: true` set, otherwise it will not run properly!) -Different behaviors can be performed through the following configurations +## provideExternalRuntime -* `true`: Usually set in the producer, after setting it will external MF runtime and use the runtime provided by the consumer. (Please make sure your consumer has `externalRuntime: "provide"` set, otherwise it will not run properly!) -* `'provide'`: Usually set in the top-level consumer. After setting, MF runtime will be injected globally. If the producer sets `externalRuntime: true`, then the runtime provided by this consumer will be used. -* `false`: Default configuration, no operation will be performed on runtime +- Type: `boolean` +- Required: No +- Default: `false` + +::: warning note +Make sure to only configure it on the topmost consumer! If multiple consumers inject runtime at the same time, the ones executed later will not overwrite the existing runtime. +::: + +Setting `true` will inject the MF runtime at the consumer. diff --git a/apps/website-new/docs/zh/configure/experiments.mdx b/apps/website-new/docs/zh/configure/experiments.mdx index 0765d13a93b..ca1fda1f90e 100644 --- a/apps/website-new/docs/zh/configure/experiments.mdx +++ b/apps/website-new/docs/zh/configure/experiments.mdx @@ -69,14 +69,20 @@ new ModuleFederationPlugin({ ## externalRuntime -- Type: `boolean | 'provide'` +- Type: `boolean` - Required: No - Default: `false` -用于控制是否 external mf runtime 或提供 mf runtime 。 +设置 `true` 后 会 external MF runtime,并使用消费者提供的 runtime 。(请确保你的消费者有设置 `provideExternalRuntime: true`,否则无法正常运行!) -可以通过下列的配置来进行不同的行为 +## provideExternalRuntime -* `true`: 通常在生产者设置,设置后 会 external MF runtime,并使用消费者提供的 runtime 。(请确保你的消费者有设置 `externalRuntime: "provide"`,否则无法正常运行!) -* `'provide'`: 通常在最顶层消费者设置,设置后会在全局注入 MF runtime ,生产者若设置了 `externalRuntime: true` ,那么就会使用此消费者提供的运行时。 -* `false`: 默认配置,不会对 runtime 进行任何操作 +- Type: `boolean` +- Required: No +- Default: `false` + +::: warning 注意 +请确保仅在最顶层消费者配置!若同时有多个消费者注入 runtime,后执行的不会覆盖已有的 runtime。 +::: + +设置 `true` 后会在消费者处注入 MF runtime。 diff --git a/packages/enhanced/src/lib/container/ModuleFederationPlugin.ts b/packages/enhanced/src/lib/container/ModuleFederationPlugin.ts index 3f727354c94..3dd5dbb9e1d 100644 --- a/packages/enhanced/src/lib/container/ModuleFederationPlugin.ts +++ b/packages/enhanced/src/lib/container/ModuleFederationPlugin.ts @@ -72,14 +72,21 @@ class ModuleFederationPlugin implements WebpackPluginInstance { compiler, ); } - if (options.experiments?.externalRuntime === 'provide') { + if (options.experiments?.provideExternalRuntime) { + if (options.exposes) { + throw new Error( + 'You can only set provideExternalRuntime: true in pure consumer which not expose modules.', + ); + } const runtimePlugins = options.runtimePlugins || []; options.runtimePlugins = runtimePlugins.concat( require.resolve( '@module-federation/inject-external-runtime-core-plugin', ), ); - } else if (options.experiments?.externalRuntime === true) { + } + + if (options.experiments?.externalRuntime === true) { const Externals = compiler.webpack.ExternalsPlugin || ExternalsPlugin; new Externals(compiler.options.externalsType || 'global', { '@module-federation/runtime-core': '_FEDERATION_RUNTIME_CORE', diff --git a/packages/enhanced/src/schemas/container/ContainerPlugin.check.ts b/packages/enhanced/src/schemas/container/ContainerPlugin.check.ts index 0cda7f74bc0..c80302d72da 100644 --- a/packages/enhanced/src/schemas/container/ContainerPlugin.check.ts +++ b/packages/enhanced/src/schemas/container/ContainerPlugin.check.ts @@ -1057,7 +1057,11 @@ function l( { const t = f; for (const t in r) - if ('externalRuntime' !== t && 'federationRuntime' !== t) + if ( + 'externalRuntime' !== t && + 'federationRuntime' !== t && + 'provideExternalRuntime' !== t + ) return ( (l.errors = [ { @@ -1070,45 +1074,21 @@ function l( ); if (t === f) { if (void 0 !== r.externalRuntime) { - let t = r.externalRuntime; - const e = f, - n = f; - let s = !1; - const a = f; - if ('provide' !== t) { - const r = { - params: {}, - }; - null === p ? (p = [r]) : p.push(r), f++; - } - var u = a === f; - if (((s = s || u), !s)) { - const r = f; - if ('boolean' != typeof t) { - const r = { - params: { - type: 'boolean', - }, - }; - null === p ? (p = [r]) : p.push(r), f++; - } - (u = r === f), (s = s || u); - } - if (!s) { - const r = { - params: {}, - }; + const t = f; + if ('boolean' != typeof r.externalRuntime) return ( - null === p ? (p = [r]) : p.push(r), - f++, - (l.errors = p), + (l.errors = [ + { + params: { + type: 'boolean', + }, + }, + ]), !1 ); - } - (f = n), null !== p && (n ? (p.length = n) : (p = null)); - var c = e === f; - } else c = !0; - if (c) + var u = t === f; + } else u = !0; + if (u) { if (void 0 !== r.federationRuntime) { let t = r.federationRuntime; const e = f, @@ -1121,8 +1101,8 @@ function l( }; null === p ? (p = [r]) : p.push(r), f++; } - var m = a === f; - if (((s = s || m), !s)) { + var c = a === f; + if (((s = s || c), !s)) { const r = f; if ('boolean' != typeof t) { const r = { @@ -1132,7 +1112,7 @@ function l( }; null === p ? (p = [r]) : p.push(r), f++; } - (m = r === f), (s = s || m); + (c = r === f), (s = s || c); } if (!s) { const r = { @@ -1147,14 +1127,31 @@ function l( } (f = n), null !== p && (n ? (p.length = n) : (p = null)), - (c = e === f); - } else c = !0; + (u = e === f); + } else u = !0; + if (u) + if (void 0 !== r.provideExternalRuntime) { + const t = f; + if ('boolean' != typeof r.provideExternalRuntime) + return ( + (l.errors = [ + { + params: { + type: 'boolean', + }, + }, + ]), + !1 + ); + u = t === f; + } else u = !0; + } } } } - var y = e === f; - } else y = !0; - if (y) { + var m = e === f; + } else m = !0; + if (m) { if (void 0 !== t.exposes) { const r = f; s(t.exposes, { @@ -1165,9 +1162,9 @@ function l( }) || ((p = null === p ? s.errors : p.concat(s.errors)), (f = p.length)), - (y = r === f); - } else y = !0; - if (y) { + (m = r === f); + } else m = !0; + if (m) { if (void 0 !== t.filename) { let e = t.filename; const n = f; @@ -1202,9 +1199,9 @@ function l( !1 ); } - y = n === f; - } else y = !0; - if (y) { + m = n === f; + } else m = !0; + if (m) { if (void 0 !== t.library) { const r = f; i(t.library, { @@ -1215,9 +1212,9 @@ function l( }) || ((p = null === p ? i.errors : p.concat(i.errors)), (f = p.length)), - (y = r === f); - } else y = !0; - if (y) { + (m = r === f); + } else m = !0; + if (m) { if (void 0 !== t.name) { let r = t.name; const e = f; @@ -1243,9 +1240,9 @@ function l( !1 ); } - y = e === f; - } else y = !0; - if (y) { + m = e === f; + } else m = !0; + if (m) { if (void 0 !== t.runtime) { let r = t.runtime; const e = f, @@ -1258,8 +1255,8 @@ function l( }; null === p ? (p = [r]) : p.push(r), f++; } - var h = a === f; - if (((s = s || h), !s)) { + var y = a === f; + if (((s = s || y), !s)) { const t = f; if (f === t) if ('string' == typeof r) { @@ -1277,7 +1274,7 @@ function l( }; null === p ? (p = [r]) : p.push(r), f++; } - (h = t === f), (s = s || h); + (y = t === f), (s = s || y); } if (!s) { const r = { @@ -1292,9 +1289,9 @@ function l( } (f = n), null !== p && (n ? (p.length = n) : (p = null)), - (y = e === f); - } else y = !0; - if (y) { + (m = e === f); + } else m = !0; + if (m) { if (void 0 !== t.runtimePlugins) { let r = t.runtimePlugins; const e = f; @@ -1334,8 +1331,8 @@ function l( }; null === p ? (p = [r]) : p.push(r), f++; } - var g = o === f; - if (((a = a || g), !a)) { + var h = o === f; + if (((a = a || h), !a)) { const r = f; if (f === r) if ( @@ -1379,9 +1376,9 @@ function l( null === p ? (p = [r]) : p.push(r), f++; } - var d = r === f; - } else d = !0; - if (d) + var g = r === f; + } else g = !0; + if (g) if (void 0 !== t.import) { let r = t.import; const e = f; @@ -1407,8 +1404,8 @@ function l( : p.push(r), f++; } - d = e === f; - } else d = !0; + g = e === f; + } else g = !0; } } } else { @@ -1419,7 +1416,7 @@ function l( }; null === p ? (p = [r]) : p.push(r), f++; } - (g = r === f), (a = a || g); + (h = r === f), (a = a || h); } if (!a) { const r = { @@ -1441,9 +1438,9 @@ function l( } } } - y = e === f; - } else y = !0; - if (y) + m = e === f; + } else m = !0; + if (m) if (void 0 !== t.shareScope) { let r = t.shareScope; const e = f; @@ -1469,8 +1466,8 @@ function l( !1 ); } - y = e === f; - } else y = !0; + m = e === f; + } else m = !0; } } } diff --git a/packages/rspack/src/ModuleFederationPlugin.ts b/packages/rspack/src/ModuleFederationPlugin.ts index d16240fce99..3144dba3d26 100644 --- a/packages/rspack/src/ModuleFederationPlugin.ts +++ b/packages/rspack/src/ModuleFederationPlugin.ts @@ -74,14 +74,22 @@ export class ModuleFederationPlugin implements RspackPluginInstance { this._patchChunkSplit(compiler, options.name); } - if (options.experiments?.externalRuntime === 'provide') { + if (options.experiments?.provideExternalRuntime) { + if (options.exposes) { + throw new Error( + 'You can only set provideExternalRuntime: true in pure consumer which not expose modules.', + ); + } + const runtimePlugins = options.runtimePlugins || []; options.runtimePlugins = runtimePlugins.concat( require.resolve( '@module-federation/inject-external-runtime-core-plugin', ), ); - } else if (options.experiments?.externalRuntime === true) { + } + + if (options.experiments?.externalRuntime === true) { const Externals = compiler.webpack.ExternalsPlugin; new Externals(compiler.options.externalsType || 'global', { '@module-federation/runtime-core': '_FEDERATION_RUNTIME_CORE', diff --git a/packages/runtime-plugins/inject-external-runtime-core-plugin/rollup.config.js b/packages/runtime-plugins/inject-external-runtime-core-plugin/rollup.config.js index 0fed7abbe9d..092af6b0ef3 100644 --- a/packages/runtime-plugins/inject-external-runtime-core-plugin/rollup.config.js +++ b/packages/runtime-plugins/inject-external-runtime-core-plugin/rollup.config.js @@ -1,4 +1,6 @@ const copy = require('rollup-plugin-copy'); +const replace = require('@rollup/plugin-replace'); +const pkg = require('./package.json'); module.exports = (rollupConfig, _projectOptions) => { rollupConfig.plugins.push( @@ -52,5 +54,11 @@ module.exports = (rollupConfig, _projectOptions) => { }; } + rollupConfig.plugins.push( + replace({ + preventAssignment: true, + __VERSION__: JSON.stringify(pkg.version), + }), + ); return rollupConfig; }; diff --git a/packages/runtime-plugins/inject-external-runtime-core-plugin/src/index.ts b/packages/runtime-plugins/inject-external-runtime-core-plugin/src/index.ts index c95fc75c5cd..995b238bf2d 100644 --- a/packages/runtime-plugins/inject-external-runtime-core-plugin/src/index.ts +++ b/packages/runtime-plugins/inject-external-runtime-core-plugin/src/index.ts @@ -2,14 +2,37 @@ import * as runtimeCore from '@module-federation/runtime-tools/runtime-core'; import type { FederationRuntimePlugin } from '@module-federation/runtime-tools/runtime-core'; declare global { + var __VERSION__: string; var _FEDERATION_RUNTIME_CORE: typeof runtimeCore; + var _FEDERATION_RUNTIME_CORE_FROM: { + version: string; + name: string; + }; } function injectExternalRuntimeCorePlugin(): FederationRuntimePlugin { return { name: 'inject-external-runtime-core-plugin', + version: __VERSION__, beforeInit(args) { + const name = args.options.name; + const version = __VERSION__; + if ( + runtimeCore.Global._FEDERATION_RUNTIME_CORE && + runtimeCore.Global._FEDERATION_RUNTIME_CORE_FROM && + (runtimeCore.Global._FEDERATION_RUNTIME_CORE_FROM.name !== name || + runtimeCore.Global._FEDERATION_RUNTIME_CORE_FROM.version !== version) + ) { + console.warn( + `Detect multiple module federation runtime! Injected runtime from ${runtimeCore.Global._FEDERATION_RUNTIME_CORE_FROM.name}@${runtimeCore.Global._FEDERATION_RUNTIME_CORE_FROM.version} and current is ${name}@${version}, pleasure ensure there is only one consumer to provider runtime!`, + ); + return args; + } runtimeCore.Global._FEDERATION_RUNTIME_CORE = runtimeCore; + runtimeCore.Global._FEDERATION_RUNTIME_CORE_FROM = { + version, + name, + }; return args; }, }; diff --git a/packages/sdk/src/types/plugins/ModuleFederationPlugin.ts b/packages/sdk/src/types/plugins/ModuleFederationPlugin.ts index a4dc892a748..f5957e3993f 100644 --- a/packages/sdk/src/types/plugins/ModuleFederationPlugin.ts +++ b/packages/sdk/src/types/plugins/ModuleFederationPlugin.ts @@ -237,7 +237,8 @@ export interface ModuleFederationPluginOptions { virtualRuntimeEntry?: boolean; experiments?: { federationRuntime?: false | 'hoisted'; - externalRuntime?: boolean | 'provide'; + externalRuntime?: boolean; + provideExternalRuntime?: boolean; }; bridge?: { /**