diff --git a/.changeset/steady-workers-remotes.md b/.changeset/steady-workers-remotes.md
new file mode 100644
index 00000000000..d37ee1ea5db
--- /dev/null
+++ b/.changeset/steady-workers-remotes.md
@@ -0,0 +1,5 @@
+---
+"@module-federation/enhanced": patch
+---
+
+Keep async entry runtime helpers available when cloning runtimes for web workers and other dynamic entrypoints.
diff --git a/.env b/.env
index 166ab9c8e68..e871d6f9876 100644
--- a/.env
+++ b/.env
@@ -1 +1,2 @@
NX_DEAMON=false
+NX_DAEMON=false
diff --git a/.github/workflows/e2e-runtime.yml b/.github/workflows/e2e-runtime.yml
index 01b24ddff61..400d9f818ae 100644
--- a/.github/workflows/e2e-runtime.yml
+++ b/.github/workflows/e2e-runtime.yml
@@ -62,4 +62,4 @@ jobs:
- name: E2E Test for Runtime Demo
if: steps.check-ci.outcome == 'success'
- run: npx kill-port --port 3005,3006,3007 && pnpm run app:runtime:dev & echo "done" && sleep 20 && npx nx run-many --target=test:e2e --projects=3005-runtime-host --parallel=1 && lsof -ti tcp:3005,3006,3007 | xargs kill
+ run: node tools/scripts/run-runtime-e2e.mjs --mode=dev
diff --git a/apps/3000-home/cypress.config.ts b/apps/3000-home/cypress.config.ts
index ee8862e00a0..2fe627196b6 100644
--- a/apps/3000-home/cypress.config.ts
+++ b/apps/3000-home/cypress.config.ts
@@ -1,6 +1,8 @@
import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
import { defineConfig } from 'cypress';
+const isCI = !!process.env.CI;
+
export default defineConfig({
projectId: 'sa6wfn',
e2e: {
@@ -11,7 +13,7 @@ export default defineConfig({
},
defaultCommandTimeout: 20000,
retries: {
- runMode: 2,
- openMode: 1,
+ runMode: isCI ? 2 : 0, // Retry in CI, fail fast locally
+ openMode: isCI ? 1 : 0,
},
});
diff --git a/apps/3001-shop/cypress.config.ts b/apps/3001-shop/cypress.config.ts
index 48060ad905c..cd895024683 100644
--- a/apps/3001-shop/cypress.config.ts
+++ b/apps/3001-shop/cypress.config.ts
@@ -1,6 +1,8 @@
import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
import { defineConfig } from 'cypress';
+const isCI = !!process.env.CI;
+
export default defineConfig({
e2e: {
...nxE2EPreset(__filename, { cypressDir: 'cypress' }),
@@ -10,7 +12,7 @@ export default defineConfig({
},
defaultCommandTimeout: 10000,
retries: {
- runMode: 3,
- openMode: 2,
+ runMode: isCI ? 3 : 0, // Retry in CI, fail fast locally
+ openMode: isCI ? 2 : 0,
},
});
diff --git a/apps/3002-checkout/cypress.config.ts b/apps/3002-checkout/cypress.config.ts
index 98ecee84114..373b9a501f0 100644
--- a/apps/3002-checkout/cypress.config.ts
+++ b/apps/3002-checkout/cypress.config.ts
@@ -1,6 +1,8 @@
import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
import { defineConfig } from 'cypress';
+const isCI = !!process.env.CI;
+
export default defineConfig({
e2e: {
...nxE2EPreset(__filename, { cypressDir: 'cypress' }),
@@ -10,7 +12,7 @@ export default defineConfig({
},
defaultCommandTimeout: 15000,
retries: {
- runMode: 2,
- openMode: 1,
+ runMode: isCI ? 2 : 0, // Retry in CI, fail fast locally
+ openMode: isCI ? 1 : 0,
},
});
diff --git a/apps/runtime-demo/3005-runtime-host/cypress/e2e/app.cy.ts b/apps/runtime-demo/3005-runtime-host/cypress/e2e/app.cy.ts
index 4383ea93ec1..126c137b961 100644
--- a/apps/runtime-demo/3005-runtime-host/cypress/e2e/app.cy.ts
+++ b/apps/runtime-demo/3005-runtime-host/cypress/e2e/app.cy.ts
@@ -77,4 +77,21 @@ describe('3005-runtime-host/', () => {
});
});
});
+
+ describe('web worker check', () => {
+ it('should display value returned from worker', () => {
+ cy.get('.worker-native-result').should('contain.text', '"answer": "1"');
+ cy.get('.worker-native-result').should(
+ 'contain.text',
+ '"federationKeys"',
+ );
+ cy.get('.worker-loader-result').should('contain.text', '"answer": "1"');
+ cy.get('.worker-loader-result').should(
+ 'contain.text',
+ '"federationKeys"',
+ );
+ cy.get('.worker-blob-result').should('contain.text', '"answer": "1"');
+ cy.get('.worker-blob-result').should('contain.text', '"federationKeys"');
+ });
+ });
});
diff --git a/apps/runtime-demo/3005-runtime-host/package.json b/apps/runtime-demo/3005-runtime-host/package.json
index 993cae30289..13af902dec8 100644
--- a/apps/runtime-demo/3005-runtime-host/package.json
+++ b/apps/runtime-demo/3005-runtime-host/package.json
@@ -2,16 +2,25 @@
"name": "runtime-host",
"private": true,
"version": "0.0.0",
+ "scripts": {
+ "build": "webpack --config webpack.config.js --mode production",
+ "build:development": "webpack --config webpack.config.js --mode development",
+ "serve": "webpack serve --config webpack.config.js --mode development --host 127.0.0.1 --port 3005 --allowed-hosts all",
+ "serve:production": "webpack serve --config webpack.config.js --mode production --host 127.0.0.1 --port 3005 --allowed-hosts all --no-hot"
+ },
"devDependencies": {
"@module-federation/core": "workspace:*",
+ "@module-federation/dts-plugin": "workspace:*",
+ "@module-federation/enhanced": "workspace:*",
"@module-federation/runtime": "workspace:*",
"@module-federation/typescript": "workspace:*",
- "@module-federation/enhanced": "workspace:*",
- "@module-federation/dts-plugin": "workspace:*",
"@pmmmwh/react-refresh-webpack-plugin": "0.5.15",
- "react-refresh": "0.14.2",
"@types/react": "18.3.11",
- "@types/react-dom": "18.3.0"
+ "@types/react-dom": "18.3.0",
+ "mini-css-extract-plugin": "2.9.2",
+ "react-refresh": "0.14.2",
+ "css-loader": "6.11.0",
+ "webpack-dev-server": "5.1.0"
},
"dependencies": {
"antd": "4.24.15",
diff --git a/apps/runtime-demo/3005-runtime-host/project.json b/apps/runtime-demo/3005-runtime-host/project.json
index 4453a83f674..edf63d2c86d 100644
--- a/apps/runtime-demo/3005-runtime-host/project.json
+++ b/apps/runtime-demo/3005-runtime-host/project.json
@@ -6,35 +6,21 @@
"tags": [],
"targets": {
"build": {
- "executor": "@nx/webpack:webpack",
- "outputs": ["{options.outputPath}"],
+ "executor": "nx:run-commands",
+ "outputs": ["{projectRoot}/dist"],
"defaultConfiguration": "production",
"options": {
- "compiler": "babel",
- "outputPath": "apps/runtime-demo/3005-runtime-host/dist",
- "index": "apps/runtime-demo/3005-runtime-host/src/index.html",
- "baseHref": "/",
- "main": "apps/runtime-demo/3005-runtime-host/src/index.ts",
- "tsConfig": "apps/runtime-demo/3005-runtime-host/tsconfig.app.json",
- "styles": [],
- "scripts": [],
- "webpackConfig": "apps/runtime-demo/3005-runtime-host/webpack.config.js",
- "babelUpwardRootMode": true
+ "command": "pnpm run build",
+ "cwd": "apps/runtime-demo/3005-runtime-host"
},
"configurations": {
"development": {
- "extractLicenses": false,
- "optimization": false,
- "sourceMap": true,
- "vendorChunk": true
+ "command": "pnpm run build:development",
+ "cwd": "apps/runtime-demo/3005-runtime-host"
},
"production": {
- "optimization": true,
- "outputHashing": "all",
- "sourceMap": false,
- "namedChunks": false,
- "extractLicenses": false,
- "vendorChunk": false
+ "command": "pnpm run build",
+ "cwd": "apps/runtime-demo/3005-runtime-host"
}
},
"dependsOn": [
@@ -45,21 +31,20 @@
]
},
"serve": {
- "executor": "@nx/webpack:dev-server",
+ "executor": "nx:run-commands",
"defaultConfiguration": "production",
"options": {
- "buildTarget": "3005-runtime-host:build",
- "hmr": true,
- "port": 3005,
- "devRemotes": ["3006-runtime-remote"]
+ "command": "pnpm run serve:production",
+ "cwd": "apps/runtime-demo/3005-runtime-host"
},
"configurations": {
"development": {
- "buildTarget": "3005-runtime-host:build:development"
+ "command": "pnpm run serve",
+ "cwd": "apps/runtime-demo/3005-runtime-host"
},
"production": {
- "buildTarget": "3005-runtime-host:build:production",
- "hmr": false
+ "command": "pnpm run serve:production",
+ "cwd": "apps/runtime-demo/3005-runtime-host"
}
},
"dependsOn": [
diff --git a/apps/runtime-demo/3005-runtime-host/src/Root.tsx b/apps/runtime-demo/3005-runtime-host/src/Root.tsx
index ede13ed7b31..f1aa97de46b 100644
--- a/apps/runtime-demo/3005-runtime-host/src/Root.tsx
+++ b/apps/runtime-demo/3005-runtime-host/src/Root.tsx
@@ -5,6 +5,9 @@ import WebpackPng from './webpack.png';
import WebpackSvg from './webpack.svg';
import { WebpackPngRemote, WebpackSvgRemote } from './Remote1';
import Remote2 from './Remote2';
+import WorkerNativeDemo from './components/WorkerNativeDemo';
+import WorkerLoaderDemo from './components/WorkerLoaderDemo';
+import WorkerBlobDemo from './components/WorkerBlobDemo';
const Root = () => (
@@ -89,6 +92,50 @@ const Root = () => (
+
+
check workers
+
+
+
+ |
+ Test case |
+ Expected |
+ Actual |
+
+
+
+
+ | ✅ |
+ Native new Worker(new URL(...)) |
+
+ Expected worker response: 1
+ |
+
+
+ |
+
+
+ | ✅ |
+ worker-loader integration |
+
+ Expected worker response: 1
+ |
+
+
+ |
+
+
+ | ✅ |
+ Blob-wrapped module worker importing loader-worker |
+
+ Expected worker response: 1
+ |
+
+
+ |
+
+
+
);
diff --git a/apps/runtime-demo/3005-runtime-host/src/components/WorkerBlobDemo.tsx b/apps/runtime-demo/3005-runtime-host/src/components/WorkerBlobDemo.tsx
new file mode 100644
index 00000000000..f3bcb01f187
--- /dev/null
+++ b/apps/runtime-demo/3005-runtime-host/src/components/WorkerBlobDemo.tsx
@@ -0,0 +1,61 @@
+import { useEffect, useState } from 'react';
+
+export function WorkerBlobDemo() {
+ const [result, setResult] = useState(null);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ let objectUrl: string | undefined;
+ let worker: Worker | undefined;
+
+ const cleanup = () => {
+ if (worker) {
+ worker.terminate();
+ }
+ if (objectUrl) {
+ URL.revokeObjectURL(objectUrl);
+ }
+ };
+
+ try {
+ const loaderUrl = new URL('../worker/loader-worker.js', import.meta.url);
+ const blob = new Blob([`import '${loaderUrl}';`], {
+ type: 'application/javascript',
+ });
+
+ objectUrl = URL.createObjectURL(blob);
+ worker = new Worker(objectUrl, {
+ name: 'mf-blob-worker',
+ type: 'module',
+ });
+
+ worker.onmessage = (event) => {
+ setResult(event.data ?? null);
+ };
+
+ worker.onerror = (event) => {
+ setError((event as unknown as ErrorEvent).message ?? 'Worker error');
+ };
+
+ worker.postMessage({ value: 'foo' });
+ } catch (err) {
+ setError((err as Error).message);
+ cleanup();
+ return cleanup;
+ }
+
+ return cleanup;
+ }, []);
+
+ return (
+
+
Expected worker response: 1
+
+ {result ? JSON.stringify(result, null, 2) : 'n/a'}
+
+ {error ?
Worker error: {error}
: null}
+
+ );
+}
+
+export default WorkerBlobDemo;
diff --git a/apps/runtime-demo/3005-runtime-host/src/components/WorkerLoaderDemo.tsx b/apps/runtime-demo/3005-runtime-host/src/components/WorkerLoaderDemo.tsx
new file mode 100644
index 00000000000..6206cdf7760
--- /dev/null
+++ b/apps/runtime-demo/3005-runtime-host/src/components/WorkerLoaderDemo.tsx
@@ -0,0 +1,47 @@
+import { useEffect, useState } from 'react';
+
+export function WorkerLoaderDemo() {
+ const [result, setResult] = useState(null);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ try {
+ const worker = new Worker(
+ new URL('../worker/loader-worker.js', import.meta.url),
+ {
+ name: 'mf-loader-worker',
+ },
+ );
+
+ worker.onmessage = (event) => {
+ setResult(event.data ?? null);
+ };
+
+ worker.onerror = (event) => {
+ setError((event as unknown as ErrorEvent).message ?? 'Worker error');
+ };
+
+ worker.postMessage({ value: 'foo' });
+
+ return () => {
+ worker.terminate();
+ };
+ } catch (err) {
+ setError((err as Error).message);
+ }
+
+ return undefined;
+ }, []);
+
+ return (
+
+
Expected worker response: 1
+
+ {result ? JSON.stringify(result, null, 2) : 'n/a'}
+
+ {error ?
Worker error: {error}
: null}
+
+ );
+}
+
+export default WorkerLoaderDemo;
diff --git a/apps/runtime-demo/3005-runtime-host/src/components/WorkerNativeDemo.tsx b/apps/runtime-demo/3005-runtime-host/src/components/WorkerNativeDemo.tsx
new file mode 100644
index 00000000000..22b1d381082
--- /dev/null
+++ b/apps/runtime-demo/3005-runtime-host/src/components/WorkerNativeDemo.tsx
@@ -0,0 +1,46 @@
+import { useEffect, useState } from 'react';
+
+export function WorkerNativeDemo() {
+ const [result, setResult] = useState(null);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ try {
+ const worker = new Worker(
+ new URL('../worker/native-worker.js', import.meta.url),
+ {
+ name: 'mf-native-worker',
+ },
+ );
+
+ worker.onmessage = (event) => {
+ setResult(event.data ?? null);
+ };
+
+ worker.onerror = (event) => {
+ setError((event as unknown as ErrorEvent).message ?? 'Worker error');
+ };
+
+ worker.postMessage({ value: 'foo' });
+
+ return () => {
+ worker.terminate();
+ };
+ } catch (err) {
+ setError((err as Error).message);
+ }
+
+ return undefined;
+ }, []);
+
+ return (
+
+
+ {result ? JSON.stringify(result, null, 2) : 'n/a'}
+
+ {error ?
Worker error: {error}
: null}
+
+ );
+}
+
+export default WorkerNativeDemo;
diff --git a/apps/runtime-demo/3005-runtime-host/src/worker/loader-worker.js b/apps/runtime-demo/3005-runtime-host/src/worker/loader-worker.js
new file mode 100644
index 00000000000..a1d56ea6735
--- /dev/null
+++ b/apps/runtime-demo/3005-runtime-host/src/worker/loader-worker.js
@@ -0,0 +1,12 @@
+/* eslint-env worker */
+/* global __webpack_require__ */
+import { workerMap } from './map.js';
+
+self.onmessage = (event) => {
+ const value = event.data && event.data.value;
+ const federationKeys = Object.keys(__webpack_require__.federation);
+ self.postMessage({
+ answer: workerMap[value] ?? null,
+ federationKeys,
+ });
+};
diff --git a/apps/runtime-demo/3005-runtime-host/src/worker/map.js b/apps/runtime-demo/3005-runtime-host/src/worker/map.js
new file mode 100644
index 00000000000..f9ab0ec2f3c
--- /dev/null
+++ b/apps/runtime-demo/3005-runtime-host/src/worker/map.js
@@ -0,0 +1,4 @@
+export const workerMap = {
+ foo: '1',
+ bar: '2',
+};
diff --git a/apps/runtime-demo/3005-runtime-host/src/worker/native-worker.js b/apps/runtime-demo/3005-runtime-host/src/worker/native-worker.js
new file mode 100644
index 00000000000..a02258fb43d
--- /dev/null
+++ b/apps/runtime-demo/3005-runtime-host/src/worker/native-worker.js
@@ -0,0 +1,15 @@
+/* eslint-env worker */
+import { workerMap } from './map.js';
+
+self.onmessage = (event) => {
+ const value = event.data && event.data.value;
+ const federation =
+ typeof __webpack_require__ !== 'undefined'
+ ? __webpack_require__.federation || {}
+ : {};
+ const federationKeys = Object.keys(federation);
+ self.postMessage({
+ answer: workerMap[value] ?? null,
+ federationKeys,
+ });
+};
diff --git a/apps/runtime-demo/3005-runtime-host/src/worker/worker.js b/apps/runtime-demo/3005-runtime-host/src/worker/worker.js
new file mode 100644
index 00000000000..69a165ec73e
--- /dev/null
+++ b/apps/runtime-demo/3005-runtime-host/src/worker/worker.js
@@ -0,0 +1,9 @@
+/* eslint-env worker */
+import { workerMap } from './map';
+
+self.onmessage = (event) => {
+ const value = event.data && event.data.value;
+ self.postMessage({
+ answer: workerMap[value] ?? null,
+ });
+};
diff --git a/apps/runtime-demo/3005-runtime-host/webpack.config.js b/apps/runtime-demo/3005-runtime-host/webpack.config.js
index 05b99f7e4b7..5f5ac54b828 100644
--- a/apps/runtime-demo/3005-runtime-host/webpack.config.js
+++ b/apps/runtime-demo/3005-runtime-host/webpack.config.js
@@ -1,108 +1,187 @@
const path = require('path');
// const { registerPluginTSTranspiler } = require('nx/src/utils/nx-plugin.js');
// registerPluginTSTranspiler();
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const {
ModuleFederationPlugin,
} = require('@module-federation/enhanced/webpack');
-const { composePlugins, withNx } = require('@nx/webpack');
-const { withReact } = require('@nx/react');
-module.exports = composePlugins(withNx(), withReact(), (config, context) => {
- config.watchOptions = {
- ignored: ['**/node_modules/**', '**/@mf-types/**', '**/dist/**'],
- };
+const DIST_PATH = path.resolve(__dirname, 'dist');
+const SRC_PATH = path.resolve(__dirname, 'src');
- // const ModuleFederationPlugin = webpack.container.ModuleFederationPlugin;
- config.plugins.push(
- new ModuleFederationPlugin({
- name: 'runtime_host',
- experiments: { asyncStartup: true },
- remotes: {
- // remote2: 'runtime_remote2@http://localhost:3007/remoteEntry.js',
- remote1: 'runtime_remote1@http://127.0.0.1:3006/mf-manifest.json',
- // remote1: `promise new Promise((resolve)=>{
- // const raw = 'runtime_remote1@http://127.0.0.1:3006/remoteEntry.js'
- // const [_, remoteUrlWithVersion] = raw.split('@')
- // const script = document.createElement('script')
- // script.src = remoteUrlWithVersion
- // script.onload = () => {
- // const proxy = {
- // get: (request) => window.runtime_remote1.get(request),
- // init: (arg) => {
- // try {
- // return window.runtime_remote1.init(arg)
- // } catch(e) {
- // console.log('runtime_remote1 container already initialized')
- // }
- // }
- // }
- // resolve(proxy)
- // }
- // document.head.appendChild(script);
- // })`,
- },
- // library: { type: 'var', name: 'runtime_remote' },
- filename: 'remoteEntry.js',
- exposes: {
- './Button': './src/Button.tsx',
- },
- dts: {
- tsConfigPath: path.resolve(__dirname, 'tsconfig.app.json'),
+module.exports = (_env = {}, argv = {}) => {
+ const mode = argv.mode || process.env.NODE_ENV || 'development';
+ const isDevelopment = mode === 'development';
+ const isWebpackServe = Boolean(
+ argv.env?.WEBPACK_SERVE ?? process.env.WEBPACK_SERVE === 'true',
+ );
+
+ return {
+ mode,
+ devtool: false,
+ entry: path.join(SRC_PATH, 'index.ts'),
+ output: {
+ path: DIST_PATH,
+ filename: isDevelopment ? '[name].js' : '[name].[contenthash].js',
+ publicPath: 'auto',
+ clean: true,
+ scriptType: 'text/javascript',
+ environment: {
+ asyncFunction: true,
},
- shareStrategy: 'loaded-first',
- shared: {
- lodash: {
- singleton: true,
- requiredVersion: '^4.0.0',
+ },
+ resolve: {
+ extensions: ['.ts', '.tsx', '.js', '.jsx'],
+ },
+ module: {
+ rules: [
+ {
+ test: /\.[jt]sx?$/,
+ exclude: /node_modules/,
+ use: {
+ loader: require.resolve('swc-loader'),
+ options: {
+ swcrc: false,
+ sourceMaps: isDevelopment,
+ jsc: {
+ parser: {
+ syntax: 'typescript',
+ tsx: true,
+ },
+ transform: {
+ react: {
+ runtime: 'automatic',
+ development: isDevelopment,
+ refresh: isWebpackServe && isDevelopment,
+ },
+ },
+ target: 'es2017',
+ },
+ },
+ },
+ },
+ {
+ test: /\.css$/i,
+ use: [
+ MiniCssExtractPlugin.loader,
+ {
+ loader: require.resolve('css-loader'),
+ options: {
+ importLoaders: 0,
+ },
+ },
+ ],
},
- antd: {
- singleton: true,
- requiredVersion: '^4.0.0',
+ {
+ test: /\.(png|svg|jpe?g|gif)$/i,
+ type: 'asset/resource',
},
- react: {
- singleton: true,
- requiredVersion: '^18.2.0',
+ ],
+ },
+ plugins: [
+ // const ModuleFederationPlugin = webpack.container.ModuleFederationPlugin;
+ new HtmlWebpackPlugin({
+ template: path.join(SRC_PATH, 'index.html'),
+ }),
+ new MiniCssExtractPlugin({
+ filename: isDevelopment ? '[name].css' : '[name].[contenthash].css',
+ chunkFilename: isDevelopment ? '[id].css' : '[id].[contenthash].css',
+ }),
+ isWebpackServe && isDevelopment && new ReactRefreshWebpackPlugin(),
+ new ModuleFederationPlugin({
+ name: 'runtime_host',
+ experiments: { asyncStartup: true },
+ remotes: {
+ // remote2: 'runtime_remote2@http://localhost:3007/remoteEntry.js',
+ remote1: 'runtime_remote1@http://127.0.0.1:3006/mf-manifest.json',
+ // remote1: `promise new Promise((resolve)=>{
+ // const raw = 'runtime_remote1@http://127.0.0.1:3006/remoteEntry.js'
+ // const [_, remoteUrlWithVersion] = raw.split('@')
+ // const script = document.createElement('script')
+ // script.src = remoteUrlWithVersion
+ // script.onload = () => {
+ // const proxy = {
+ // get: (request) => window.runtime_remote1.get(request),
+ // init: (arg) => {
+ // try {
+ // return window.runtime_remote1.init(arg)
+ // } catch(e) {
+ // console.log('runtime_remote1 container already initialized')
+ // }
+ // }
+ // }
+ // resolve(proxy)
+ // }
+ // document.head.appendChild(script);
+ // })`,
},
- 'react/': {
- singleton: true,
- requiredVersion: '^18.2.0',
+ // library: { type: 'var', name: 'runtime_remote' },
+ filename: 'remoteEntry.js',
+ exposes: {
+ './Button': './src/Button.tsx',
},
- 'react-dom': {
- singleton: true,
- requiredVersion: '^18.2.0',
+ dts: {
+ tsConfigPath: path.resolve(__dirname, 'tsconfig.app.json'),
},
- 'react-dom/': {
- singleton: true,
- requiredVersion: '^18.2.0',
+ shareStrategy: 'loaded-first',
+ shared: {
+ lodash: {
+ singleton: true,
+ requiredVersion: '^4.0.0',
+ },
+ antd: {
+ singleton: true,
+ requiredVersion: '^4.0.0',
+ },
+ react: {
+ singleton: true,
+ requiredVersion: '^18.2.0',
+ },
+ 'react/': {
+ singleton: true,
+ requiredVersion: '^18.2.0',
+ },
+ 'react-dom': {
+ singleton: true,
+ requiredVersion: '^18.2.0',
+ },
+ 'react-dom/': {
+ singleton: true,
+ requiredVersion: '^18.2.0',
+ },
},
+ }),
+ ].filter(Boolean),
+ optimization: {
+ runtimeChunk: false,
+ minimize: false,
+ moduleIds: 'named',
+ },
+ performance: {
+ hints: false,
+ },
+ experiments: {
+ // Temporary workaround - https://github.com/nrwl/nx/issues/16983
+ outputModule: false,
+ },
+ watchOptions: {
+ ignored: ['**/node_modules/**', '**/@mf-types/**', '**/dist/**'],
+ },
+ devServer: {
+ host: '127.0.0.1',
+ allowedHosts: 'all',
+ headers: {
+ 'Access-Control-Allow-Origin': '*',
},
- }),
- );
- if (!config.devServer) {
- config.devServer = {};
- }
- config.devServer.host = '127.0.0.1';
- config.plugins.forEach((p) => {
- if (p.constructor.name === 'ModuleFederationPlugin') {
- //Temporary workaround - https://github.com/nrwl/nx/issues/16983
- p._options.library = undefined;
- }
- });
-
- //Temporary workaround - https://github.com/nrwl/nx/issues/16983
- config.experiments = { outputModule: false };
-
- // Update the webpack config as needed here.
- // e.g. `config.plugins.push(new MyPlugin())`
- config.output = {
- ...config.output,
- scriptType: 'text/javascript',
- };
- config.optimization = {
- runtimeChunk: false,
- minimize: false,
- moduleIds: 'named',
+ port: 3005,
+ hot: isWebpackServe && isDevelopment,
+ historyApiFallback: true,
+ static: DIST_PATH,
+ devMiddleware: {
+ writeToDisk: true,
+ },
+ },
};
- // const mf = await withModuleFederation(defaultConfig);
- return config;
-});
+};
diff --git a/apps/runtime-demo/3006-runtime-remote/package.json b/apps/runtime-demo/3006-runtime-remote/package.json
index 405e720e45a..b4c7dd8b0e9 100644
--- a/apps/runtime-demo/3006-runtime-remote/package.json
+++ b/apps/runtime-demo/3006-runtime-remote/package.json
@@ -2,14 +2,23 @@
"name": "runtime-remote1",
"version": "0.0.1",
"private": true,
+ "scripts": {
+ "build": "webpack --config webpack.config.js --mode production",
+ "build:development": "webpack --config webpack.config.js --mode development",
+ "serve": "webpack serve --config webpack.config.js --mode development --host 127.0.0.1 --port 3006 --allowed-hosts all",
+ "serve:production": "webpack serve --config webpack.config.js --mode production --host 127.0.0.1 --port 3006 --allowed-hosts all --no-hot"
+ },
"devDependencies": {
"@module-federation/core": "workspace:*",
"@module-federation/enhanced": "workspace:*",
"@module-federation/typescript": "workspace:*",
"@pmmmwh/react-refresh-webpack-plugin": "0.5.15",
"react-refresh": "0.14.2",
+ "css-loader": "6.11.0",
+ "webpack-dev-server": "5.1.0",
"@types/react": "18.3.11",
- "@types/react-dom": "18.3.0"
+ "@types/react-dom": "18.3.0",
+ "mini-css-extract-plugin": "2.9.2"
},
"dependencies": {
"antd": "4.24.15",
diff --git a/apps/runtime-demo/3006-runtime-remote/project.json b/apps/runtime-demo/3006-runtime-remote/project.json
index 9ca44a376df..29666d4efe1 100644
--- a/apps/runtime-demo/3006-runtime-remote/project.json
+++ b/apps/runtime-demo/3006-runtime-remote/project.json
@@ -6,35 +6,21 @@
"tags": [],
"targets": {
"build": {
- "executor": "@nx/webpack:webpack",
- "outputs": ["{options.outputPath}"],
+ "executor": "nx:run-commands",
+ "outputs": ["{projectRoot}/dist"],
"defaultConfiguration": "production",
"options": {
- "compiler": "babel",
- "outputPath": "apps/runtime-demo/3006-runtime-remote/dist",
- "index": "apps/runtime-demo/3006-runtime-remote/src/index.html",
- "baseHref": "/",
- "main": "apps/runtime-demo/3006-runtime-remote/src/index.tsx",
- "tsConfig": "apps/runtime-demo/3006-runtime-remote/tsconfig.app.json",
- "styles": [],
- "scripts": [],
- "webpackConfig": "apps/runtime-demo/3006-runtime-remote/webpack.config.js",
- "babelUpwardRootMode": true
+ "command": "pnpm run build",
+ "cwd": "apps/runtime-demo/3006-runtime-remote"
},
"configurations": {
"development": {
- "extractLicenses": false,
- "optimization": false,
- "sourceMap": true,
- "vendorChunk": true
+ "command": "pnpm run build:development",
+ "cwd": "apps/runtime-demo/3006-runtime-remote"
},
"production": {
- "optimization": true,
- "outputHashing": "all",
- "sourceMap": false,
- "namedChunks": false,
- "extractLicenses": false,
- "vendorChunk": false
+ "command": "pnpm run build",
+ "cwd": "apps/runtime-demo/3006-runtime-remote"
}
},
"dependsOn": [
@@ -45,12 +31,11 @@
]
},
"serve": {
- "executor": "@nx/webpack:dev-server",
+ "executor": "nx:run-commands",
"defaultConfiguration": "production",
"options": {
- "buildTarget": "3006-runtime-remote:build",
- "hmr": true,
- "port": 3006
+ "command": "pnpm run serve:production",
+ "cwd": "apps/runtime-demo/3006-runtime-remote"
},
"dependsOn": [
{
@@ -60,11 +45,12 @@
],
"configurations": {
"development": {
- "buildTarget": "3006-runtime-remote:build:development"
+ "command": "pnpm run serve",
+ "cwd": "apps/runtime-demo/3006-runtime-remote"
},
"production": {
- "buildTarget": "3006-runtime-remote:build:production",
- "hmr": false
+ "command": "pnpm run serve:production",
+ "cwd": "apps/runtime-demo/3006-runtime-remote"
}
}
},
diff --git a/apps/runtime-demo/3006-runtime-remote/webpack.config.js b/apps/runtime-demo/3006-runtime-remote/webpack.config.js
index 29cde381cb3..6c3cc47b762 100644
--- a/apps/runtime-demo/3006-runtime-remote/webpack.config.js
+++ b/apps/runtime-demo/3006-runtime-remote/webpack.config.js
@@ -1,37 +1,93 @@
-// const { registerPluginTSTranspiler } = require('nx/src/utils/nx-plugin.js');
-// registerPluginTSTranspiler();
-
-const { composePlugins, withNx } = require('@nx/webpack');
-const { withReact } = require('@nx/react');
-
const path = require('path');
-// const { withModuleFederation } = require('@nx/react/module-federation');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const {
ModuleFederationPlugin,
} = require('@module-federation/enhanced/webpack');
-const packageJson = require('./package.json');
process.env.FEDERATION_DEBUG = true;
-module.exports = composePlugins(
- withNx(),
- withReact(),
- async (config, context) => {
- config.watchOptions = {
- ignored: ['**/node_modules/**', '**/@mf-types/**'],
- };
- // const ModuleFederationPlugin = webpack.container.ModuleFederationPlugin;
- config.watchOptions = {
- ignored: ['**/dist/**'],
- };
- if (!config.devServer) {
- config.devServer = {};
- }
- config.devServer.host = '127.0.0.1';
- config.plugins.push(
+const DIST_PATH = path.resolve(__dirname, 'dist');
+const SRC_PATH = path.resolve(__dirname, 'src');
+
+module.exports = (_env = {}, argv = {}) => {
+ const mode = argv.mode || process.env.NODE_ENV || 'development';
+ const isDevelopment = mode === 'development';
+ const isWebpackServe = Boolean(
+ argv.env?.WEBPACK_SERVE ?? process.env.WEBPACK_SERVE === 'true',
+ );
+
+ return {
+ mode,
+ devtool: isDevelopment ? 'source-map' : false,
+ entry: path.join(SRC_PATH, 'index.tsx'),
+ output: {
+ path: DIST_PATH,
+ filename: isDevelopment ? '[name].js' : '[name].[contenthash].js',
+ publicPath: 'http://127.0.0.1:3006/',
+ clean: true,
+ scriptType: 'text/javascript',
+ },
+ resolve: {
+ extensions: ['.ts', '.tsx', '.js', '.jsx'],
+ },
+ module: {
+ rules: [
+ {
+ test: /\.[jt]sx?$/,
+ exclude: /node_modules/,
+ use: {
+ loader: require.resolve('swc-loader'),
+ options: {
+ swcrc: false,
+ sourceMaps: isDevelopment,
+ jsc: {
+ parser: {
+ syntax: 'typescript',
+ tsx: true,
+ },
+ transform: {
+ react: {
+ runtime: 'automatic',
+ development: isDevelopment,
+ refresh: isWebpackServe && isDevelopment,
+ },
+ },
+ target: 'es2017',
+ },
+ },
+ },
+ },
+ {
+ test: /\.css$/i,
+ use: [
+ MiniCssExtractPlugin.loader,
+ {
+ loader: require.resolve('css-loader'),
+ options: {
+ importLoaders: 0,
+ },
+ },
+ ],
+ },
+ {
+ test: /\.(png|svg|jpe?g|gif)$/i,
+ type: 'asset/resource',
+ },
+ ],
+ },
+ plugins: [
+ new HtmlWebpackPlugin({
+ template: path.join(SRC_PATH, 'index.html'),
+ }),
+ new MiniCssExtractPlugin({
+ filename: isDevelopment ? '[name].css' : '[name].[contenthash].css',
+ chunkFilename: isDevelopment ? '[id].css' : '[id].[contenthash].css',
+ }),
+ isWebpackServe && isDevelopment && new ReactRefreshWebpackPlugin(),
new ModuleFederationPlugin({
name: 'runtime_remote1',
- // library: { type: 'var', name: 'runtime_remote' },
filename: 'remoteEntry.js',
exposes: {
'./useCustomRemoteHook': './src/components/useCustomRemoteHook',
@@ -72,63 +128,31 @@ module.exports = composePlugins(
tsConfigPath: path.resolve(__dirname, 'tsconfig.app.json'),
},
}),
- );
- // config.externals={
- // 'react':'React',
- // 'react-dom':'ReactDom'
- // }
- config.optimization.runtimeChunk = false;
- config.plugins.forEach((p) => {
- if (p.constructor.name === 'ModuleFederationPlugin') {
- //Temporary workaround - https://github.com/nrwl/nx/issues/16983
- p._options.library = undefined;
- }
- });
-
- //Temporary workaround - https://github.com/nrwl/nx/issues/16983
- config.experiments = { outputModule: false };
-
- // Update the webpack config as needed here.
- // e.g. `config.plugins.push(new MyPlugin())`
- config.output = {
- ...config.output,
- publicPath: 'http://127.0.0.1:3006/',
- scriptType: 'text/javascript',
- };
- config.optimization = {
- // ...config.optimization,
+ ].filter(Boolean),
+ optimization: {
runtimeChunk: false,
minimize: false,
moduleIds: 'named',
- };
- // const mf = await withModuleFederation(defaultConfig);
- return config;
- /** @type {import('webpack').Configuration} */
- // const parsedConfig = mf(config, context);
-
- // parsedConfig.plugins.forEach((p) => {
- // if (p.constructor.name === 'ModuleFederationPlugin') {
- // //Temporary workaround - https://github.com/nrwl/nx/issues/16983
- // p._options.library = undefined;
- // }
- // });
-
- // parsedConfig.devServer = {
- // ...(parsedConfig.devServer || {}),
- // //Needs to resolve static files from the dist folder (@mf-types)
- // static: path.resolve(__dirname, '../../dist/apps/runtime-demo/remote'),
- // };
-
- // //Temporary workaround - https://github.com/nrwl/nx/issues/16983
- // parsedConfig.experiments = { outputModule: false };
-
- // // Update the webpack config as needed here.
- // // e.g. `config.plugins.push(new MyPlugin())`
- // parsedConfig.output = {
- // ...parsedConfig.output,
- // scriptType: 'text/javascript',
- // };
-
- // return parsedConfig;
- },
-);
+ },
+ performance: {
+ hints: false,
+ },
+ experiments: {
+ outputModule: false,
+ },
+ watchOptions: {
+ ignored: ['**/node_modules/**', '**/@mf-types/**', '**/dist/**'],
+ },
+ devServer: {
+ host: '127.0.0.1',
+ allowedHosts: 'all',
+ headers: {
+ 'Access-Control-Allow-Origin': '*',
+ },
+ port: 3006,
+ hot: isWebpackServe && isDevelopment,
+ historyApiFallback: true,
+ static: DIST_PATH,
+ },
+ };
+};
diff --git a/apps/runtime-demo/3007-runtime-remote/package.json b/apps/runtime-demo/3007-runtime-remote/package.json
index 747452c27a7..91a75aed6fc 100644
--- a/apps/runtime-demo/3007-runtime-remote/package.json
+++ b/apps/runtime-demo/3007-runtime-remote/package.json
@@ -2,14 +2,23 @@
"name": "runtime-remote2",
"version": "0.0.0",
"private": true,
+ "scripts": {
+ "build": "webpack --config webpack.config.js --mode production",
+ "build:development": "webpack --config webpack.config.js --mode development",
+ "serve": "webpack serve --config webpack.config.js --mode development --host 127.0.0.1 --port 3007 --allowed-hosts all --no-live-reload",
+ "serve:production": "webpack serve --config webpack.config.js --mode production --host 127.0.0.1 --port 3007 --allowed-hosts all --no-live-reload --no-hot"
+ },
"devDependencies": {
"@module-federation/core": "workspace:*",
"@module-federation/enhanced": "workspace:*",
"@module-federation/typescript": "workspace:*",
"@pmmmwh/react-refresh-webpack-plugin": "0.5.15",
"react-refresh": "0.14.2",
+ "css-loader": "6.11.0",
"@types/react": "18.3.11",
- "@types/react-dom": "18.3.0"
+ "@types/react-dom": "18.3.0",
+ "mini-css-extract-plugin": "2.9.2",
+ "webpack-dev-server": "5.1.0"
},
"dependencies": {
"antd": "4.24.15",
diff --git a/apps/runtime-demo/3007-runtime-remote/project.json b/apps/runtime-demo/3007-runtime-remote/project.json
index 31a6da2c311..c7c2d7263c3 100644
--- a/apps/runtime-demo/3007-runtime-remote/project.json
+++ b/apps/runtime-demo/3007-runtime-remote/project.json
@@ -6,35 +6,21 @@
"tags": [],
"targets": {
"build": {
- "executor": "@nx/webpack:webpack",
- "outputs": ["{options.outputPath}"],
+ "executor": "nx:run-commands",
+ "outputs": ["{projectRoot}/dist"],
"defaultConfiguration": "production",
"options": {
- "compiler": "babel",
- "outputPath": "apps/runtime-demo/3007-runtime-remote/dist",
- "index": "apps/runtime-demo/3007-runtime-remote/src/index.html",
- "baseHref": "/",
- "main": "apps/runtime-demo/3007-runtime-remote/src/index.tsx",
- "tsConfig": "apps/runtime-demo/3007-runtime-remote/tsconfig.app.json",
- "styles": [],
- "scripts": [],
- "webpackConfig": "apps/runtime-demo/3007-runtime-remote/webpack.config.js",
- "babelUpwardRootMode": true
+ "command": "pnpm run build",
+ "cwd": "apps/runtime-demo/3007-runtime-remote"
},
"configurations": {
"development": {
- "extractLicenses": false,
- "optimization": false,
- "sourceMap": true,
- "vendorChunk": true
+ "command": "pnpm run build:development",
+ "cwd": "apps/runtime-demo/3007-runtime-remote"
},
"production": {
- "optimization": true,
- "outputHashing": "all",
- "sourceMap": false,
- "namedChunks": false,
- "extractLicenses": false,
- "vendorChunk": false
+ "command": "pnpm run build",
+ "cwd": "apps/runtime-demo/3007-runtime-remote"
}
},
"dependsOn": [
@@ -45,12 +31,11 @@
]
},
"serve": {
- "executor": "@nx/webpack:dev-server",
+ "executor": "nx:run-commands",
"defaultConfiguration": "production",
"options": {
- "buildTarget": "3007-runtime-remote:build",
- "hmr": true,
- "port": 3007
+ "command": "pnpm run serve:production",
+ "cwd": "apps/runtime-demo/3007-runtime-remote"
},
"dependsOn": [
{
@@ -60,11 +45,12 @@
],
"configurations": {
"development": {
- "buildTarget": "3007-runtime-remote:build:development"
+ "command": "pnpm run serve",
+ "cwd": "apps/runtime-demo/3007-runtime-remote"
},
"production": {
- "buildTarget": "3007-runtime-remote:build:production",
- "hmr": false
+ "command": "pnpm run serve:production",
+ "cwd": "apps/runtime-demo/3007-runtime-remote"
}
}
},
diff --git a/apps/runtime-demo/3007-runtime-remote/webpack.config.js b/apps/runtime-demo/3007-runtime-remote/webpack.config.js
index a1d1739431a..7efb638fbb5 100644
--- a/apps/runtime-demo/3007-runtime-remote/webpack.config.js
+++ b/apps/runtime-demo/3007-runtime-remote/webpack.config.js
@@ -1,26 +1,91 @@
-// const { registerPluginTSTranspiler } = require('nx/src/utils/nx-plugin.js');
-// registerPluginTSTranspiler();
-
-const { composePlugins, withNx } = require('@nx/webpack');
-const { withReact } = require('@nx/react');
-
const path = require('path');
-// const { withModuleFederation } = require('@nx/react/module-federation');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const {
ModuleFederationPlugin,
} = require('@module-federation/enhanced/webpack');
-module.exports = composePlugins(
- withNx(),
- withReact(),
- async (config, context) => {
- config.watchOptions = {
- ignored: ['**/node_modules/**', '**/@mf-types/**', '**/dist/**'],
- };
- config.plugins.push(
+const DIST_PATH = path.resolve(__dirname, 'dist');
+const SRC_PATH = path.resolve(__dirname, 'src');
+
+module.exports = (_env = {}, argv = {}) => {
+ const mode = argv.mode || process.env.NODE_ENV || 'development';
+ const isDevelopment = mode === 'development';
+ const isWebpackServe = Boolean(
+ argv.env?.WEBPACK_SERVE ?? process.env.WEBPACK_SERVE === 'true',
+ );
+
+ return {
+ mode,
+ devtool: isDevelopment ? 'source-map' : false,
+ entry: path.join(SRC_PATH, 'index.tsx'),
+ output: {
+ path: DIST_PATH,
+ filename: isDevelopment ? '[name].js' : '[name].[contenthash].js',
+ publicPath: 'http://127.0.0.1:3007/',
+ clean: true,
+ scriptType: 'text/javascript',
+ },
+ resolve: {
+ extensions: ['.ts', '.tsx', '.js', '.jsx'],
+ },
+ module: {
+ rules: [
+ {
+ test: /\.[jt]sx?$/,
+ exclude: /node_modules/,
+ use: {
+ loader: require.resolve('swc-loader'),
+ options: {
+ swcrc: false,
+ sourceMaps: isDevelopment,
+ jsc: {
+ parser: {
+ syntax: 'typescript',
+ tsx: true,
+ },
+ transform: {
+ react: {
+ runtime: 'automatic',
+ development: isDevelopment,
+ refresh: isWebpackServe && isDevelopment,
+ },
+ },
+ target: 'es2017',
+ },
+ },
+ },
+ },
+ {
+ test: /\.css$/i,
+ use: [
+ MiniCssExtractPlugin.loader,
+ {
+ loader: require.resolve('css-loader'),
+ options: {
+ importLoaders: 0,
+ },
+ },
+ ],
+ },
+ {
+ test: /\.(png|svg|jpe?g|gif)$/i,
+ type: 'asset/resource',
+ },
+ ],
+ },
+ plugins: [
+ new HtmlWebpackPlugin({
+ template: path.join(SRC_PATH, 'index.html'),
+ }),
+ new MiniCssExtractPlugin({
+ filename: isDevelopment ? '[name].css' : '[name].[contenthash].css',
+ chunkFilename: isDevelopment ? '[id].css' : '[id].[contenthash].css',
+ }),
+ isWebpackServe && isDevelopment && new ReactRefreshWebpackPlugin(),
new ModuleFederationPlugin({
name: 'runtime_remote2',
- // library: { type: 'var', name: 'runtime_remote' },
filename: 'remoteEntry.js',
exposes: {
'./ButtonOldAnt': './src/components/ButtonOldAnt',
@@ -56,35 +121,32 @@ module.exports = composePlugins(
disableLiveReload: true,
},
}),
- );
- if (!config.devServer) {
- config.devServer = {};
- }
- config.devServer.host = '127.0.0.1';
- config.optimization.runtimeChunk = false;
- config.plugins.forEach((p) => {
- if (p.constructor.name === 'ModuleFederationPlugin') {
- //Temporary workaround - https://github.com/nrwl/nx/issues/16983
- p._options.library = undefined;
- }
- });
-
- //Temporary workaround - https://github.com/nrwl/nx/issues/16983
- config.experiments = { outputModule: false };
-
- // Update the webpack config as needed here.
- // e.g. `config.plugins.push(new MyPlugin())`
- config.output = {
- ...config.output,
- publicPath: 'http://127.0.0.1:3007/',
- scriptType: 'text/javascript',
- };
- config.optimization = {
- ...config.optimization,
+ ].filter(Boolean),
+ optimization: {
runtimeChunk: false,
minimize: false,
- };
- // const mf = await withModuleFederation(defaultConfig);
- return config;
- },
-);
+ moduleIds: 'named',
+ },
+ performance: {
+ hints: false,
+ },
+ experiments: {
+ outputModule: false,
+ },
+ watchOptions: {
+ ignored: ['**/node_modules/**', '**/@mf-types/**', '**/dist/**'],
+ },
+ devServer: {
+ host: '127.0.0.1',
+ allowedHosts: 'all',
+ headers: {
+ 'Access-Control-Allow-Origin': '*',
+ },
+ port: 3007,
+ hot: isWebpackServe && isDevelopment,
+ liveReload: false,
+ historyApiFallback: true,
+ static: DIST_PATH,
+ },
+ };
+};
diff --git a/nx.json b/nx.json
index 4d62dbd7190..ec518e9c5b5 100644
--- a/nx.json
+++ b/nx.json
@@ -50,6 +50,14 @@
"cache": false
}
},
+ "tasksRunnerOptions": {
+ "default": {
+ "runner": "nx/tasks-runners/default",
+ "options": {
+ "useDaemonProcess": false
+ }
+ }
+ },
"namedInputs": {
"default": ["{projectRoot}/**/*", "sharedGlobals"],
"production": [
@@ -66,6 +74,9 @@
],
"sharedGlobals": ["{workspaceRoot}/babel.config.json"]
},
+ "tui": {
+ "enabled": false
+ },
"workspaceLayout": {
"appsDir": "apps",
"libsDir": "packages"
diff --git a/package.json b/package.json
index 5d25b332b76..9b0b5b822b6 100644
--- a/package.json
+++ b/package.json
@@ -25,7 +25,7 @@
"f": "nx format:write",
"enhanced:jest": "pnpm build && cd packages/enhanced && NODE_OPTIONS=--experimental-vm-modules npx jest test/ConfigTestCases.basictest.js test/unit",
"lint": "nx run-many --target=lint",
- "test": "nx run-many --target=test",
+ "test": "NX_TUI=false nx run-many --target=test --projects=tag:type:pkg --skip-nx-cache",
"build": "NX_TUI=false nx run-many --target=build --parallel=5 --projects=tag:type:pkg",
"build:pkg": "NX_TUI=false nx run-many --targets=build --projects=tag:type:pkg --skip-nx-cache",
"test:pkg": "NX_TUI=false nx run-many --targets=test --projects=tag:type:pkg --skip-nx-cache",
@@ -42,7 +42,7 @@
"app:next:build": "nx run-many --target=build --parallel=2 --configuration=production -p 3000-home,3001-shop,3002-checkout",
"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 --parallel=10 --configuration=development -p node-host,node-local-remote,node-remote,node-dynamic-remote-new-version,node-dynamic-remote",
- "app:runtime:dev": "nx run-many --target=serve -p 3005-runtime-host,3006-runtime-remote,3007-runtime-remote",
+ "app:runtime:dev": "NX_DAEMON=false nx run-many --target=serve -p 3005-runtime-host,3006-runtime-remote,3007-runtime-remote",
"app:manifest:dev": "NX_TUI=false nx run-many --target=serve --configuration=development --parallel=100 -p modernjs,manifest-webpack-host,3009-webpack-provider,3010-rspack-provider,3011-rspack-manifest-provider,3012-rspack-js-entry-provider",
"app:manifest:prod": "NX_TUI=false nx run-many --target=serve --configuration=production --parallel=100 -p modernjs,manifest-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",
diff --git a/packages/enhanced/jest.config.ts b/packages/enhanced/jest.config.ts
index e9f78245ad8..a2c1baada04 100644
--- a/packages/enhanced/jest.config.ts
+++ b/packages/enhanced/jest.config.ts
@@ -1,8 +1,7 @@
/* eslint-disable */
-import { readFileSync, rmdirSync, existsSync } from 'fs';
+import { readFileSync, rmSync, existsSync } from 'fs';
import path from 'path';
import os from 'os';
-const rimraf = require('rimraf');
// Reading the SWC compilation config and remove the "exclude"
// for the test files to be compiled by SWC
@@ -10,7 +9,10 @@ const { exclude: _, ...swcJestConfig } = JSON.parse(
readFileSync(`${__dirname}/.swcrc`, 'utf-8'),
);
-rimraf.sync(__dirname + '/test/js');
+const transpiledDir = path.join(__dirname, 'test/js');
+if (existsSync(transpiledDir)) {
+ rmSync(transpiledDir, { recursive: true, force: true });
+}
// disable .swcrc look-up by SWC core because we're passing in swcJestConfig ourselves.
// If we do not disable this, SWC Core will read .swcrc and won't transform our test files due to "exclude"
@@ -23,13 +25,11 @@ if (swcJestConfig.swcrc === undefined) {
// jest needs EsModule Interop to find the default exported setup/teardown functions
// swcJestConfig.module.noInterop = false;
-const testMatch = [];
-
-if (process.env['TEST_TYPE'] === 'unit') {
- testMatch.push('/test/unit/**/*.test.ts');
-} else {
- testMatch.push('/test/*.basictest.js');
-}
+const testMatch = [
+ '/test/*.basictest.js',
+ '/test/unit/**/*.test.ts',
+ '/test/compiler-unit/**/*.test.ts',
+];
export default {
displayName: 'enhanced',
diff --git a/packages/enhanced/jest.embed.ts b/packages/enhanced/jest.embed.ts
index 4e09a7c2e17..7dcf0129a28 100644
--- a/packages/enhanced/jest.embed.ts
+++ b/packages/enhanced/jest.embed.ts
@@ -1,8 +1,7 @@
/* eslint-disable */
-import { readFileSync, rmdirSync, existsSync } from 'fs';
+import { readFileSync, rmSync, existsSync } from 'fs';
import path from 'path';
import os from 'os';
-const rimraf = require('rimraf');
// Reading the SWC compilation config and remove the "exclude"
// for the test files to be compiled by SWC
@@ -10,7 +9,10 @@ const { exclude: _, ...swcJestConfig } = JSON.parse(
readFileSync(`${__dirname}/.swcrc`, 'utf-8'),
);
-rimraf.sync(__dirname + '/test/js');
+const transpiledDir = path.join(__dirname, 'test/js');
+if (existsSync(transpiledDir)) {
+ rmSync(transpiledDir, { recursive: true, force: true });
+}
// disable .swcrc look-up by SWC core because we're passing in swcJestConfig ourselves.
// If we do not disable this, SWC Core will read .swcrc and won't transform our test files due to "exclude"
diff --git a/packages/enhanced/project.json b/packages/enhanced/project.json
index 4730f4dbe0d..d3d73607c2d 100644
--- a/packages/enhanced/project.json
+++ b/packages/enhanced/project.json
@@ -49,11 +49,7 @@
"parallel": false,
"commands": [
{
- "command": "TEST_TYPE=basic node --expose-gc --max-old-space-size=24576 --experimental-vm-modules --trace-deprecation ./node_modules/jest-cli/bin/jest --logHeapUsage --config packages/enhanced/jest.config.ts --silent",
- "forwardAllArgs": false
- },
- {
- "command": "TEST_TYPE=unit node --expose-gc --max-old-space-size=24576 --experimental-vm-modules --trace-deprecation ./node_modules/jest-cli/bin/jest --logHeapUsage --config packages/enhanced/jest.config.ts --silent",
+ "command": "node --expose-gc --max-old-space-size=24576 --experimental-vm-modules --trace-deprecation ./node_modules/jest-cli/bin/jest --logHeapUsage --config packages/enhanced/jest.config.ts --silent",
"forwardAllArgs": false
}
]
diff --git a/packages/enhanced/src/lib/container/HoistContainerReferencesPlugin.ts b/packages/enhanced/src/lib/container/HoistContainerReferencesPlugin.ts
index 2e0d3e9fff1..d033a57451c 100644
--- a/packages/enhanced/src/lib/container/HoistContainerReferencesPlugin.ts
+++ b/packages/enhanced/src/lib/container/HoistContainerReferencesPlugin.ts
@@ -10,6 +10,7 @@ import { normalizeWebpackPath } from '@module-federation/sdk/normalize-webpack-p
import FederationModulesPlugin from './runtime/FederationModulesPlugin';
import ContainerEntryDependency from './ContainerEntryDependency';
import FederationRuntimeDependency from './runtime/FederationRuntimeDependency';
+import FederationWorkerRuntimeDependency from './runtime/FederationWorkerRuntimeDependency';
import RemoteToExternalDependency from './RemoteToExternalDependency';
import FallbackDependency from './FallbackDependency';
@@ -27,10 +28,10 @@ class HoistContainerReferences implements WebpackPluginInstance {
compiler.hooks.thisCompilation.tap(
PLUGIN_NAME,
(compilation: Compilation) => {
- const logger = compilation.getLogger(PLUGIN_NAME);
const hooks = FederationModulesPlugin.getCompilationHooks(compilation);
const containerEntryDependencies = new Set();
const federationRuntimeDependencies = new Set();
+ const workerRuntimeDependencies = new Set();
const remoteDependencies = new Set();
hooks.addContainerEntryDependency.tap(
@@ -42,7 +43,11 @@ class HoistContainerReferences implements WebpackPluginInstance {
hooks.addFederationRuntimeDependency.tap(
'HoistContainerReferences',
(dep: FederationRuntimeDependency) => {
- federationRuntimeDependencies.add(dep);
+ if (dep instanceof FederationWorkerRuntimeDependency) {
+ workerRuntimeDependencies.add(dep);
+ } else {
+ federationRuntimeDependencies.add(dep);
+ }
},
);
hooks.addRemoteDependency.tap(
@@ -61,12 +66,16 @@ class HoistContainerReferences implements WebpackPluginInstance {
},
(chunks: Iterable) => {
const runtimeChunks = this.getRuntimeChunks(compilation);
+ const debugLogger = compilation.getLogger(`${PLUGIN_NAME}:debug`);
+ debugLogger.warn(
+ `container=${containerEntryDependencies.size} runtime=${federationRuntimeDependencies.size} worker=${workerRuntimeDependencies.size} remote=${remoteDependencies.size}`,
+ );
this.hoistModulesInChunks(
compilation,
runtimeChunks,
- logger,
containerEntryDependencies,
federationRuntimeDependencies,
+ workerRuntimeDependencies,
remoteDependencies,
);
},
@@ -79,13 +88,109 @@ class HoistContainerReferences implements WebpackPluginInstance {
private hoistModulesInChunks(
compilation: Compilation,
runtimeChunks: Set,
- logger: ReturnType,
containerEntryDependencies: Set,
federationRuntimeDependencies: Set,
+ workerRuntimeDependencies: Set,
remoteDependencies: Set,
): void {
const { chunkGraph, moduleGraph } = compilation;
const allModulesToHoist = new Set();
+ const runtimeDependencyChunks = new Set();
+ const debugLogger = compilation.getLogger(`${PLUGIN_NAME}:hoist`);
+
+ const runtimeChunkByName = new Map();
+ for (const chunk of runtimeChunks) {
+ const registerName = (name?: string) => {
+ if (name && !runtimeChunkByName.has(name)) {
+ runtimeChunkByName.set(name, chunk);
+ }
+ };
+
+ registerName(chunk.name);
+
+ const runtime = (chunk as unknown as { runtime?: Iterable })
+ .runtime;
+ if (runtime && typeof (runtime as any)[Symbol.iterator] === 'function') {
+ for (const name of runtime as Iterable) {
+ registerName(name);
+ }
+ }
+ }
+
+ const addRuntimeChunksByName = (
+ targetChunks: Set,
+ runtimeKey: string,
+ ) => {
+ const direct = runtimeChunkByName.get(runtimeKey);
+ if (direct) {
+ targetChunks.add(direct);
+ }
+
+ if (runtimeKey.includes('\n')) {
+ for (const fragment of runtimeKey.split(/\n/)) {
+ const chunk = runtimeChunkByName.get(fragment);
+ if (chunk) {
+ targetChunks.add(chunk);
+ }
+ }
+ }
+
+ const namedChunk = compilation.namedChunks.get(runtimeKey);
+ if (namedChunk) {
+ targetChunks.add(namedChunk);
+ }
+ };
+
+ const collectTargetChunks = (
+ targetModule: Module,
+ dependency?: Dependency,
+ ): Set => {
+ const targetChunks = new Set();
+
+ for (const chunk of chunkGraph.getModuleChunks(targetModule)) {
+ targetChunks.add(chunk);
+ }
+
+ const moduleRuntimes = chunkGraph.getModuleRuntimes(targetModule);
+ for (const runtimeSpec of moduleRuntimes) {
+ compilation.compiler.webpack.util.runtime.forEachRuntime(
+ runtimeSpec,
+ (runtimeKey) => {
+ if (!runtimeKey) {
+ return;
+ }
+ addRuntimeChunksByName(targetChunks, runtimeKey);
+ },
+ );
+ }
+
+ if (dependency) {
+ const parentBlock = moduleGraph.getParentBlock(dependency);
+ if (parentBlock instanceof AsyncDependenciesBlock) {
+ const chunkGroup = chunkGraph.getBlockChunkGroup(parentBlock);
+ if (chunkGroup) {
+ for (const chunk of chunkGroup.chunks) {
+ targetChunks.add(chunk);
+ }
+ }
+ }
+ }
+
+ return targetChunks;
+ };
+
+ const connectModulesToChunks = (
+ targetChunks: Iterable,
+ modules: Iterable,
+ ) => {
+ for (const chunk of targetChunks) {
+ for (const module of modules) {
+ if (!chunkGraph.isModuleInChunk(module, chunk)) {
+ chunkGraph.connectChunkAndModule(chunk, module);
+ }
+ }
+ }
+ };
// Process container entry dependencies (needed for nextjs-mf exposed modules)
for (const dep of containerEntryDependencies) {
@@ -97,27 +202,54 @@ class HoistContainerReferences implements WebpackPluginInstance {
'initial',
);
referencedModules.forEach((m: Module) => allModulesToHoist.add(m));
- const moduleRuntimes = chunkGraph.getModuleRuntimes(containerEntryModule);
- const runtimes = new Set();
- for (const runtimeSpec of moduleRuntimes) {
- compilation.compiler.webpack.util.runtime.forEachRuntime(
- runtimeSpec,
- (runtimeKey) => {
- if (runtimeKey) {
- runtimes.add(runtimeKey);
- }
- },
- );
+ const targetChunks = collectTargetChunks(containerEntryModule, dep);
+ if (targetChunks.size === 0) {
+ continue;
}
- for (const runtime of runtimes) {
- const runtimeChunk = compilation.namedChunks.get(runtime);
- if (!runtimeChunk) continue;
- for (const module of referencedModules) {
- if (!chunkGraph.isModuleInChunk(module, runtimeChunk)) {
- chunkGraph.connectChunkAndModule(runtimeChunk, module);
- }
- }
+
+ debugLogger.warn(
+ `container entry modules -> [${Array.from(referencedModules)
+ .map((module: Module) =>
+ typeof (module as any)?.identifier === 'function'
+ ? (module as any).identifier()
+ : (module?.toString?.() ?? '[unknown]'),
+ )
+ .join(', ')}]`,
+ );
+
+ connectModulesToChunks(targetChunks, referencedModules);
+ }
+
+ // Worker federation runtime dependencies may originate from async blocks
+ // (e.g. web workers). Hoist the full dependency graph to keep worker
+ // runtimes self-contained.
+ for (const dep of workerRuntimeDependencies) {
+ const runtimeModule = moduleGraph.getModule(dep);
+ if (!runtimeModule) continue;
+
+ const referencedModules = getAllReferencedModules(
+ compilation,
+ runtimeModule,
+ 'all',
+ );
+ referencedModules.forEach((module) => allModulesToHoist.add(module));
+
+ const targetChunks = collectTargetChunks(runtimeModule, dep);
+ if (targetChunks.size === 0) {
+ continue;
+ }
+
+ for (const chunk of targetChunks) {
+ runtimeDependencyChunks.add(chunk);
}
+
+ debugLogger.warn(
+ `worker runtime modules -> [${Array.from(targetChunks)
+ .map((chunk) => chunk.name ?? String(chunk.id ?? ''))
+ .join(', ')}]`,
+ );
+
+ connectModulesToChunks(targetChunks, referencedModules);
}
// Federation Runtime Dependencies: use 'initial' (not 'all')
@@ -130,27 +262,22 @@ class HoistContainerReferences implements WebpackPluginInstance {
'initial',
);
referencedModules.forEach((m: Module) => allModulesToHoist.add(m));
- const moduleRuntimes = chunkGraph.getModuleRuntimes(runtimeModule);
- const runtimes = new Set();
- for (const runtimeSpec of moduleRuntimes) {
- compilation.compiler.webpack.util.runtime.forEachRuntime(
- runtimeSpec,
- (runtimeKey) => {
- if (runtimeKey) {
- runtimes.add(runtimeKey);
- }
- },
- );
+ const targetChunks = collectTargetChunks(runtimeModule, dep);
+ if (targetChunks.size === 0) {
+ continue;
}
- for (const runtime of runtimes) {
- const runtimeChunk = compilation.namedChunks.get(runtime);
- if (!runtimeChunk) continue;
- for (const module of referencedModules) {
- if (!chunkGraph.isModuleInChunk(module, runtimeChunk)) {
- chunkGraph.connectChunkAndModule(runtimeChunk, module);
- }
- }
+
+ for (const chunk of targetChunks) {
+ runtimeDependencyChunks.add(chunk);
}
+
+ debugLogger.warn(
+ `runtime modules -> [${Array.from(targetChunks)
+ .map((chunk) => chunk.name ?? String(chunk.id ?? ''))
+ .join(', ')}]`,
+ );
+
+ connectModulesToChunks(targetChunks, referencedModules);
}
// Process remote dependencies
@@ -163,25 +290,30 @@ class HoistContainerReferences implements WebpackPluginInstance {
'initial',
);
referencedRemoteModules.forEach((m: Module) => allModulesToHoist.add(m));
- const remoteModuleRuntimes = chunkGraph.getModuleRuntimes(remoteModule);
- const remoteRuntimes = new Set();
- for (const runtimeSpec of remoteModuleRuntimes) {
- compilation.compiler.webpack.util.runtime.forEachRuntime(
- runtimeSpec,
- (runtimeKey) => {
- if (runtimeKey) remoteRuntimes.add(runtimeKey);
- },
- );
+ const targetChunks = collectTargetChunks(remoteModule, remoteDep);
+ for (const chunk of runtimeDependencyChunks) {
+ targetChunks.add(chunk);
}
- for (const runtime of remoteRuntimes) {
- const runtimeChunk = compilation.namedChunks.get(runtime);
- if (!runtimeChunk) continue;
- for (const module of referencedRemoteModules) {
- if (!chunkGraph.isModuleInChunk(module, runtimeChunk)) {
- chunkGraph.connectChunkAndModule(runtimeChunk, module);
- }
- }
+ if (targetChunks.size === 0) {
+ continue;
}
+
+ debugLogger.warn(
+ `remote modules -> [${Array.from(targetChunks)
+ .map((chunk) => chunk.name ?? String(chunk.id ?? ''))
+ .join(', ')}]`,
+ );
+ debugLogger.warn(
+ `remote module ids -> [${Array.from(referencedRemoteModules)
+ .map((module: Module) =>
+ typeof (module as any)?.identifier === 'function'
+ ? (module as any).identifier()
+ : (module?.toString?.() ?? '[unknown]'),
+ )
+ .join(', ')}]`,
+ );
+
+ connectModulesToChunks(targetChunks, referencedRemoteModules);
}
this.cleanUpChunks(compilation, allModulesToHoist);
diff --git a/packages/enhanced/src/lib/container/RemoteRuntimeModule.ts b/packages/enhanced/src/lib/container/RemoteRuntimeModule.ts
index d6a3b88b947..f97142721d0 100644
--- a/packages/enhanced/src/lib/container/RemoteRuntimeModule.ts
+++ b/packages/enhanced/src/lib/container/RemoteRuntimeModule.ts
@@ -136,6 +136,8 @@ class RemoteRuntimeModule extends RuntimeModule {
);
return Template.asString([
+ `${federationGlobal}.bundlerRuntimeOptions = ${federationGlobal}.bundlerRuntimeOptions || {};`,
+ `${federationGlobal}.bundlerRuntimeOptions.remotes = ${federationGlobal}.bundlerRuntimeOptions.remotes || {};`,
`var chunkMapping = ${JSON.stringify(
chunkToRemotesMapping,
null,
@@ -158,7 +160,9 @@ class RemoteRuntimeModule extends RuntimeModule {
`${
RuntimeGlobals.ensureChunkHandlers
}.remotes = ${runtimeTemplate.basicFunction('chunkId, promises', [
- `${federationGlobal}.bundlerRuntime.remotes({idToRemoteMap,chunkMapping, idToExternalAndNameMapping, chunkId, promises, webpackRequire:${RuntimeGlobals.require}});`,
+ `if(${federationGlobal}.bundlerRuntime && ${federationGlobal}.bundlerRuntime.remotes){`,
+ `\t${federationGlobal}.bundlerRuntime.remotes({idToRemoteMap,chunkMapping, idToExternalAndNameMapping, chunkId, promises, webpackRequire:${RuntimeGlobals.require}});`,
+ `}`,
])}`,
]);
}
diff --git a/packages/enhanced/src/lib/container/runtime/ChildCompilationRuntimePlugin.ts b/packages/enhanced/src/lib/container/runtime/ChildCompilationRuntimePlugin.ts
deleted file mode 100644
index af5bf1c471c..00000000000
--- a/packages/enhanced/src/lib/container/runtime/ChildCompilationRuntimePlugin.ts
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Zackary Jackson @ScriptedAlchemy
-*/
-
-// This stores the previous child compilation based solution
-// it is not currently used
-
-import { normalizeWebpackPath } from '@module-federation/sdk/normalize-webpack-path';
-import type { Compiler, Compilation, Chunk, Module, ChunkGraph } from 'webpack';
-import { getFederationGlobalScope } from './utils';
-import fs from 'fs';
-import path from 'path';
-import { ConcatSource } from 'webpack-sources';
-import { transformSync } from '@swc/core';
-import { infrastructureLogger as logger } from '@module-federation/sdk';
-
-const { RuntimeModule, Template, RuntimeGlobals } = require(
- normalizeWebpackPath('webpack'),
-) as typeof import('webpack');
-
-const onceForCompilationMap = new WeakMap();
-const federationGlobal = getFederationGlobalScope(RuntimeGlobals);
-
-class RuntimeModuleChunkPlugin {
- apply(compiler: Compiler): void {
- compiler.hooks.thisCompilation.tap(
- 'ModuleChunkFormatPlugin',
- (compilation: Compilation) => {
- compilation.hooks.optimizeModuleIds.tap(
- 'ModuleChunkFormatPlugin',
- (modules: Iterable) => {
- for (const module of modules) {
- const moduleId = compilation.chunkGraph.getModuleId(module);
- if (typeof moduleId === 'string') {
- compilation.chunkGraph.setModuleId(
- module,
- `(embed)${moduleId}`,
- );
- } else {
- compilation.chunkGraph.setModuleId(module, `1000${moduleId}`);
- }
- }
- },
- );
-
- const hooks =
- compiler.webpack.javascript.JavascriptModulesPlugin.getCompilationHooks(
- compilation,
- );
-
- hooks.renderChunk.tap(
- 'ModuleChunkFormatPlugin',
- (
- modules: any,
- renderContext: { chunk: Chunk; chunkGraph: ChunkGraph },
- ) => {
- const { chunk, chunkGraph } = renderContext;
-
- const source = new ConcatSource();
- source.add('var federation = ');
- source.add(modules);
- source.add('\n');
- const entries = Array.from(
- chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk),
- );
- for (let i = 0; i < entries.length; i++) {
- const [module, _entrypoint] = entries[i];
- const final = i + 1 === entries.length;
- const moduleId = chunkGraph.getModuleId(module);
- source.add('\n');
- if (final) {
- source.add('for (var mod in federation) {\n');
- source.add(
- `${RuntimeGlobals.moduleFactories}[mod] = federation[mod];\n`,
- );
- source.add('}\n');
- source.add('federation = ');
- }
- source.add(
- `${RuntimeGlobals.require}(${typeof moduleId === 'number' ? moduleId : JSON.stringify(moduleId)});\n`,
- );
- }
- return source;
- },
- );
- },
- );
- }
-}
-
-class CustomRuntimePlugin {
- private entryModule?: string | number;
- private bundlerRuntimePath: string;
- private tempDir: string;
-
- constructor(path: string, tempDir: string) {
- this.bundlerRuntimePath = path.replace('cjs', 'esm');
- this.tempDir = tempDir;
- }
-
- apply(compiler: Compiler): void {
- compiler.hooks.make.tapAsync(
- 'CustomRuntimePlugin',
- (compilation: Compilation, callback: (err?: Error) => void) => {
- if (onceForCompilationMap.has(compilation)) return callback();
- onceForCompilationMap.set(compilation, null);
- const target = compilation.options.target || 'default';
- const outputPath = path.join(
- this.tempDir,
- `${target}-custom-runtime-bundle.js`,
- );
-
- if (fs.existsSync(outputPath)) {
- const source = fs.readFileSync(outputPath, 'utf-8');
- onceForCompilationMap.set(compiler, source);
- return callback();
- }
-
- if (onceForCompilationMap.has(compiler)) return callback();
- onceForCompilationMap.set(compiler, null);
-
- const childCompiler = compilation.createChildCompiler(
- 'EmbedFederationRuntimeCompiler',
- {
- filename: '[name].js',
- library: {
- type: 'var',
- name: 'federation',
- export: 'default',
- },
- },
- [
- new compiler.webpack.EntryPlugin(
- compiler.context,
- this.bundlerRuntimePath,
- {
- name: 'custom-runtime-bundle',
- runtime: 'other',
- },
- ),
- new compiler.webpack.library.EnableLibraryPlugin('var'),
- new RuntimeModuleChunkPlugin(),
- ],
- );
-
- childCompiler.context = compiler.context;
- childCompiler.options.devtool = undefined;
- childCompiler.options.optimization.splitChunks = false;
- childCompiler.options.optimization.removeAvailableModules = true;
- logger.log('Creating child compiler for', this.bundlerRuntimePath);
-
- childCompiler.hooks.thisCompilation.tap(
- this.constructor.name,
- (childCompilation) => {
- childCompilation.hooks.processAssets.tap(
- this.constructor.name,
- () => {
- const source =
- childCompilation.assets['custom-runtime-bundle.js'] &&
- (childCompilation.assets[
- 'custom-runtime-bundle.js'
- ].source() as string);
-
- const entry = childCompilation.entrypoints.get(
- 'custom-runtime-bundle',
- );
- const entryChunk = entry?.getEntrypointChunk();
-
- if (entryChunk) {
- const entryModule = Array.from(
- childCompilation.chunkGraph.getChunkEntryModulesIterable(
- entryChunk,
- ),
- )[0];
- this.entryModule =
- childCompilation.chunkGraph.getModuleId(entryModule);
- }
-
- onceForCompilationMap.set(compilation, source);
- onceForCompilationMap.set(compiler, source);
- fs.writeFileSync(outputPath, source);
- logger.log('got compilation asset');
- childCompilation.chunks.forEach((chunk) => {
- chunk.files.forEach((file) => {
- childCompilation.deleteAsset(file);
- });
- });
- },
- );
- },
- );
- childCompiler.runAsChild(
- (
- err?: Error | null,
- entries?: Chunk[],
- childCompilation?: Compilation,
- ) => {
- if (err) {
- return callback(err);
- }
-
- if (!childCompilation) {
- logger.warn(
- 'Embed Federation Runtime: Child compilation is undefined',
- );
- return callback();
- }
-
- if (childCompilation.errors.length) {
- return callback(childCompilation.errors[0]);
- }
-
- logger.log('Code built successfully');
-
- callback();
- },
- );
- },
- );
-
- compiler.hooks.thisCompilation.tap(
- 'CustomRuntimePlugin',
- (compilation: Compilation) => {
- const handler = (chunk: Chunk, runtimeRequirements: Set) => {
- if (chunk.id === 'build time chunk') {
- return;
- }
- if (runtimeRequirements.has('embeddedFederationRuntime')) return;
- if (!runtimeRequirements.has(federationGlobal)) {
- return;
- }
- const bundledCode = onceForCompilationMap.get(compilation);
- if (!bundledCode) return;
- runtimeRequirements.add('embeddedFederationRuntime');
- const runtimeModule = new CustomRuntimeModule(
- bundledCode,
- this.entryModule,
- );
-
- compilation.addRuntimeModule(chunk, runtimeModule);
- logger.log(`Custom runtime module added to chunk: ${chunk.name}`);
- };
- compilation.hooks.runtimeRequirementInTree
- .for(federationGlobal)
- .tap('CustomRuntimePlugin', handler);
- },
- );
- }
-}
-
-class CustomRuntimeModule extends RuntimeModule {
- private entryModuleId: string | number | undefined;
-
- constructor(
- private readonly entryPath: string,
- entryModuleId: string | number | undefined,
- ) {
- super('CustomRuntimeModule', RuntimeModule.STAGE_BASIC);
- this.entryPath = entryPath;
- this.entryModuleId = entryModuleId;
- }
-
- override identifier() {
- return 'webpack/runtime/embed/federation';
- }
-
- override generate(): string {
- const runtimeModule = this.entryPath;
- const { code: transformedCode } = transformSync(
- this.entryPath.replace('var federation;', 'var federation = '),
- {
- jsc: {
- parser: {
- syntax: 'ecmascript',
- jsx: false,
- },
- target: 'es2022',
- minify: {
- compress: {
- unused: true,
- dead_code: true,
- drop_debugger: true,
- },
- mangle: false,
- format: {
- comments: false,
- },
- },
- },
- },
- );
-
- return Template.asString([
- runtimeModule,
- transformedCode,
- `for (var mod in federation) {
- ${Template.indent(`${RuntimeGlobals.moduleFactories}[mod] = federation[mod];`)}
- }`,
- `federation = ${RuntimeGlobals.require}(${JSON.stringify(this.entryModuleId)});`,
- `federation = ${RuntimeGlobals.compatGetDefaultExport}(federation)();`,
- `var prevFederation = ${federationGlobal}`,
- `${federationGlobal} = {}`,
- `for (var key in federation) {`,
- Template.indent(`${federationGlobal}[key] = federation[key];`),
- `}`,
- `for (var key in prevFederation) {`,
- Template.indent(`${federationGlobal}[key] = prevFederation[key];`),
- `}`,
- 'federation = undefined;',
- ]);
- }
-}
-
-export { CustomRuntimePlugin, CustomRuntimeModule, RuntimeModuleChunkPlugin };
diff --git a/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimeModule.ts b/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimeModule.ts
index da5a72b6451..7332bb83139 100644
--- a/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimeModule.ts
+++ b/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimeModule.ts
@@ -22,8 +22,11 @@ class EmbedFederationRuntimeModule extends RuntimeModule {
containerEntrySet: Set<
ContainerEntryDependency | FederationRuntimeDependency
>,
+ stage?: number,
) {
- super('embed federation', RuntimeModule.STAGE_ATTACH);
+ // Use provided stage or default to STAGE_ATTACH (10)
+ // Worker chunks use STAGE_NORMAL - 2 (-2) to run before RemoteRuntimeModule
+ super('embed federation', stage ?? 10);
this.containerEntrySet = containerEntrySet;
this._cachedGeneratedCode = undefined;
}
@@ -61,24 +64,33 @@ class EmbedFederationRuntimeModule extends RuntimeModule {
runtimeRequirements: new Set(),
});
- const result = Template.asString([
- `var prevStartup = ${RuntimeGlobals.startup};`,
- `var hasRun = false;`,
- `${RuntimeGlobals.startup} = ${compilation.runtimeTemplate.basicFunction(
- '',
- [
- `if (!hasRun) {`,
- ` hasRun = true;`,
- ` ${initRuntimeModuleGetter};`,
- `}`,
- `if (typeof prevStartup === 'function') {`,
- ` return prevStartup();`,
- `} else {`,
- ` console.warn('[Module Federation] prevStartup is not a function, skipping startup execution');`,
- `}`,
- ],
- )};`,
- ]);
+ // Generate different code based on stage
+ // Stage 10 (STAGE_ATTACH for JSONP): Load federation entry INSIDE startup hook BEFORE prevStartup()
+ // Stage 5 (STAGE_BASIC for workers): Load federation entry IMMEDIATELY to initialize bundlerRuntime early
+ const result =
+ this.stage === 5
+ ? // Worker pattern: Load federation entry immediately
+ Template.asString([`${initRuntimeModuleGetter};`])
+ : // JSONP/default pattern: Load inside startup hook
+ Template.asString([
+ `var prevStartup = ${RuntimeGlobals.startup};`,
+ `var hasRun = false;`,
+ `${RuntimeGlobals.startup} = ${compilation.runtimeTemplate.basicFunction(
+ '',
+ [
+ `if (!hasRun) {`,
+ ` hasRun = true;`,
+ ` ${initRuntimeModuleGetter};`,
+ `}`,
+ `if (typeof prevStartup === 'function') {`,
+ ` return prevStartup();`,
+ `} else {`,
+ ` console.warn('[Module Federation] prevStartup is not a function, skipping startup execution');`,
+ `}`,
+ ],
+ )};`,
+ ]);
+
this._cachedGeneratedCode = result;
return result;
}
diff --git a/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts b/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts
index c1fe93ee994..4bc708d02e5 100644
--- a/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts
+++ b/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts
@@ -147,8 +147,38 @@ class EmbedFederationRuntimePlugin {
// Mark as embedded and add the runtime module.
runtimeRequirements.add('embeddedFederationRuntime');
+
+ // Determine stage based on chunk loading type
+ // Following webpack's pattern from JsonpChunkLoadingPlugin and ImportScriptsChunkLoadingPlugin
+ const options = chunk.getEntryOptions();
+ const globalChunkLoading = compilation.outputOptions.chunkLoading;
+ const chunkLoading =
+ options && options.chunkLoading !== undefined
+ ? options.chunkLoading
+ : globalChunkLoading;
+
+ const { RuntimeModule } = compiler.webpack;
+ let stage: number;
+
+ if (chunkLoading === 'jsonp') {
+ // For JSONP chunks (Next.js): use STAGE_ATTACH (10)
+ // This loads federation entry inside startup hook BEFORE prevStartup()
+ stage = RuntimeModule.STAGE_ATTACH;
+ } else if (chunkLoading === 'import-scripts') {
+ // For worker chunks: use STAGE_BASIC (5)
+ // Must run AFTER FederationRuntimeModule (stage -1) creates __webpack_require__.federation
+ // And AFTER RemoteRuntimeModule (stage 0) defines functions that use bundlerRuntime
+ // But BEFORE ImportScriptsChunkLoadingRuntimeModule (stage 10) calls those functions
+ stage = RuntimeModule.STAGE_BASIC;
+ } else {
+ // For other chunk types (async-node, require, etc): use STAGE_ATTACH (10)
+ // Same as JSONP - load before prevStartup()
+ stage = RuntimeModule.STAGE_ATTACH;
+ }
+
const runtimeModule = new EmbedFederationRuntimeModule(
containerEntrySet,
+ stage,
);
compilation.addRuntimeModule(chunk, runtimeModule);
};
diff --git a/packages/enhanced/src/lib/container/runtime/FederationRuntimePlugin.ts b/packages/enhanced/src/lib/container/runtime/FederationRuntimePlugin.ts
index 81b82c64623..3c28d8de956 100644
--- a/packages/enhanced/src/lib/container/runtime/FederationRuntimePlugin.ts
+++ b/packages/enhanced/src/lib/container/runtime/FederationRuntimePlugin.ts
@@ -24,6 +24,7 @@ import EmbedFederationRuntimePlugin from './EmbedFederationRuntimePlugin';
import FederationModulesPlugin from './FederationModulesPlugin';
import HoistContainerReferences from '../HoistContainerReferencesPlugin';
import FederationRuntimeDependency from './FederationRuntimeDependency';
+import FederationWorkerRuntimeDependency from './FederationWorkerRuntimeDependency';
const ModuleDependency = require(
normalizeWebpackPath('webpack/lib/dependencies/ModuleDependency'),
@@ -35,6 +36,9 @@ const { RuntimeGlobals, Template } = require(
const { mkdirpSync } = require(
normalizeWebpackPath('webpack/lib/util/fs'),
) as typeof import('webpack/lib/util/fs');
+const WorkerDependency = require(
+ normalizeWebpackPath('webpack/lib/dependencies/WorkerDependency'),
+);
const RuntimeToolsPath = require.resolve(
'@module-federation/runtime-tools/dist/index.esm.js',
@@ -249,6 +253,81 @@ class FederationRuntimePlugin {
FederationRuntimeDependency,
new ModuleDependency.Template(),
);
+ compilation.dependencyFactories.set(
+ FederationWorkerRuntimeDependency,
+ normalModuleFactory,
+ );
+ compilation.dependencyTemplates.set(
+ FederationWorkerRuntimeDependency,
+ FederationWorkerRuntimeDependency.createTemplate(),
+ );
+
+ const federationHooks =
+ FederationModulesPlugin.getCompilationHooks(compilation);
+ const processedWorkerBlocks = new WeakSet