From 8a8c073e0b29d15b77b1edbead546d3aa27ffb52 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Fri, 3 May 2024 23:32:12 -0700 Subject: [PATCH 1/3] feat(webpack-bundler-runtime): create container function --- package.json | 2 +- packages/webpack-bundler-runtime/package.json | 7 + packages/webpack-bundler-runtime/project.json | 3 +- .../webpack-bundler-runtime/src/container.ts | 271 ++++++++++++++++++ 4 files changed, 281 insertions(+), 2 deletions(-) create mode 100644 packages/webpack-bundler-runtime/src/container.ts diff --git a/package.json b/package.json index fdd559f350c..d8e7c60ab21 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "app:next:prod": "nx run-many --target=serve --configuration=production -p 3000-home,3001-shop,3002-checkout", "app:node:dev": "nx run-many --target=serve --configuration=development -p node-host,node-local-remote,node-remote", "app:runtime:dev": "nx run-many --target=serve -p 3005-runtime-host,3006-runtime-remote,3007-runtime-remote", - "app:manifest:dev": "nx run-many --target=serve --parallel=10 -p 3008-webpack-host,3009-webpack-provider,3010-rspack-provider,3011-rspack-manifest-provider,3012-rspack-js-entry-provider", + "app:manifest:dev": "nx run-many --target=serve --parallel=100 -p 3008-webpack-host,3009-webpack-provider,3010-rspack-provider,3011-rspack-manifest-provider,3012-rspack-js-entry-provider", "app:ts:dev": "nx run-many --target=serve -p react_ts_host,react_ts_nested_remote,react_ts_remote", "commitlint": "commitlint --edit", "prepare": "husky install", diff --git a/packages/webpack-bundler-runtime/package.json b/packages/webpack-bundler-runtime/package.json index 207393113e1..fcc7e59df61 100644 --- a/packages/webpack-bundler-runtime/package.json +++ b/packages/webpack-bundler-runtime/package.json @@ -32,6 +32,10 @@ "import": "./dist/constant.esm.js", "require": "./dist/constant.cjs.js" }, + "./container": { + "import": "./dist/container.esm.js", + "require": "./dist/container.cjs.js" + }, "./*": "./*" }, "typesVersions": { @@ -43,5 +47,8 @@ "./dist/constant.cjs.d.ts" ] } + }, + "devDependencies": { + "@module-federation/runtime": "workspace:*" } } diff --git a/packages/webpack-bundler-runtime/project.json b/packages/webpack-bundler-runtime/project.json index 78283027883..62e684b30ee 100644 --- a/packages/webpack-bundler-runtime/project.json +++ b/packages/webpack-bundler-runtime/project.json @@ -17,7 +17,8 @@ "compiler": "swc", "format": ["cjs", "esm"], "additionalEntryPoints": [ - "packages/webpack-bundler-runtime/src/constant.ts" + "packages/webpack-bundler-runtime/src/constant.ts", + "packages/webpack-bundler-runtime/src/container.ts" ], "external": ["@module-federation/*"], "buildableProjectDepsInPackageJsonType": "dependencies", diff --git a/packages/webpack-bundler-runtime/src/container.ts b/packages/webpack-bundler-runtime/src/container.ts new file mode 100644 index 00000000000..4a2d122bb14 --- /dev/null +++ b/packages/webpack-bundler-runtime/src/container.ts @@ -0,0 +1,271 @@ +import bundler_runtime_base from './index'; +import type { Options } from '@module-federation/runtime/types'; + +interface ExposesConfig { + /** + * Request to a module that should be exposed by this container. + */ + import: ExposesItem | ExposesItems; + /** + * Custom chunk name for the exposed module. + */ + name?: string; +} + +type Exposes = (ExposesItem | ExposesObject)[] | ExposesObject; +/** + * Module that should be exposed by this container. + */ +type ExposesItem = string; +/** + * Modules that should be exposed by this container. + */ +type ExposesItems = ExposesItem[]; + +interface ExposesObject { + /** + * Modules that should be exposed by this container. + */ + [k: string]: ExposesConfig | ExposesItem | ExposesItems; +} + +interface ExtendedOptions extends Options { + exposes: { [key: string]: () => Promise<() => any> }; +} + +export const createContainer = async (federationOptions: ExtendedOptions) => { + // await instantiatePatch(federationOptions, true); + const { exposes, name, remotes = [], shared, plugins } = federationOptions; + + const __webpack_modules__ = { + './node_modules/.federation/entry.1f2288102e035e2ed66b2efaf60ad043.js': ( + //@ts-ignore + module, + //@ts-ignore + __webpack_exports__, + //@ts-ignore + __webpack_require__, + ) => { + __webpack_require__.r(__webpack_exports__); + const bundler_runtime = __webpack_require__.n(bundler_runtime_base); + const prevFederation = __webpack_require__.federation; + __webpack_require__.federation = {}; + for (const key in bundler_runtime()) { + __webpack_require__.federation[key] = bundler_runtime()[key]; + } + for (const key in prevFederation) { + __webpack_require__.federation[key] = prevFederation[key]; + } + if (!__webpack_require__.federation.instance) { + const pluginsToAdd = plugins || []; + __webpack_require__.federation.initOptions.plugins = __webpack_require__ + .federation.initOptions.plugins + ? __webpack_require__.federation.initOptions.plugins.concat( + pluginsToAdd, + ) + : pluginsToAdd; + __webpack_require__.federation.instance = + __webpack_require__.federation.runtime.init( + __webpack_require__.federation.initOptions, + ); + if (__webpack_require__.federation.attachShareScopeMap) { + __webpack_require__.federation.attachShareScopeMap( + __webpack_require__, + ); + } + if (__webpack_require__.federation.installInitialConsumes) { + __webpack_require__.federation.installInitialConsumes(); + } + } + }, + //@ts-ignore + 'webpack/container/entry/createContainer': ( + //@ts-ignore + + module, + //@ts-ignore + exports, + //@ts-ignore + __webpack_require__, + ) => { + const moduleMap = {}; + for (const key in exposes) { + if (Object.prototype.hasOwnProperty.call(exposes, key)) { + //@ts-ignore + moduleMap[key] = () => + Promise.resolve(exposes[key]()).then((m) => () => m); + } + } + //@ts-ignore + const get = (module, getScope) => { + __webpack_require__.R = getScope; + getScope = __webpack_require__.o(moduleMap, module) + ? //@ts-ignore + moduleMap[module]() + : Promise.resolve().then(() => { + throw new Error( + `Module "${module}" does not exist in container.`, + ); + }); + __webpack_require__.R = undefined; + return getScope; + }; + //@ts-ignore + const init = (shareScope, initScope, remoteEntryInitOptions) => { + return __webpack_require__.federation.bundlerRuntime.initContainerEntry( + { + webpackRequire: __webpack_require__, + shareScope: shareScope, + initScope: initScope, + remoteEntryInitOptions: remoteEntryInitOptions, + shareScopeKey: 'default', + }, + ); + }; + __webpack_require__( + './node_modules/.federation/entry.1f2288102e035e2ed66b2efaf60ad043.js', + ); + + // This exports getters to disallow modifications + __webpack_require__.d(exports, { + get: () => get, + init: () => init, + moduleMap: () => moduleMap, + }); + }, + }; + + const __webpack_module_cache__ = {}; + + //@ts-ignore + const __webpack_require__ = (moduleId) => { + //@ts-ignore + let cachedModule = __webpack_module_cache__[moduleId]; + if (cachedModule !== undefined) { + return cachedModule.exports; + } + //@ts-ignore + let module = (__webpack_module_cache__[moduleId] = { + id: moduleId, + loaded: false, + exports: {}, + }); + + const execOptions = { + id: moduleId, + module: module, + //@ts-ignore + factory: __webpack_modules__[moduleId], + require: __webpack_require__, + }; + __webpack_require__.i.forEach((handler) => { + handler(execOptions); + }); + module = execOptions.module; + execOptions.factory.call( + module.exports, + module, + module.exports, + execOptions.require, + ); + + module.loaded = true; + + return module.exports; + }; + + __webpack_require__.m = __webpack_modules__; + __webpack_require__.c = __webpack_module_cache__; + //@ts-ignore + __webpack_require__.i = []; + + //@ts-ignore + if (!__webpack_require__.federation) { + __webpack_require__.federation = { + initOptions: { + name: name, + //@ts-ignore + remotes: remotes.map((remote) => ({ + type: remote.type, + alias: remote.alias, + name: remote.name, + //@ts-ignore + entry: remote.entry, + shareScope: remote.shareScope || 'default', + })), + }, + chunkMatcher: () => true, + rootOutputDir: '', + initialConsumes: undefined, + bundlerRuntimeOptions: {}, + }; + } + //@ts-ignore + __webpack_require__.n = (module) => { + const getter = + module && module.__esModule ? () => module['default'] : () => module; + __webpack_require__.d(getter, { a: getter }); + return getter; + }; + + //@ts-ignore + __webpack_require__.d = (exports, definition) => { + for (const key in definition) { + if ( + __webpack_require__.o(definition, key) && + !__webpack_require__.o(exports, key) + ) { + Object.defineProperty(exports, key, { + enumerable: true, + get: definition[key], + }); + } + } + }; + + __webpack_require__.f = {}; + + __webpack_require__.g = (() => { + if (typeof globalThis === 'object') return globalThis; + try { + return this || new Function('return this')(); + } catch (e) { + if (typeof window === 'object') return window; + } + })(); + + //@ts-ignore + __webpack_require__.o = (obj, prop) => + Object.prototype.hasOwnProperty.call(obj, prop); + //@ts-ignore + __webpack_require__.r = (exports) => { + if (typeof Symbol !== 'undefined' && Symbol.toStringTag) { + Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); + } + Object.defineProperty(exports, '__esModule', { value: true }); + }; + + //@ts-ignore + __webpack_require__.federation.initOptions.shared = shared; + __webpack_require__.S = {}; + const initPromises = {}; + const initTokens = {}; + //@ts-ignore + __webpack_require__.I = (name, initScope) => { + //@ts-ignore + return __webpack_require__.federation.bundlerRuntime.I({ + shareScopeName: name, + webpackRequire: __webpack_require__, + initPromises: initPromises, + initTokens: initTokens, + initScope: initScope, + }); + }; + + const __webpack_exports__ = __webpack_require__( + 'webpack/container/entry/createContainer', + ); + const __webpack_exports__get = __webpack_exports__.get; + const __webpack_exports__init = __webpack_exports__.init; + return __webpack_exports__; +}; From 94e3631c019455451b398927c6361aca782266d6 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Wed, 15 May 2024 12:47:49 -0700 Subject: [PATCH 2/3] refactor(webpack-bundler-runtime): clean up types --- .../webpack-bundler-runtime/src/container.ts | 41 ++++--------------- 1 file changed, 9 insertions(+), 32 deletions(-) diff --git a/packages/webpack-bundler-runtime/src/container.ts b/packages/webpack-bundler-runtime/src/container.ts index 4a2d122bb14..bdfd59ce048 100644 --- a/packages/webpack-bundler-runtime/src/container.ts +++ b/packages/webpack-bundler-runtime/src/container.ts @@ -1,40 +1,11 @@ import bundler_runtime_base from './index'; -import type { Options } from '@module-federation/runtime/types'; +import type { UserOptions } from '@module-federation/runtime/types'; -interface ExposesConfig { - /** - * Request to a module that should be exposed by this container. - */ - import: ExposesItem | ExposesItems; - /** - * Custom chunk name for the exposed module. - */ - name?: string; -} - -type Exposes = (ExposesItem | ExposesObject)[] | ExposesObject; -/** - * Module that should be exposed by this container. - */ -type ExposesItem = string; -/** - * Modules that should be exposed by this container. - */ -type ExposesItems = ExposesItem[]; - -interface ExposesObject { - /** - * Modules that should be exposed by this container. - */ - [k: string]: ExposesConfig | ExposesItem | ExposesItems; -} - -interface ExtendedOptions extends Options { +interface ExtendedOptions extends UserOptions { exposes: { [key: string]: () => Promise<() => any> }; } -export const createContainer = async (federationOptions: ExtendedOptions) => { - // await instantiatePatch(federationOptions, true); +export const createContainer = (federationOptions: ExtendedOptions) => { const { exposes, name, remotes = [], shared, plugins } = federationOptions; const __webpack_modules__ = { @@ -269,3 +240,9 @@ export const createContainer = async (federationOptions: ExtendedOptions) => { const __webpack_exports__init = __webpack_exports__.init; return __webpack_exports__; }; +export const createContainerAsync = async ( + federationOptions: ExtendedOptions, +) => { + // todo: consider async startup options here, for "async boundary" provision. + return createContainer(federationOptions); +}; From ff6c8d280e234bd6b83f785562a7be09cdc9633f Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Fri, 17 May 2024 15:33:03 -0700 Subject: [PATCH 3/3] chore: changeset --- .changeset/new-eyes-hang.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/new-eyes-hang.md diff --git a/.changeset/new-eyes-hang.md b/.changeset/new-eyes-hang.md new file mode 100644 index 00000000000..cf6a8956f0c --- /dev/null +++ b/.changeset/new-eyes-hang.md @@ -0,0 +1,5 @@ +--- +'@module-federation/webpack-bundler-runtime': patch +--- + +createContainer factory function added. Allows container interfaced to be dynamically created