diff --git a/.changeset/chatty-hornets-attend.md b/.changeset/chatty-hornets-attend.md new file mode 100644 index 0000000000..86e34dfeee --- /dev/null +++ b/.changeset/chatty-hornets-attend.md @@ -0,0 +1,6 @@ +--- +'@module-federation/nextjs-mf': minor +'@module-federation/enhanced': minor +--- + +enable chunk hoisting when runtime is embedded diff --git a/.changeset/kind-nails-roll.md b/.changeset/kind-nails-roll.md new file mode 100644 index 0000000000..61bec70777 --- /dev/null +++ b/.changeset/kind-nails-roll.md @@ -0,0 +1,5 @@ +--- +'@module-federation/enhanced': minor +--- + +Embedded runtime and async entry startup diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 2888d723ef..19226d4369 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -2,7 +2,7 @@ name: Build Affected Packages on: pull_request: - branches: [main] + branches: [main, '**'] push: branches: [main] @@ -62,9 +62,14 @@ jobs: uses: ./.github/workflows/e2e-node.yml secrets: inherit - e2e-next: + e2e-next-dev: + needs: checkout-install + uses: ./.github/workflows/e2e-next-dev.yml + secrets: inherit + + e2e-next-prod: needs: checkout-install - uses: ./.github/workflows/e2e-next.yml + uses: ./.github/workflows/e2e-next-prod.yml secrets: inherit e2e-modern-ssr: diff --git a/.github/workflows/e2e-manifest.yml b/.github/workflows/e2e-manifest.yml index 92313ed498..44a4bc6330 100644 --- a/.github/workflows/e2e-manifest.yml +++ b/.github/workflows/e2e-manifest.yml @@ -43,4 +43,4 @@ jobs: - name: E2E Test for Manifest Demo if: steps.check-ci.outcome == 'success' - run: pnpm run app:manifest:dev & echo "done" && npx wait-on tcp:3009 && npx wait-on tcp:3012 && npx nx run-many --target=e2e --projects=manifest-webpack-host --parallel=1 && lsof -ti tcp:3013,3009,3010,3011,3012 | xargs kill + run: pnpm run app:manifest:dev & echo "done" && sleep 15 && npx wait-on tcp:3009 && npx wait-on tcp:3012 && npx nx run-many --target=e2e --projects=manifest-webpack-host --parallel=1 && lsof -ti tcp:3013,3009,3010,3011,3012 | xargs kill diff --git a/.github/workflows/e2e-next-dev.yml b/.github/workflows/e2e-next-dev.yml new file mode 100644 index 0000000000..ccce8bc377 --- /dev/null +++ b/.github/workflows/e2e-next-dev.yml @@ -0,0 +1,52 @@ +name: E2E Test for Next.js Dev + +on: + workflow_call: + +jobs: + e2e-next-dev: + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Install Pnpm + run: corepack enable + + - name: Setup Node.js 18 + uses: actions/setup-node@v3 + with: + node-version: '18' + cache: 'pnpm' + + - name: Set Nx SHA + uses: nrwl/nx-set-shas@v3 + + - name: Set SKIP_DEVTOOLS_POSTINSTALL environment variable + run: echo "SKIP_DEVTOOLS_POSTINSTALL=true" >> $GITHUB_ENV + + - name: Install Dependencies + run: pnpm install + + - name: Install Cypress + run: npx cypress install + + - name: Run Build for All + run: npx nx run-many --targets=build --projects=tag:type:pkg + + - name: Run condition check script + id: check-ci + run: node tools/scripts/ci-is-affected.mjs --appName=3000-home + + - name: E2E Test for Next.js Dev + if: steps.check-ci.outcome == 'success' + run: | + pnpm run app:next:dev > /dev/null 2>&1 & + sleep 1 && + npx wait-on tcp:3001 && + npx wait-on tcp:3002 && + npx wait-on tcp:3000 && + npx nx run-many --target=test:e2e --projects=3000-home,3001-shop,3002-checkout --parallel=1 && + lsof -ti tcp:3000,3001,3002 | xargs kill diff --git a/.github/workflows/e2e-next.yml b/.github/workflows/e2e-next-prod.yml similarity index 74% rename from .github/workflows/e2e-next.yml rename to .github/workflows/e2e-next-prod.yml index ccd9f10a3f..b43e74b9df 100644 --- a/.github/workflows/e2e-next.yml +++ b/.github/workflows/e2e-next-prod.yml @@ -1,11 +1,10 @@ -# .github/workflows/e2e-next-dev.yml -name: E2E Test for Next.js Dev +name: E2E Test for Next.js Prod on: workflow_call: jobs: - e2e-next: + e2e-next-prod: runs-on: ubuntu-latest steps: - name: Checkout Repository @@ -52,14 +51,3 @@ jobs: npx wait-on tcp:3000 && npx nx run-many --target=test:e2e --projects=3000-home,3001-shop,3002-checkout --parallel=1 && lsof -ti tcp:3000,3001,3002 | xargs kill - - - name: E2E Test for Next.js Dev - if: steps.check-ci.outcome == 'success' - run: | - pnpm run app:next:dev > /dev/null 2>&1 & - sleep 1 && - npx wait-on tcp:3001 && - npx wait-on tcp:3002 && - npx wait-on tcp:3000 && - npx nx run-many --target=test:e2e --projects=3000-home,3001-shop,3002-checkout --parallel=1 && - lsof -ti tcp:3000,3001,3002 | xargs kill diff --git a/.github/workflows/e2e-router.yml b/.github/workflows/e2e-router.yml index 669089e074..5e7a5e2a28 100644 --- a/.github/workflows/e2e-router.yml +++ b/.github/workflows/e2e-router.yml @@ -43,4 +43,4 @@ jobs: - name: E2E Test for Runtime Demo if: steps.check-ci.outcome == 'success' - run: npx kill-port --port 2000,2001,2002,2003,2004,2200,2100 && pnpm run app:router:dev & echo "done" && sleep 20 && npx nx run-many --target=test:e2e --projects=router-host-2000 --parallel=1 && lsof -ti tcp:2000,2001,2002,2003,2004,2200,2100 | xargs kill + run: npx kill-port --port 2000,2001,2002,2003,2004,2200,2100 && pnpm run app:router:dev & echo "done" && sleep 30 && npx nx run-many --target=test:e2e --projects=router-host-2000 --parallel=1 && lsof -ti tcp:2000,2001,2002,2003,2004,2200,2100 | xargs kill diff --git a/.gitignore b/.gitignore index 03dd21f086..516129cc94 100644 --- a/.gitignore +++ b/.gitignore @@ -5,8 +5,6 @@ /tmp /out-tsc /build -**/@mf-types -**/@mf-types/** /docs /.nx # dependencies @@ -51,6 +49,10 @@ migrations.json packages/**/dist apps/**/dist +**/@mf-types +**/@mf-types/** +**/cypress/downloads + # test cases !packages/enhanced/test/configCases/**/**/node_modules packages/enhanced/test/js @@ -58,10 +60,6 @@ packages/enhanced/test/js **/.mf **/.mf/** -/apps/manifest-demo/**/@mf-types/ -/apps/manifest-demo/webpack-host/@mf-types/ -/apps/manifest-demo/3008-webpack-host/@mf-types/ - # dts test cases **/dist-test **/dist-test/** diff --git a/apps/3000-home/cypress/e2e/app.cy.ts b/apps/3000-home/cypress/e2e/app.cy.ts index 92008de438..5dda8279f2 100644 --- a/apps/3000-home/cypress/e2e/app.cy.ts +++ b/apps/3000-home/cypress/e2e/app.cy.ts @@ -22,10 +22,12 @@ describe('3000-home/', () => { it('should display welcome message', () => { getH1().contains('This is SPA combined'); }); - it('Api endpoint works', () => { - const urls = ['/api/test']; - urls.forEach((url) => { - cy.request(url); // This makes a GET request, not a full page visit + }); + + describe('API endpoint should return json', () => { + it('Query Endpoint', () => { + cy.request('/api/test').then((response) => { + expect(response.headers['content-type']).to.include('application/json'); }); }); }); diff --git a/apps/3000-home/package.json b/apps/3000-home/package.json index 76b488a73b..8527afffc3 100644 --- a/apps/3000-home/package.json +++ b/apps/3000-home/package.json @@ -24,7 +24,8 @@ }, "devDependencies": { "@module-federation/nextjs-mf": "workspace:*", - "@module-federation/utilities": "workspace:*" + "@module-federation/utilities": "workspace:*", + "@module-federation/runtime": "workspace:*" }, "scripts": { "start": "next start", diff --git a/apps/3000-home/pages/_app.tsx b/apps/3000-home/pages/_app.tsx index cb9044faa3..533db175ab 100644 --- a/apps/3000-home/pages/_app.tsx +++ b/apps/3000-home/pages/_app.tsx @@ -1,5 +1,7 @@ import * as React from 'react'; import { useState } from 'react'; +import { init } from '@module-federation/runtime'; +console.log('logging init', typeof init); import App from 'next/app'; import { Layout, version, ConfigProvider } from 'antd'; import { StyleProvider } from '@ant-design/cssinjs'; diff --git a/apps/manifest-demo/webpack-host/src/index.ts b/apps/manifest-demo/webpack-host/src/index.ts deleted file mode 100644 index b93c7a0268..0000000000 --- a/apps/manifest-demo/webpack-host/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -import('./bootstrap'); diff --git a/apps/manifest-demo/webpack-host/src/index.tsx b/apps/manifest-demo/webpack-host/src/index.tsx new file mode 100644 index 0000000000..29dcae9d86 --- /dev/null +++ b/apps/manifest-demo/webpack-host/src/index.tsx @@ -0,0 +1,51 @@ +// import('./bootstrap'); +import React, { StrictMode, lazy } from 'react'; +import { init } from '@module-federation/runtime'; +import * as ReactDOM from 'react-dom/client'; +import { + createBrowserRouter, + createRoutesFromElements, + Route, + RouterProvider, +} from 'react-router-dom'; +import App from './App'; +import Root from './Root'; +import customPlugin from './runtimePlugin'; + +const router = createBrowserRouter([ + { + path: '/', + element: , + children: [ + { + path: 'basic', + element: , + }, + { + path: 'preload', + Component: lazy(() => import('./Preload')), + }, + ], + }, +]); + +init({ + name: 'manifest_host', + remotes: [ + { + name: 'rspack_provider', + alias: 'dynamic-remote', + entry: 'http://localhost:3010/mf-manifest.json', + }, + ], + plugins: [customPlugin()], +}); + +const root = ReactDOM.createRoot( + document.getElementById('root') as HTMLElement, +); +root.render( + + + , +); diff --git a/apps/manifest-demo/webpack-host/webpack.config.js b/apps/manifest-demo/webpack-host/webpack.config.js index d4d83b5fa6..161ed08ff5 100644 --- a/apps/manifest-demo/webpack-host/webpack.config.js +++ b/apps/manifest-demo/webpack-host/webpack.config.js @@ -33,6 +33,7 @@ module.exports = composePlugins(withNx(), withReact(), (config, context) => { 'react-dom': {}, 'react-dom/': {}, }, + embedRuntime: true, runtimePlugins: [path.join(__dirname, './runtimePlugin.ts')], }), ); @@ -46,6 +47,7 @@ module.exports = composePlugins(withNx(), withReact(), (config, context) => { if (config.devServer) { config.devServer.client.overlay = false; } + config.entry = './src/index.tsx'; //Temporary workaround - https://github.com/nrwl/nx/issues/16983 config.experiments = { outputModule: false }; @@ -54,8 +56,10 @@ module.exports = composePlugins(withNx(), withReact(), (config, context) => { scriptType: 'text/javascript', }; config.optimization = { - runtimeChunk: false, + runtimeChunk: 'single', minimize: false, + moduleIds: 'named', + chunkIds: 'named', }; config.output.publicPath = 'http://localhost:3013/'; return config; diff --git a/apps/modernjs-ssr/dynamic-nested-remote/@mf-types/dynamic_remote/Image.d.ts b/apps/modernjs-ssr/dynamic-nested-remote/@mf-types/dynamic_remote/Image.d.ts index a63e90edef..d4a0c1006a 100644 --- a/apps/modernjs-ssr/dynamic-nested-remote/@mf-types/dynamic_remote/Image.d.ts +++ b/apps/modernjs-ssr/dynamic-nested-remote/@mf-types/dynamic_remote/Image.d.ts @@ -1,2 +1,2 @@ -export * from './compiled-types/Image'; -export { default } from './compiled-types/Image'; \ No newline at end of file +export * from './compiled-types/src/components/Image'; +export { default } from './compiled-types/src/components/Image'; \ No newline at end of file diff --git a/apps/modernjs-ssr/dynamic-nested-remote/@mf-types/dynamic_remote/compiled-types/Image.d.ts b/apps/modernjs-ssr/dynamic-nested-remote/@mf-types/dynamic_remote/compiled-types/Image.d.ts deleted file mode 100644 index e51e05fa50..0000000000 --- a/apps/modernjs-ssr/dynamic-nested-remote/@mf-types/dynamic_remote/compiled-types/Image.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare const _default: ({ text }: { - text: string; -}) => JSX.Element; -export default _default; diff --git a/apps/modernjs-ssr/host/cypress/downloads/downloads.html b/apps/modernjs-ssr/host/cypress/downloads/downloads.html new file mode 100644 index 0000000000..18d3040b46 --- /dev/null +++ b/apps/modernjs-ssr/host/cypress/downloads/downloads.html @@ -0,0 +1 @@ +Cr24 diff --git a/apps/runtime-demo/3005-runtime-host/src/Remote1.tsx b/apps/runtime-demo/3005-runtime-host/src/Remote1.tsx index 5939167be4..ed82d67d21 100644 --- a/apps/runtime-demo/3005-runtime-host/src/Remote1.tsx +++ b/apps/runtime-demo/3005-runtime-host/src/Remote1.tsx @@ -1,22 +1,15 @@ import React, { Suspense, lazy } from 'react'; +import { loadRemote } from '@module-federation/runtime'; -export const WebpackSvgRemote = function () { - const WebpackSvgRemote = lazy(() => import('remote1/WebpackSvg')); - return ( - - - - ); -}; +export const WebpackSvgRemote = React.lazy(async () => { + const WebpackSvgRemote = await loadRemote('remote1/WebpackSvg'); + return WebpackSvgRemote; +}); -export const WebpackPngRemote = function () { - const WebpackPngRemote = lazy(() => import('remote1/WebpackPng')); - return ( - - - - ); -}; +export const WebpackPngRemote = React.lazy(async () => { + const WebpackPngRemote = await loadRemote('remote1/WebpackPng'); + return WebpackPngRemote; +}); function Remote1() { return ( diff --git a/apps/runtime-demo/3005-runtime-host/src/Root.tsx b/apps/runtime-demo/3005-runtime-host/src/Root.tsx index ede13ed7b3..5de10fb721 100644 --- a/apps/runtime-demo/3005-runtime-host/src/Root.tsx +++ b/apps/runtime-demo/3005-runtime-host/src/Root.tsx @@ -1,5 +1,5 @@ import React, { Suspense, lazy } from 'react'; -import TestRemoteHook from './test-remote-hook'; +// import TestRemoteHook from './test-remote-hook'; import LocalBtn from './components/ButtonOldAnt'; import WebpackPng from './webpack.png'; import WebpackSvg from './webpack.svg'; @@ -31,9 +31,7 @@ const Root = () => ( - - - + {/* */} ✅ diff --git a/apps/runtime-demo/3005-runtime-host/src/bootstrap.tsx b/apps/runtime-demo/3005-runtime-host/src/bootstrap.tsx index 3f87d871a1..39d18db75b 100644 --- a/apps/runtime-demo/3005-runtime-host/src/bootstrap.tsx +++ b/apps/runtime-demo/3005-runtime-host/src/bootstrap.tsx @@ -14,6 +14,11 @@ init({ alias: 'dynamic-remote', entry: 'http://127.0.0.1:3007/mf-manifest.json', }, + { + name: 'runtime_remote1', + alias: 'remote1', + entry: 'http://127.0.0.1:3006/mf-manifest.json', + }, ], }); diff --git a/apps/runtime-demo/3005-runtime-host/src/index.ts b/apps/runtime-demo/3005-runtime-host/src/index.ts index ca109b9585..51ffb285cf 100644 --- a/apps/runtime-demo/3005-runtime-host/src/index.ts +++ b/apps/runtime-demo/3005-runtime-host/src/index.ts @@ -6,4 +6,4 @@ import customPlugin from './runtimePlugin'; registerGlobalPlugins([customPlugin()]); -import('./bootstrap'); +require('./bootstrap'); diff --git a/apps/runtime-demo/3005-runtime-host/src/test-remote-hook.tsx b/apps/runtime-demo/3005-runtime-host/src/test-remote-hook.tsx index 47dd0a35bf..30d47f50ec 100644 --- a/apps/runtime-demo/3005-runtime-host/src/test-remote-hook.tsx +++ b/apps/runtime-demo/3005-runtime-host/src/test-remote-hook.tsx @@ -1,38 +1,38 @@ -import useCustomRemoteHook from 'remote1/useCustomRemoteHook'; +// import useCustomRemoteHook from 'remote1/useCustomRemoteHook'; -// function RemoteHookText() { -// // @ts-ignore ignore -// const RemoteText = React.lazy(async () => { -// //@ts-ignore -// const useCustomRemoteHook = await loadRemote('app2/useCustomRemoteHook') as ()=>string; -// console.log(111,useCustomRemoteHook) -// const text = useCustomRemoteHook.default(); -// console.log(23424,text) -// return text; -// }); -// return ( -// -//
{RemoteText}
-//
-// ); -// } +// // function RemoteHookText() { +// // // @ts-ignore ignore +// // const RemoteText = React.lazy(async () => { +// // //@ts-ignore +// // const useCustomRemoteHook = await loadRemote('app2/useCustomRemoteHook') as ()=>string; +// // console.log(111,useCustomRemoteHook) +// // const text = useCustomRemoteHook.default(); +// // console.log(23424,text) +// // return text; +// // }); +// // return ( +// // +// //
{RemoteText}
+// //
+// // ); +// // } -const TestRemoteHook = () => { - const text = useCustomRemoteHook(); +// const TestRemoteHook = () => { +// const text = useCustomRemoteHook(); - return ( - <> -
- Page with custom remote hook. You must see text in red box below: -
- {text} -
-
- - ); -}; +// return ( +// <> +//
+// Page with custom remote hook. You must see text in red box below: +//
+// {text} +//
+//
+// +// ); +// }; -export default TestRemoteHook; +// export default TestRemoteHook; diff --git a/apps/runtime-demo/3005-runtime-host/webpack.config.js b/apps/runtime-demo/3005-runtime-host/webpack.config.js index 795fa1994d..e99788d656 100644 --- a/apps/runtime-demo/3005-runtime-host/webpack.config.js +++ b/apps/runtime-demo/3005-runtime-host/webpack.config.js @@ -15,30 +15,31 @@ module.exports = composePlugins(withNx(), withReact(), (config, context) => { config.plugins.push( new ModuleFederationPlugin({ name: 'runtime_host', - 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); - // })`, - }, + embedRuntime: 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: { diff --git a/apps/runtime-demo/3006-runtime-remote/project.json b/apps/runtime-demo/3006-runtime-remote/project.json index ef779f8c35..4bb502ab58 100644 --- a/apps/runtime-demo/3006-runtime-remote/project.json +++ b/apps/runtime-demo/3006-runtime-remote/project.json @@ -13,7 +13,7 @@ "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.ts", + "main": "apps/runtime-demo/3006-runtime-remote/src/index.tsx", "tsConfig": "apps/runtime-demo/3006-runtime-remote/tsconfig.app.json", "styles": [], "scripts": [], diff --git a/apps/runtime-demo/3006-runtime-remote/src/index.ts b/apps/runtime-demo/3006-runtime-remote/src/index.ts deleted file mode 100644 index b93c7a0268..0000000000 --- a/apps/runtime-demo/3006-runtime-remote/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -import('./bootstrap'); diff --git a/apps/runtime-demo/3006-runtime-remote/src/bootstrap.tsx b/apps/runtime-demo/3006-runtime-remote/src/index.tsx similarity index 100% rename from apps/runtime-demo/3006-runtime-remote/src/bootstrap.tsx rename to apps/runtime-demo/3006-runtime-remote/src/index.tsx diff --git a/apps/runtime-demo/3006-runtime-remote/webpack.config.js b/apps/runtime-demo/3006-runtime-remote/webpack.config.js index ff856b0437..3409c37fea 100644 --- a/apps/runtime-demo/3006-runtime-remote/webpack.config.js +++ b/apps/runtime-demo/3006-runtime-remote/webpack.config.js @@ -93,7 +93,7 @@ module.exports = composePlugins( scriptType: 'text/javascript', }; config.optimization = { - ...config.optimization, + // ...config.optimization, runtimeChunk: false, minimize: false, }; diff --git a/apps/runtime-demo/3007-runtime-remote/project.json b/apps/runtime-demo/3007-runtime-remote/project.json index 27e80e17ae..48d0a429db 100644 --- a/apps/runtime-demo/3007-runtime-remote/project.json +++ b/apps/runtime-demo/3007-runtime-remote/project.json @@ -13,7 +13,7 @@ "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.ts", + "main": "apps/runtime-demo/3007-runtime-remote/src/index.tsx", "tsConfig": "apps/runtime-demo/3007-runtime-remote/tsconfig.app.json", "styles": [], "scripts": [], diff --git a/apps/runtime-demo/3007-runtime-remote/src/index.ts b/apps/runtime-demo/3007-runtime-remote/src/index.ts deleted file mode 100644 index b93c7a0268..0000000000 --- a/apps/runtime-demo/3007-runtime-remote/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -import('./bootstrap'); diff --git a/apps/runtime-demo/3007-runtime-remote/src/bootstrap.tsx b/apps/runtime-demo/3007-runtime-remote/src/index.tsx similarity index 100% rename from apps/runtime-demo/3007-runtime-remote/src/bootstrap.tsx rename to apps/runtime-demo/3007-runtime-remote/src/index.tsx diff --git a/package.json b/package.json index 07499f7b67..c1e74fedc6 100644 --- a/package.json +++ b/package.json @@ -7,10 +7,10 @@ }, "packageManager": "pnpm@8.11.0", "private": true, - "description": "Module Federation helper for NextJS", + "description": "Module Federation v2", "main": "src/index.js", "types": "src/index.d.ts", - "repository": "https://github.com/module-federation/nextjs-mf", + "repository": "https://github.com/module-federation/core", "author": "Zack Jackson ", "contributors": [ "Pavel Chertorogov, nodkz (www.ps.kz)", @@ -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 --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:router:dev": "kill-port 2000 2200 2100 2001 2002 2003 && nx run-many --target=serve --parallel=10 --projects='router-*'", + "app:router:dev": "nx run-many --target=serve --parallel=10 --projects='router-*'", "app:manifest:dev": "kill-port 3009 3010 3011 3012 3013 && nx run-many --target=serve --parallel=100 -p 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", "app:modern:dev": "nx run-many --target=serve --parallel=10 --configuration=development -p modernjs-ssr-dynamic-nested-remote,modernjs-ssr-dynamic-remote,modernjs-ssr-dynamic-remote-new-version,modernjs-ssr-host,modernjs-ssr-nested-remote,modernjs-ssr-remote,modernjs-ssr-remote-new-version", diff --git a/packages/enhanced/package.json b/packages/enhanced/package.json index 51c492f3f0..e1505f2c10 100644 --- a/packages/enhanced/package.json +++ b/packages/enhanced/package.json @@ -68,7 +68,8 @@ }, "devDependencies": { "@module-federation/webpack-bundler-runtime": "workspace:*", - "@types/btoa": "^1.2.5" + "@types/btoa": "^1.2.5", + "@types/enhanced-resolve": "^5.0.0" }, "dependencies": { "@module-federation/sdk": "workspace:*", diff --git a/packages/enhanced/src/lib/container/HoistContainerReferencesPlugin.ts b/packages/enhanced/src/lib/container/HoistContainerReferencesPlugin.ts index 4ee2aa7834..a3f9178d01 100644 --- a/packages/enhanced/src/lib/container/HoistContainerReferencesPlugin.ts +++ b/packages/enhanced/src/lib/container/HoistContainerReferencesPlugin.ts @@ -3,30 +3,114 @@ import type { Compilation, Chunk, WebpackPluginInstance, + Module, + NormalModule as NormalModuleType, } from 'webpack'; +import { normalizeWebpackPath } from '@module-federation/sdk/normalize-webpack-path'; +import type { RuntimeSpec } from 'webpack/lib/util/runtime'; +import type ExportsInfo from 'webpack/lib/ExportsInfo'; import ContainerEntryModule from './ContainerEntryModule'; +const { NormalModule, AsyncDependenciesBlock } = require( + normalizeWebpackPath('webpack'), +) as typeof import('webpack'); +const ConcatenatedModule = require( + normalizeWebpackPath('webpack/lib/optimize/ConcatenatedModule'), +) as typeof import('webpack/lib/optimize/ConcatenatedModule'); + +const PLUGIN_NAME = 'HoistContainerReferences'; + /** * This class is used to hoist container references in the code. * @constructor */ export class HoistContainerReferences implements WebpackPluginInstance { - private containerName?: string | undefined; + private readonly containerName: string; + private readonly entryFilePath?: string; + private readonly bundlerRuntimeDep?: string; + private readonly explanation: string; - constructor(name?: string | undefined) { - this.containerName = name; + constructor( + name?: string, + entryFilePath?: string, + bundlerRuntimeDep?: string, + ) { + this.containerName = name || 'no known chunk name'; + this.entryFilePath = entryFilePath; + this.bundlerRuntimeDep = bundlerRuntimeDep; + this.explanation = + 'Bundler runtime path module is required for proper functioning'; } apply(compiler: Compiler): void { compiler.hooks.thisCompilation.tap( - 'HoistContainerReferences', + PLUGIN_NAME, (compilation: Compilation) => { - compilation.hooks.afterOptimizeChunks.tap( - 'HoistContainerReferences', + const logger = compilation.getLogger(PLUGIN_NAME); + const { chunkGraph, moduleGraph } = compilation; + + // Hook into the optimizeChunks phase + compilation.hooks.optimizeChunks.tap( + { + name: PLUGIN_NAME, + // advanced stage is where SplitChunksPlugin runs. + stage: 11, // advanced + 1 + }, (chunks: Iterable) => { - for (const chunk of chunks) { - if (this.chunkContainsContainerEntryModule(chunk, compilation)) { - this.hoistModulesInChunk(chunk, compilation); + const runtimeChunks = this.getRuntimeChunks(compilation); + this.hoistModulesInChunks( + compilation, + runtimeChunks, + chunks, + logger, + ); + }, + ); + + // Hook into the optimizeDependencies phase + compilation.hooks.optimizeDependencies.tap( + { + name: PLUGIN_NAME, + // basic optimization stage - it runs first + stage: -10, + }, + (modules: Iterable) => { + if (this.entryFilePath) { + let runtime: RuntimeSpec | undefined; + for (const [name, { options }] of compilation.entries) { + runtime = compiler.webpack.util.runtime.mergeRuntimeOwned( + runtime, + compiler.webpack.util.runtime.getEntryRuntime( + compilation, + name, + options, + ), + ); + } + for (const module of modules) { + if ( + module instanceof NormalModule && + module.resource === this.bundlerRuntimeDep + ) { + const allRefs = this.getAllReferencedModules( + compilation, + module, + 'initial', + ); + for (const module of allRefs) { + const exportsInfo: ExportsInfo = + moduleGraph.getExportsInfo(module); + // Since i dont use the import federation var, tree shake will eliminate it. + // also because currently the runtime is copied into all runtime chunks + // some might not have the runtime import in the tree to begin with + exportsInfo.setUsedInUnknownWay(runtime); + moduleGraph.addExtraReason(module, this.explanation); + if (module.factoryMeta === undefined) { + module.factoryMeta = {}; + } + module.factoryMeta.sideEffectFree = false; + } + } } } }, @@ -35,46 +119,172 @@ export class HoistContainerReferences implements WebpackPluginInstance { ); } - private chunkContainsContainerEntryModule( - chunk: Chunk, + // Helper method to collect all referenced modules recursively + private getAllReferencedModules( compilation: Compilation, - ): boolean { - for (const module of compilation.chunkGraph.getChunkModulesIterable( - chunk, - )) { - if (module instanceof ContainerEntryModule) { - return true; + module: Module, + type?: 'all' | 'initial', + ): Set { + const collectedModules = new Set([module]); + const stack = [module]; + + while (stack.length > 0) { + const currentModule = stack.pop(); + if (!currentModule) continue; + const mgm = compilation.moduleGraph._getModuleGraphModule(currentModule); + if (mgm && mgm.outgoingConnections) { + for (const connection of mgm.outgoingConnections) { + if (type === 'initial') { + const parentBlock = compilation.moduleGraph.getParentBlock( + connection.dependency, + ); + if (parentBlock instanceof AsyncDependenciesBlock) { + continue; + } + } + if (connection.module && !collectedModules.has(connection.module)) { + collectedModules.add(connection.module); + stack.push(connection.module); + } + } + } + } + + return collectedModules; + } + + // Helper method to find a specific module in a chunk + private findModule( + compilation: Compilation, + chunk: Chunk, + entryFilePath: string, + ): Module | null { + const { chunkGraph } = compilation; + let module: Module | null = null; + for (const mod of chunkGraph.getChunkEntryModulesIterable(chunk)) { + if (mod instanceof NormalModule && mod.resource === entryFilePath) { + module = mod; + break; + } + + if (mod instanceof ConcatenatedModule) { + for (const m of mod.modules) { + if (m instanceof NormalModule && m.resource === entryFilePath) { + module = mod; + break; + } + } } } - return false; + return module; } - private hoistModulesInChunk(chunk: Chunk, compilation: Compilation): void { - const chunkGraph = compilation.chunkGraph; - const runtimeChunks = this.getRuntimeChunks(chunk, compilation); - - for (const module of chunkGraph.getChunkModulesIterable(chunk)) { - if ( - !chunk.hasRuntime() && - (!this.containerName || - (chunk.name !== this.containerName && - chunk.name !== this.containerName + '_partial')) - ) { - chunkGraph.disconnectChunkAndModule(chunk, module); + // Method to hoist modules in chunks + private hoistModulesInChunks( + compilation: Compilation, + runtimeChunks: Set, + chunks: Iterable, + logger: ReturnType, + ): void { + const { chunkGraph, moduleGraph } = compilation; + // when runtimeChunk: single is set - ContainerPlugin will create a "partial" chunk we can use to + // move modules into the runtime chunk + const partialChunk = this.containerName + ? compilation.namedChunks.get(this.containerName) + : undefined; + let runtimeModule; + if (!partialChunk) { + for (const chunk of chunks) { + if ( + chunkGraph.getNumberOfEntryModules(chunk) > 0 && + this.entryFilePath + ) { + runtimeModule = this.findModule( + compilation, + chunk, + this.entryFilePath, + ); + + if (runtimeModule) break; + } } + } else { + const entryModules = + chunkGraph.getChunkEntryModulesIterable(partialChunk); + runtimeModule = entryModules + ? Array.from(entryModules).find( + (module) => module instanceof ContainerEntryModule, + ) + : undefined; + } - for (const runtimeChunk of runtimeChunks) { - if (chunkGraph.isModuleInChunk(module, runtimeChunk)) continue; - chunkGraph.connectChunkAndModule(runtimeChunk, module); + if (!runtimeModule) { + logger.error( + '[Federation HoistContainerReferences] unable to find runtime module:', + this.entryFilePath, + ); + return; + } + + const allReferencedModules = this.getAllReferencedModules( + compilation, + runtimeModule, + 'initial', + ); + + // If single runtime chunk, copy the remoteEntry into the runtime chunk to allow for embed container + // this will not work well if there multiple runtime chunks from entrypoints (like next) + // need better solution to multi runtime chunk hoisting + if (partialChunk) { + for (const module of chunkGraph.getChunkModulesIterable(partialChunk)) { + allReferencedModules.add(module); } } + + for (const chunk of runtimeChunks) { + for (const module of allReferencedModules) { + if (!chunkGraph.isModuleInChunk(module, chunk)) { + chunkGraph.connectChunkAndModule(chunk, module); + } + } + } + + // Set used exports for the runtime module + this.cleanUpChunks(compilation, allReferencedModules); } - private getRuntimeChunks(chunk: Chunk, compilation: Compilation): Chunk[] { - const runtimeChunks = []; - for (const c of compilation.chunks) { - if (c.hasRuntime() && c !== chunk) { - runtimeChunks.push(c); + // Method to clean up chunks by disconnecting unused modules + private cleanUpChunks(compilation: Compilation, modules: Set): void { + const { chunkGraph } = compilation; + for (const module of modules) { + for (const chunk of chunkGraph.getModuleChunks(module)) { + if (!chunk.hasRuntime()) { + chunkGraph.disconnectChunkAndModule(chunk, module); + if ( + chunkGraph.getNumberOfChunkModules(chunk) === 0 && + chunkGraph.getNumberOfEntryModules(chunk) === 0 + ) { + chunkGraph.disconnectChunk(chunk); + compilation.chunks.delete(chunk); + if (chunk.name) { + compilation.namedChunks.delete(chunk.name); + } + } + } + } + } + modules.clear(); + } + + // Helper method to get runtime chunks from the compilation + private getRuntimeChunks(compilation: Compilation): Set { + const runtimeChunks = new Set(); + const entries = compilation.entrypoints; + + for (const entrypoint of entries.values()) { + const runtimeChunk = entrypoint.getRuntimeChunk(); + if (runtimeChunk) { + runtimeChunks.add(runtimeChunk); } } return runtimeChunks; diff --git a/packages/enhanced/src/lib/container/ModuleFederationPlugin.ts b/packages/enhanced/src/lib/container/ModuleFederationPlugin.ts index 2505833d25..a143f3aef2 100644 --- a/packages/enhanced/src/lib/container/ModuleFederationPlugin.ts +++ b/packages/enhanced/src/lib/container/ModuleFederationPlugin.ts @@ -19,6 +19,7 @@ import ContainerReferencePlugin from './ContainerReferencePlugin'; import FederationRuntimePlugin from './runtime/FederationRuntimePlugin'; import { RemoteEntryPlugin } from './runtime/RemoteEntryPlugin'; import { ExternalsType } from 'webpack/declarations/WebpackOptions'; +import StartupChunkDependenciesPlugin from '../startup/MfStartupChunkDependenciesPlugin'; const isValidExternalsType = require( normalizeWebpackPath( @@ -63,10 +64,18 @@ class ModuleFederationPlugin implements WebpackPluginInstance { compiler, ); } + if (options.embedRuntime) { + new StartupChunkDependenciesPlugin({ + asyncChunkLoading: true, + }).apply(compiler); + } + if (options.dts !== false) { new DtsPlugin(options).apply(compiler); } + new FederationRuntimePlugin(options).apply(compiler); + const library = options.library || { type: 'var', name: options.name }; const remoteType = options.remoteType || diff --git a/packages/enhanced/src/lib/container/RemoteRuntimeModule.ts b/packages/enhanced/src/lib/container/RemoteRuntimeModule.ts index 635c845c83..14005344c1 100644 --- a/packages/enhanced/src/lib/container/RemoteRuntimeModule.ts +++ b/packages/enhanced/src/lib/container/RemoteRuntimeModule.ts @@ -33,6 +33,17 @@ class RemoteRuntimeModule extends RuntimeModule { const chunkToRemotesMapping: Record = {}; const idToExternalAndNameMapping: Record = {}; const idToRemoteMap: RemotesOptions['idToRemoteMap'] = {}; + // let chunkReferences: Set = new Set(); + + // if (this.chunk && chunkGraph) { + // const requirements = chunkGraph.getTreeRuntimeRequirements(this.chunk); + // if (requirements.has('federation-entry-startup')) { + // chunkReferences = this.chunk.getAllReferencedChunks(); + // } else { + // // remote entry doesnt need federation startup, can have async chunk map only + // chunkReferences = this.chunk.getAllAsyncChunks(); + // } + // } const allChunks = [ ...Array.from(this.chunk?.getAllReferencedChunks() || []), diff --git a/packages/enhanced/src/lib/container/runtime/ChildCompilationRuntimePlugin.ts b/packages/enhanced/src/lib/container/runtime/ChildCompilationRuntimePlugin.ts new file mode 100644 index 0000000000..215ea9d9ef --- /dev/null +++ b/packages/enhanced/src/lib/container/runtime/ChildCompilationRuntimePlugin.ts @@ -0,0 +1,310 @@ +// 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'; + +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; + console.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); + console.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) { + console.warn( + 'Embed Federation Runtime: Child compilation is undefined', + ); + return callback(); + } + + if (childCompilation.errors.length) { + return callback(childCompilation.errors[0]); + } + + console.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); + console.log(`Custom runtime module added to chunk: ${chunk.name}`); + }; + compilation.hooks.runtimeRequirementInTree + .for(federationGlobal) + .tap('CustomRuntimePlugin', handler); + }, + ); + } +} + +class CustomRuntimeModule extends RuntimeModule { + private bundledCode: string | null = null; + 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 new file mode 100644 index 0000000000..8f5d83b1f0 --- /dev/null +++ b/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimeModule.ts @@ -0,0 +1,96 @@ +import { normalizeWebpackPath } from '@module-federation/sdk/normalize-webpack-path'; +import { getFederationGlobalScope } from './utils'; +import type { Chunk, Module } from 'webpack'; + +const { RuntimeModule, NormalModule, Template, RuntimeGlobals } = require( + normalizeWebpackPath('webpack'), +) as typeof import('webpack'); +const ConcatenatedModule = require( + normalizeWebpackPath('webpack/lib/optimize/ConcatenatedModule'), +) as typeof import('webpack/lib/optimize/ConcatenatedModule'); + +const federationGlobal = getFederationGlobalScope(RuntimeGlobals); + +class EmbedFederationRuntimeModule extends RuntimeModule { + private bundlerRuntimePath: string; + + constructor(bundlerRuntimePath: string) { + super('EmbedFederationRuntimeModule', RuntimeModule.STAGE_ATTACH); + this.bundlerRuntimePath = bundlerRuntimePath; + } + + override identifier() { + return 'webpack/runtime/embed/federation'; + } + + override generate(): string | null { + const { compilation, chunk, chunkGraph, bundlerRuntimePath } = this; + if (!chunk || !chunkGraph || !compilation) { + return null; + } + + const found = this.findModule(chunk, bundlerRuntimePath); + if (!found) return null; + + const initRuntimeModuleGetter = compilation.runtimeTemplate.moduleRaw({ + module: found, + chunkGraph, + request: this.bundlerRuntimePath, + weak: false, + runtimeRequirements: new Set(), + }); + + const exportExpr = compilation.runtimeTemplate.exportFromImport({ + moduleGraph: compilation.moduleGraph, + module: found, + request: this.bundlerRuntimePath, + exportName: ['default'], + originModule: found, + asiSafe: true, + isCall: false, + callContext: false, + defaultInterop: true, + importVar: 'federation', + initFragments: [], + runtime: chunk.runtime, + runtimeRequirements: new Set(), + }); + + return Template.asString([ + `var federation = ${initRuntimeModuleGetter};`, + `federation = ${exportExpr}`, + `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;', + ]); + } + + private findModule(chunk: Chunk, bundlerRuntimePath: string): Module | null { + const { chunkGraph, compilation } = this; + if (!chunk || !chunkGraph || !compilation) { + return null; + } + for (const mod of chunkGraph.getChunkModulesIterable(chunk)) { + if (mod instanceof NormalModule && mod.resource === bundlerRuntimePath) { + return mod; + } + + if (mod instanceof ConcatenatedModule) { + for (const m of mod.modules) { + if (m instanceof NormalModule && m.resource === bundlerRuntimePath) { + return mod; + } + } + } + } + return null; + } +} + +export default EmbedFederationRuntimeModule; diff --git a/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts b/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts new file mode 100644 index 0000000000..5a5c0ce0af --- /dev/null +++ b/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts @@ -0,0 +1,49 @@ +import { normalizeWebpackPath } from '@module-federation/sdk/normalize-webpack-path'; +import EmbedFederationRuntimeModule from './EmbedFederationRuntimeModule'; +const { RuntimeGlobals } = require( + normalizeWebpackPath('webpack'), +) as typeof import('webpack'); +import type { Compiler, Compilation, Chunk, Module, ChunkGraph } from 'webpack'; +import { getFederationGlobalScope } from './utils'; +const EntryDependency = require( + normalizeWebpackPath('webpack/lib/dependencies/EntryDependency'), +) as typeof import('webpack/lib/dependencies/EntryDependency'); + +const federationGlobal = getFederationGlobalScope(RuntimeGlobals); + +class EmbedFederationRuntimePlugin { + private bundlerRuntimePath: string; + + constructor(path: string) { + this.bundlerRuntimePath = path; + } + + apply(compiler: Compiler): void { + compiler.hooks.thisCompilation.tap( + 'EmbedFederationRuntimePlugin', + (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; + } + + runtimeRequirements.add('embeddedFederationRuntime'); + const runtimeModule = new EmbedFederationRuntimeModule( + this.bundlerRuntimePath, + ); + + compilation.addRuntimeModule(chunk, runtimeModule); + }; + compilation.hooks.runtimeRequirementInTree + .for(federationGlobal) + .tap('EmbedFederationRuntimePlugin', handler); + }, + ); + } +} + +export default EmbedFederationRuntimePlugin; diff --git a/packages/enhanced/src/lib/container/runtime/FederationRuntimeModule.ts b/packages/enhanced/src/lib/container/runtime/FederationRuntimeModule.ts index 45d92eecb4..abd0e63e7b 100644 --- a/packages/enhanced/src/lib/container/runtime/FederationRuntimeModule.ts +++ b/packages/enhanced/src/lib/container/runtime/FederationRuntimeModule.ts @@ -14,12 +14,12 @@ const { RuntimeModule, RuntimeGlobals, Template } = require( ) as typeof import('webpack'); class FederationRuntimeModule extends RuntimeModule { - runtimeRequirements: Set; + runtimeRequirements: ReadonlySet; containerName: string; initOptionsWithoutShared: NormalizedRuntimeInitOptionsWithOutShared; constructor( - runtimeRequirements: Set, + runtimeRequirements: ReadonlySet, containerName: string, initOptionsWithoutShared: NormalizedRuntimeInitOptionsWithOutShared, ) { diff --git a/packages/enhanced/src/lib/container/runtime/FederationRuntimePlugin.ts b/packages/enhanced/src/lib/container/runtime/FederationRuntimePlugin.ts index 5aea3f17c9..140b90ade1 100644 --- a/packages/enhanced/src/lib/container/runtime/FederationRuntimePlugin.ts +++ b/packages/enhanced/src/lib/container/runtime/FederationRuntimePlugin.ts @@ -1,6 +1,12 @@ -import type { Compiler, WebpackPluginInstance } from 'webpack'; +import type { + Compiler, + WebpackPluginInstance, + Compilation, + Chunk, +} from 'webpack'; import { normalizeWebpackPath } from '@module-federation/sdk/normalize-webpack-path'; import FederationRuntimeModule from './FederationRuntimeModule'; +import type { moduleFederationPlugin } from '@module-federation/sdk'; import { getFederationGlobalScope, normalizeRuntimeInitOptionsWithOutShared, @@ -11,7 +17,9 @@ import { import fs from 'fs'; import path from 'path'; import { TEMP_DIR } from '../constant'; -import type { moduleFederationPlugin } from '@module-federation/sdk'; +import EmbedFederationRuntimePlugin from './EmbedFederationRuntimePlugin'; +import ContainerEntryModule from '../ContainerEntryModule'; +import HoistContainerReferences from '../HoistContainerReferencesPlugin'; import pBtoa from 'btoa'; const { RuntimeGlobals, Template } = require( @@ -32,6 +40,12 @@ const BundlerRuntimePath = require.resolve( const RuntimePath = require.resolve('@module-federation/runtime', { paths: [RuntimeToolsPath], }); +const EmbeddedRuntimePath = require.resolve( + '@module-federation/runtime/embedded', + { + paths: [RuntimeToolsPath], + }, +); const federationGlobal = getFederationGlobalScope(RuntimeGlobals); @@ -46,7 +60,11 @@ class FederationRuntimePlugin { this.bundlerRuntimePath = BundlerRuntimePath; } - static getTemplate(runtimePlugins: string[], bundlerRuntimePath?: string) { + static getTemplate( + runtimePlugins: string[], + bundlerRuntimePath?: string, + embedRuntime: boolean = false, + ) { // internal runtime plugin const normalizedBundlerRuntimePath = normalizeToPosixPath( bundlerRuntimePath || BundlerRuntimePath, @@ -69,17 +87,25 @@ class FederationRuntimePlugin { }); } + const embedRuntimeLines = Template.asString([ + `if(!${federationGlobal}.runtime){`, + Template.indent([ + `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];`]), + '}', + ]), + '}', + ]); + return Template.asString([ `import federation from '${normalizedBundlerRuntimePath}';`, runtimePluginTemplates, - `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];`]), - '}', + embedRuntimeLines, `if(!${federationGlobal}.instance){`, Template.indent([ runtimePluginNames.length @@ -113,11 +139,13 @@ class FederationRuntimePlugin { containerName: string, runtimePlugins: string[], bundlerRuntimePath?: string, + embedRuntime: boolean = false, ) { const hash = createHash( `${containerName} ${FederationRuntimePlugin.getTemplate( runtimePlugins, bundlerRuntimePath, + embedRuntime, )}`, ); return path.join(TEMP_DIR, `entry.${hash}.js`); @@ -137,6 +165,7 @@ class FederationRuntimePlugin { this.options.name!, this.options.runtimePlugins!, this.bundlerRuntimePath, + this.options.embedRuntime, ); } else { this.entryFilePath = `data:text/javascript;charset=utf-8;base64,${pBtoa( @@ -163,6 +192,7 @@ class FederationRuntimePlugin { FederationRuntimePlugin.getTemplate( this.options.runtimePlugins!, this.bundlerRuntimePath, + this.options.embedRuntime, ), ); } @@ -205,37 +235,72 @@ class FederationRuntimePlugin { compiler.hooks.thisCompilation.tap( this.constructor.name, - (compilation, { normalModuleFactory }) => { + (compilation: Compilation, { normalModuleFactory }) => { + const isEnabledForChunk = (chunk: Chunk): boolean => { + const [entryModule] = + compilation.chunkGraph.getChunkEntryModulesIterable(chunk) || []; + return entryModule instanceof ContainerEntryModule; + }; + const handler = (chunk: Chunk, runtimeRequirements: Set) => { + if (runtimeRequirements.has(federationGlobal)) return; + runtimeRequirements.add(federationGlobal); + runtimeRequirements.add(RuntimeGlobals.interceptModuleExecution); + runtimeRequirements.add(RuntimeGlobals.moduleCache); + runtimeRequirements.add(RuntimeGlobals.compatGetDefaultExport); + + compilation.addRuntimeModule( + chunk, + new FederationRuntimeModule( + runtimeRequirements, + name, + initOptionsWithoutShared, + ), + ); + }; + compilation.hooks.additionalTreeRuntimeRequirements.tap( this.constructor.name, - (chunk, runtimeRequirements) => { - if (runtimeRequirements.has(federationGlobal)) { + (chunk: Chunk, runtimeRequirements: Set) => { + if (!chunk.hasRuntime()) return; + if (runtimeRequirements.has(RuntimeGlobals.initializeSharing)) return; - } - runtimeRequirements.add(RuntimeGlobals.interceptModuleExecution); - runtimeRequirements.add(RuntimeGlobals.moduleCache); - runtimeRequirements.add(RuntimeGlobals.compatGetDefaultExport); - runtimeRequirements.add(federationGlobal); - compilation.addRuntimeModule( - chunk, - new FederationRuntimeModule( - runtimeRequirements, - name, - initOptionsWithoutShared, - ), - ); + if (runtimeRequirements.has(RuntimeGlobals.currentRemoteGetScope)) + return; + if (runtimeRequirements.has(RuntimeGlobals.shareScopeMap)) return; + if (runtimeRequirements.has(federationGlobal)) return; + handler(chunk, runtimeRequirements); }, ); + + // if federation runtime requirements exist + // attach runtime module to the chunk + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.initializeSharing) + .tap(this.constructor.name, handler); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.currentRemoteGetScope) + .tap(this.constructor.name, handler); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.shareScopeMap) + .tap(this.constructor.name, handler); + compilation.hooks.runtimeRequirementInTree + .for(federationGlobal) + .tap(this.constructor.name, handler); }, ); } setRuntimeAlias(compiler: Compiler) { - let runtimePath = RuntimePath; + let runtimePath = this.options?.embedRuntime + ? EmbeddedRuntimePath + : RuntimePath; if (this.options?.implementation) { - runtimePath = require.resolve('@module-federation/runtime', { - paths: [this.options.implementation], - }); + runtimePath = require.resolve( + `@module-federation/runtime${this.options?.embedRuntime ? '/embedded' : ''}`, + { + paths: [this.options.implementation], + }, + ); } if (Array.isArray(compiler.options.resolve.alias)) { return; @@ -281,7 +346,6 @@ class FederationRuntimePlugin { ); if (useContainerPlugin && !this.options) { - // @ts-ignore this.options = useContainerPlugin._options; } @@ -292,7 +356,7 @@ class FederationRuntimePlugin { }; } if (this.options && !this.options?.name) { - // the instance may get the same one if the name is the same https://github.com/module-federation/core/blob/main/packages/runtime/src/index.ts#L18 + //! the instance may get the same one if the name is the same https://github.com/module-federation/core/blob/main/packages/runtime/src/index.ts#L18 this.options.name = compiler.options.output.uniqueName || `container_${Date.now()}`; } @@ -305,7 +369,33 @@ class FederationRuntimePlugin { }, ); } + if (this.options?.embedRuntime) { + this.bundlerRuntimePath = this.bundlerRuntimePath.replace( + '.cjs.js', + '.esm.js', + ); + new EmbedFederationRuntimePlugin(this.bundlerRuntimePath).apply(compiler); + + new HoistContainerReferences( + this.options.name ? this.options.name + '_partial' : undefined, + // hoist all modules of federation entry + this.getFilePath(), + this.bundlerRuntimePath, + ).apply(compiler); + + new compiler.webpack.NormalModuleReplacementPlugin( + /@module-federation\/runtime/, + (resolveData) => { + if (/webpack-bundler-runtime/.test(resolveData.contextInfo.issuer)) { + resolveData.request = RuntimePath.replace('cjs', 'esm'); + if (resolveData.createData) { + resolveData.createData.request = resolveData.request; + } + } + }, + ).apply(compiler); + } this.prependEntry(compiler); this.injectRuntime(compiler); this.setRuntimeAlias(compiler); diff --git a/packages/enhanced/src/lib/sharing/ConsumeSharedRuntimeModule.ts b/packages/enhanced/src/lib/sharing/ConsumeSharedRuntimeModule.ts index e719523c5a..0ed0842722 100644 --- a/packages/enhanced/src/lib/sharing/ConsumeSharedRuntimeModule.ts +++ b/packages/enhanced/src/lib/sharing/ConsumeSharedRuntimeModule.ts @@ -92,6 +92,13 @@ class ConsumeSharedRuntimeModule extends RuntimeModule { moduleIdToSourceMapping.set(id, sharedInfoAndHandlerStr); } }; + // const chunkReferences = this._runtimeRequirements.has( + // 'federation-entry-startup', + // ) + // ? this.chunk?.getAllReferencedChunks() + // : this.chunk?.getAllAsyncChunks(); + // + // const allChunks = chunkReferences || []; const allChunks = [...(this.chunk?.getAllReferencedChunks() || [])]; for (const chunk of allChunks) { const modules = chunkGraph.getChunkModulesIterableBySourceType( diff --git a/packages/enhanced/src/lib/sharing/ProvideSharedPlugin.ts b/packages/enhanced/src/lib/sharing/ProvideSharedPlugin.ts index 5b1030628b..e66ebae2fe 100644 --- a/packages/enhanced/src/lib/sharing/ProvideSharedPlugin.ts +++ b/packages/enhanced/src/lib/sharing/ProvideSharedPlugin.ts @@ -268,7 +268,7 @@ class ProvideSharedPlugin { compilation.dependencyFactories.set( ProvideSharedDependency, - // @ts-ignore + //@ts-ignore new ProvideSharedModuleFactory(), ); }, diff --git a/packages/enhanced/src/lib/startup/MfStartupChunkDependenciesPlugin.ts b/packages/enhanced/src/lib/startup/MfStartupChunkDependenciesPlugin.ts new file mode 100644 index 0000000000..2dd6c8295d --- /dev/null +++ b/packages/enhanced/src/lib/startup/MfStartupChunkDependenciesPlugin.ts @@ -0,0 +1,148 @@ +'use strict'; + +import { normalizeWebpackPath } from '@module-federation/sdk/normalize-webpack-path'; +import { generateEntryStartup } from './StartupHelpers'; +import type { Compiler, Chunk } from 'webpack'; +import ContainerEntryModule from '../container/ContainerEntryModule'; + +const { RuntimeGlobals } = require( + normalizeWebpackPath('webpack'), +) as typeof import('webpack'); + +const StartupEntrypointRuntimeModule = require( + normalizeWebpackPath('webpack/lib/runtime/StartupEntrypointRuntimeModule'), +) as typeof import('webpack/lib/runtime/StartupEntrypointRuntimeModule'); +const ConcatenatedModule = require( + normalizeWebpackPath('webpack/lib/optimize/ConcatenatedModule'), +) as typeof import('webpack/lib/optimize/ConcatenatedModule'); + +interface Options { + asyncChunkLoading?: boolean; +} + +class StartupChunkDependenciesPlugin { + asyncChunkLoading: boolean; + + constructor(options: Options) { + this.asyncChunkLoading = options.asyncChunkLoading ?? true; + } + + private isEnabledForChunk(chunk: Chunk, compilation: any): boolean { + if (chunk.id === 'build time chunk') return false; + const [finalEntry] = + compilation.chunkGraph.getChunkEntryModulesIterable(chunk) || []; + return !(finalEntry instanceof ContainerEntryModule); + } + + apply(compiler: Compiler): void { + compiler.hooks.thisCompilation.tap( + 'MfStartupChunkDependenciesPlugin', + (compilation) => { + const isEnabledForChunk = (chunk: Chunk) => + this.isEnabledForChunk(chunk, compilation); + + compilation.hooks.additionalTreeRuntimeRequirements.tap( + 'StartupChunkDependenciesPlugin', + (chunk, set, { chunkGraph }) => { + if (!isEnabledForChunk(chunk)) return; + if (chunk.hasRuntime()) { + set.add(RuntimeGlobals.startupEntrypoint); + set.add(RuntimeGlobals.ensureChunk); + set.add(RuntimeGlobals.ensureChunkIncludeEntries); + } + }, + ); + + compilation.hooks.additionalChunkRuntimeRequirements.tap( + 'MfStartupChunkDependenciesPlugin', + (chunk, set, { chunkGraph }) => { + if (!isEnabledForChunk(chunk)) return; + if (chunkGraph.getNumberOfEntryModules(chunk) === 0) return; + set.add('federation-entry-startup'); + }, + ); + + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.startupEntrypoint) + .tap( + 'StartupChunkDependenciesPlugin', + (chunk, set, { chunkGraph }) => { + if (!isEnabledForChunk(chunk)) return; + set.add(RuntimeGlobals.require); + set.add(RuntimeGlobals.ensureChunk); + set.add(RuntimeGlobals.ensureChunkIncludeEntries); + compilation.addRuntimeModule( + chunk, + new StartupEntrypointRuntimeModule(this.asyncChunkLoading), + ); + }, + ); + + const { renderStartup } = + compiler.webpack.javascript.JavascriptModulesPlugin.getCompilationHooks( + compilation, + ); + + renderStartup.tap( + 'MfStartupChunkDependenciesPlugin', + (startupSource, lastInlinedModule, renderContext) => { + const { chunk, chunkGraph, runtimeTemplate } = renderContext; + + if (!isEnabledForChunk(chunk)) { + return startupSource; + } + + let federationRuntimeModule: any = null; + + const isFederationModule = (module: any) => + module.context?.endsWith('.federation'); + + for (const module of chunkGraph.getChunkEntryModulesIterable( + chunk, + )) { + if (isFederationModule(module)) { + federationRuntimeModule = module; + break; + } + + if (module && '_modules' in module) { + for (const concatModule of ( + module as InstanceType + )._modules) { + if (isFederationModule(concatModule)) { + federationRuntimeModule = module; + break; + } + } + } + } + + if (!federationRuntimeModule) { + return startupSource; + } + + const federationModuleId = chunkGraph.getModuleId( + federationRuntimeModule, + ); + const entryModules = Array.from( + chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk), + ); + + return new compiler.webpack.sources.ConcatSource( + `${RuntimeGlobals.require}(${JSON.stringify(federationModuleId)});\n`, + generateEntryStartup( + chunkGraph, + runtimeTemplate, + entryModules, + chunk, + false, + ), + ); + }, + ); + }, + ); + } +} + +export default StartupChunkDependenciesPlugin; diff --git a/packages/enhanced/src/lib/startup/StartupHelpers.ts b/packages/enhanced/src/lib/startup/StartupHelpers.ts new file mode 100644 index 0000000000..7acb388c81 --- /dev/null +++ b/packages/enhanced/src/lib/startup/StartupHelpers.ts @@ -0,0 +1,155 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra, Zack Jackson @ScriptedAlchemy +*/ + +'use strict'; +import type Chunk from 'webpack/lib/Chunk'; +import type ChunkGraph from 'webpack/lib/ChunkGraph'; +import { normalizeWebpackPath } from '@module-federation/sdk/normalize-webpack-path'; +import type { EntryModuleWithChunkGroup } from 'webpack/lib/ChunkGraph'; +import type RuntimeTemplate from 'webpack/lib/RuntimeTemplate'; +import type Entrypoint from 'webpack/lib/Entrypoint'; + +const { RuntimeGlobals, Template } = require( + normalizeWebpackPath('webpack'), +) as typeof import('webpack'); +const { isSubset } = require( + normalizeWebpackPath('webpack/lib/util/SetHelpers'), +) as typeof import('webpack/lib/util/SetHelpers'); +const { getAllChunks } = require( + normalizeWebpackPath('webpack/lib/javascript/ChunkHelpers'), +) as typeof import('webpack/lib/javascript/ChunkHelpers'); + +const EXPORT_PREFIX = `var ${RuntimeGlobals.exports} = `; + +export const federationStartup = 'federation-entry-startup'; + +export const generateEntryStartup = ( + chunkGraph: ChunkGraph, + runtimeTemplate: RuntimeTemplate, + entries: EntryModuleWithChunkGroup[], + chunk: Chunk, + passive: boolean, +): string => { + /** @type {string[]} */ + const runtime: string[] = [ + `var __webpack_exec__ = ${runtimeTemplate.returningFunction( + `${RuntimeGlobals.require}(${RuntimeGlobals.entryModuleId} = moduleId)`, + 'moduleId', + )}`, + '', + '\n', + 'var promises = [];', + ]; + + const treeRuntimeRequirements = chunkGraph.getTreeRuntimeRequirements(chunk); + const chunkRuntimeRequirements = + chunkGraph.getChunkRuntimeRequirements(chunk); + const federation = + chunkRuntimeRequirements.has(federationStartup) || + treeRuntimeRequirements.has(federationStartup); + + const hasRemotes = + chunkRuntimeRequirements.has(RuntimeGlobals.currentRemoteGetScope) || + // check if tree has req + treeRuntimeRequirements.has(RuntimeGlobals.currentRemoteGetScope) || + // check if chunk contains remote module types + // currentRemoteGetScope is not reliable requirement for host check + !!chunkGraph.getChunkModulesIterableBySourceType(chunk, 'remote'); + + const hasConsumes = + chunkRuntimeRequirements.has(RuntimeGlobals.initializeSharing) || + treeRuntimeRequirements.has(RuntimeGlobals.initializeSharing) || + chunkRuntimeRequirements.has(RuntimeGlobals.shareScopeMap) || + treeRuntimeRequirements.has(RuntimeGlobals.shareScopeMap); + + const runModule = (id: string) => { + return `__webpack_exec__(${JSON.stringify(id)})`; + }; + const outputCombination = ( + chunks: Set, + moduleIds: string[], + final?: boolean, + ) => { + if (chunks.size === 0 && !federation) { + runtime.push( + `${final ? EXPORT_PREFIX : ''}(${moduleIds.map(runModule).join(', ')});`, + ); + } else { + const fn = runtimeTemplate.returningFunction( + moduleIds.map(runModule).join(', '), + ); + if (federation) { + const chunkIds = Array.from(chunks, (c: Chunk) => c.id); + + const wrappedInit = (body: string) => + Template.asString([ + 'Promise.all([', + Template.indent([ + hasConsumes + ? `${RuntimeGlobals.ensureChunkHandlers}.consumes,` + : '', + hasRemotes + ? `${RuntimeGlobals.ensureChunkHandlers}.remotes,` + : '', + ]), + `].reduce(${runtimeTemplate.returningFunction(`handler('${chunk.id}', p), p`, 'p, handler')}, promises)`, + `).then(${runtimeTemplate.returningFunction(body)});`, + ]); + + const wrap = wrappedInit( + `${ + passive + ? RuntimeGlobals.onChunksLoaded + : RuntimeGlobals.startupEntrypoint + }(0, ${JSON.stringify(chunkIds)}, ${fn})`, + ); + + runtime.push(`${final && !passive ? EXPORT_PREFIX : ''}${wrap}`); + } else { + const chunkIds = Array.from(chunks, (c: Chunk) => c.id); + runtime.push( + `${final && !passive ? EXPORT_PREFIX : ''}${ + passive + ? RuntimeGlobals.onChunksLoaded + : RuntimeGlobals.startupEntrypoint + }(0, ${JSON.stringify(chunkIds)}, ${fn});`, + ); + if (final && passive) { + runtime.push(`${EXPORT_PREFIX}${RuntimeGlobals.onChunksLoaded}();`); + } + } + } + }; + + let currentChunks: Set | undefined = undefined; + let currentModuleIds: string[] | undefined = undefined; + + for (const [module, entrypoint] of entries) { + if (!entrypoint) continue; + const runtimeChunk = entrypoint.getRuntimeChunk() as Entrypoint.Chunk; + const moduleId = chunkGraph.getModuleId(module) as string; + const chunks = getAllChunks(entrypoint as Entrypoint, chunk, runtimeChunk); + if ( + currentChunks && + currentChunks.size === chunks.size && + isSubset(currentChunks, chunks) + ) { + currentModuleIds!.push(moduleId); + } else { + if (currentChunks) { + outputCombination(currentChunks, currentModuleIds!); + } + currentChunks = chunks; + currentModuleIds = [moduleId]; + } + } + + // output current modules with export prefix + if (currentChunks) { + outputCombination(currentChunks, currentModuleIds!, true); + } + runtime.push(''); + return Template.asString(runtime); +}; diff --git a/packages/enhanced/src/wrapper/HoistContainerReferencesPlugin.ts b/packages/enhanced/src/wrapper/HoistContainerReferencesPlugin.ts index e2fc48aed2..a1ea8b3657 100644 --- a/packages/enhanced/src/wrapper/HoistContainerReferencesPlugin.ts +++ b/packages/enhanced/src/wrapper/HoistContainerReferencesPlugin.ts @@ -7,10 +7,21 @@ export default class HoistContainerReferencesPlugin implements WebpackPluginInstance { name: string; - private containerName?: string | undefined; + private containerName: string; + private entryFilePath?: string; + private bundlerRuntimeDep?: string; + private explanation: string; - constructor(name?: string | undefined) { - this.containerName = name; + constructor( + name?: string, + entryFilePath?: string, + bundlerRuntimeDep?: string, + ) { + this.containerName = name || 'no known chunk name'; + this.entryFilePath = entryFilePath; + this.bundlerRuntimeDep = bundlerRuntimeDep; + this.explanation = + 'Bundler runtime path module is required for proper functioning'; this.name = PLUGIN_NAME; } @@ -20,6 +31,10 @@ export default class HoistContainerReferencesPlugin const CoreHoistContainerReferencesPlugin = require('../lib/container/HoistContainerReferencesPlugin') .default as typeof import('../lib/container/HoistContainerReferencesPlugin').default; - new CoreHoistContainerReferencesPlugin().apply(compiler); + new CoreHoistContainerReferencesPlugin( + this.containerName, + this.entryFilePath, + this.bundlerRuntimeDep, + ).apply(compiler); } } diff --git a/packages/nextjs-mf/package.json b/packages/nextjs-mf/package.json index c3c0b178e3..5bf3aa530a 100644 --- a/packages/nextjs-mf/package.json +++ b/packages/nextjs-mf/package.json @@ -43,7 +43,8 @@ "@module-federation/runtime": "workspace:*", "@module-federation/sdk": "workspace:*", "@module-federation/enhanced": "workspace:*", - "@module-federation/node": "workspace:*" + "@module-federation/node": "workspace:*", + "@module-federation/webpack-bundler-runtime": "workspace:*" }, "peerDependencies": { "webpack": "^5.40.0", diff --git a/packages/nextjs-mf/src/loaders/fixImageLoader.ts b/packages/nextjs-mf/src/loaders/fixImageLoader.ts index db0d528196..99a0903ea7 100644 --- a/packages/nextjs-mf/src/loaders/fixImageLoader.ts +++ b/packages/nextjs-mf/src/loaders/fixImageLoader.ts @@ -27,9 +27,8 @@ export async function fixImageLoader( ) { this.cacheable(true); - const isServer = this._compiler?.options.name !== 'client'; - //@ts-ignore - const { publicPath } = this._compiler.webpack.RuntimeGlobals; + const isServer = this._compiler?.options?.name !== 'client'; + const publicPath = this._compiler?.webpack?.RuntimeGlobals?.publicPath ?? ''; const result = await this.importModule( `${this.resourcePath}.webpack[javascript/auto]!=!${remaining}`, diff --git a/packages/nextjs-mf/src/plugins/NextFederationPlugin/apply-client-plugins.ts b/packages/nextjs-mf/src/plugins/NextFederationPlugin/apply-client-plugins.ts index 1f33dd3012..53ad5124b6 100644 --- a/packages/nextjs-mf/src/plugins/NextFederationPlugin/apply-client-plugins.ts +++ b/packages/nextjs-mf/src/plugins/NextFederationPlugin/apply-client-plugins.ts @@ -1,11 +1,9 @@ import type { Compiler } from 'webpack'; -import { - ModuleFederationPluginOptions, - NextFederationPluginExtraOptions, -} from '@module-federation/utilities'; +import { NextFederationPluginExtraOptions } from '@module-federation/utilities'; import { ChunkCorrelationPlugin } from '@module-federation/node'; import InvertedContainerPlugin from '../container/InvertedContainerPlugin'; -import { HoistContainerReferencesPlugin } from '@module-federation/enhanced'; +import type { moduleFederationPlugin } from '@module-federation/sdk'; + /** * Applies client-specific plugins. * @@ -17,33 +15,32 @@ import { HoistContainerReferencesPlugin } from '@module-federation/enhanced'; * This function applies plugins to the Webpack compiler instance that are specific to the client build of * a Next.js application with Module Federation enabled. These plugins include the following: * - * - DelegateModulesPlugin: Delegates modules to the webpack container runtime that can be streamed to other runtimes. * - ChunkCorrelationPlugin: Collects metadata on chunks to enable proper module loading across different runtimes. * - InvertedContainerPlugin: Adds custom runtime modules to the container runtime to allow a host to expose its * own remote interface at startup. - * - * If automatic page stitching is enabled, a loader is added to process the `next/dist/client/page-loader.js` - * file. If a custom library is specified in the options, an error is thrown. The options.library property is + + * If automatic page stitching is enabled, a warning is logged indicating that it is disabled in v7. + * If a custom library is specified in the options, an error is logged. The options.library property is * also set to `{ type: 'window', name: options.name }`. */ export function applyClientPlugins( compiler: Compiler, - options: ModuleFederationPluginOptions, + options: moduleFederationPlugin.ModuleFederationPluginOptions, extraOptions: NextFederationPluginExtraOptions, ): void { - const { webpack } = compiler; - const { remotes, name } = options; + const { name } = options; + // Adjust the public path if it is set to the default Next.js path if (compiler.options.output.publicPath === '/_next/') { compiler.options.output.publicPath = 'auto'; } - // If automatic page stitching is enabled, add a new rule to the compiler's module rules + // Log a warning if automatic page stitching is enabled, as it is disabled in v7 if (extraOptions.automaticPageStitching) { console.warn('[nextjs-mf]', 'automatic page stitching is disabled in v7'); } - // If a custom library is set, log an error message + // Log an error if a custom library is set, as it is not allowed if (options.library) { console.error('[nextjs-mf] you cannot set custom library'); } @@ -54,23 +51,19 @@ export function applyClientPlugins( name, }; - // Add a new chunk correlation plugin to the compiler + // Apply the ChunkCorrelationPlugin to collect metadata on chunks new ChunkCorrelationPlugin({ filename: [ 'static/chunks/federated-stats.json', 'server/federated-stats.json', ], - //@ts-ignore }).apply(compiler); - new HoistContainerReferencesPlugin(options.name).apply(compiler); - - // Add a new commonjs chunk loading plugin to the compiler + // Apply the InvertedContainerPlugin to add custom runtime modules to the container runtime new InvertedContainerPlugin({ runtime: 'webpack', container: options.name, remotes: options.remotes as Record, debug: extraOptions.debug, - //@ts-ignore }).apply(compiler); } diff --git a/packages/nextjs-mf/src/plugins/NextFederationPlugin/apply-server-plugins.ts b/packages/nextjs-mf/src/plugins/NextFederationPlugin/apply-server-plugins.ts index 3d20453a06..b09b17a927 100644 --- a/packages/nextjs-mf/src/plugins/NextFederationPlugin/apply-server-plugins.ts +++ b/packages/nextjs-mf/src/plugins/NextFederationPlugin/apply-server-plugins.ts @@ -1,65 +1,98 @@ -import type { Compiler } from 'webpack'; -import { ModuleFederationPluginOptions } from '@module-federation/utilities'; -import { HoistContainerReferencesPlugin } from '@module-federation/enhanced'; +import type { + WebpackOptionsNormalized, + Compiler, + ExternalItemFunctionData, +} from 'webpack'; +import type { moduleFederationPlugin } from '@module-federation/sdk'; import path from 'path'; import InvertedContainerPlugin from '../container/InvertedContainerPlugin'; -import { ModuleFederationPlugin } from '@module-federation/enhanced/webpack'; + +type EntryStaticNormalized = Awaited< + ReturnType any>> +>; + +interface ModifyEntryOptions { + compiler: Compiler; + prependEntry?: (entry: EntryStaticNormalized) => void; + staticEntry?: EntryStaticNormalized; +} + +// Modifies the Webpack entry configuration +export function modifyEntry(options: ModifyEntryOptions): void { + const { compiler, staticEntry, prependEntry } = options; + const operator = ( + oriEntry: EntryStaticNormalized, + newEntry: EntryStaticNormalized, + ): EntryStaticNormalized => Object.assign(oriEntry, newEntry); + + // If the entry is a function, wrap it to modify the result + if (typeof compiler.options.entry === 'function') { + const prevEntryFn = compiler.options.entry; + compiler.options.entry = async () => { + let res = await prevEntryFn(); + if (staticEntry) { + res = operator(res, staticEntry); + } + if (prependEntry) { + prependEntry(res); + } + return res; + }; + } else { + // If the entry is an object, directly modify it + if (staticEntry) { + compiler.options.entry = operator(compiler.options.entry, staticEntry); + } + if (prependEntry) { + prependEntry(compiler.options.entry); + } + } +} + /** - * This function applies server-specific plugins to the webpack compiler. + * Applies server-specific plugins to the webpack compiler. * * @param {Compiler} compiler - The Webpack compiler instance. - * @param {ModuleFederationPluginOptions} options - The ModuleFederationPluginOptions instance. - * - * @returns {void} + * @param {moduleFederationPlugin.ModuleFederationPluginOptions} options - The ModuleFederationPluginOptions instance. */ export function applyServerPlugins( compiler: Compiler, - options: ModuleFederationPluginOptions, + options: moduleFederationPlugin.ModuleFederationPluginOptions, ): void { const chunkFileName = compiler.options?.output?.chunkFilename; const uniqueName = compiler?.options?.output?.uniqueName || options.name; + const suffix = `-[chunkhash].js`; + // Modify chunk filename to include a unique suffix if not already present if ( typeof chunkFileName === 'string' && uniqueName && !chunkFileName.includes(uniqueName) ) { - const suffix = `-[chunkhash].js`; compiler.options.output.chunkFilename = chunkFileName.replace( '.js', suffix, ); } - new HoistContainerReferencesPlugin(options.name).apply(compiler); - - // Add a new commonjs chunk loading plugin to the compiler + // Apply the InvertedContainerPlugin to the compiler new InvertedContainerPlugin({ runtime: 'webpack-runtime', container: options.name, remotes: options.remotes as Record, debug: false, - //@ts-ignore }).apply(compiler); } /** - * This function configures server-specific library and filename options. + * Configures server-specific library and filename options. * * @param {ModuleFederationPluginOptions} options - The ModuleFederationPluginOptions instance. - * - * @returns {void} - * - * @remarks - * This function configures the library and filename options for server builds. The library option is - * set to the commonjs-module format for chunks and the container, which allows them to be streamed over - * to hosts with the NodeFederationPlugin. The filename option is set to the basename of the current - * filename. */ export function configureServerLibraryAndFilename( - options: ModuleFederationPluginOptions, + options: moduleFederationPlugin.ModuleFederationPluginOptions, ): void { - // Configure the library option with type "commonjs-module" and the name from the options + // Set the library option to "commonjs-module" format with the name from the options options.library = { type: 'commonjs-module', name: options.name, @@ -70,67 +103,53 @@ export function configureServerLibraryAndFilename( } /** - * This function patches Next.js' default externals function to make sure shared modules are bundled and not treated as external. + * Patches Next.js' default externals function to ensure shared modules are bundled and not treated as external. * * @param {Compiler} compiler - The Webpack compiler instance. * @param {ModuleFederationPluginOptions} options - The ModuleFederationPluginOptions instance. - * - * @returns {void} - * - * @remarks - * In server builds, all node modules are treated as external, which prevents them from being shared - * via module federation. To work around this limitation, we mark shared modules as internalizable - * modules that webpack puts into chunks that can be streamed to other runtimes as needed. - * - * This function replaces Next.js' default externals function with a new asynchronous function that - * checks whether a module should be treated as external. If the module should not be treated as - * external, the function returns without calling the original externals function. Otherwise, the - * function calls the original externals function and retrieves the result. If the result is null, - * the function returns without further processing. If the module is from Next.js or React, the - * function returns the original result. Otherwise, the function returns null. */ export function handleServerExternals( compiler: Compiler, - options: ModuleFederationPluginOptions, + options: moduleFederationPlugin.ModuleFederationPluginOptions, ): void { if ( Array.isArray(compiler.options.externals) && - compiler.options.externals[0] + typeof compiler.options.externals[0] === 'function' ) { - // Retrieve the original externals function - const originalExternals = compiler.options.externals[0]; + const originalExternals = compiler.options.externals[0] as ( + data: ExternalItemFunctionData, + callback: any, + ) => undefined | string; - // Replace the original externals function with a new asynchronous function - compiler.options.externals[0] = async function (ctx: any, callback: any) { - //@ts-ignore + compiler.options.externals[0] = async function ( + ctx: ExternalItemFunctionData, + callback: any, + ) { const fromNext = await originalExternals(ctx, callback); - // If the result is null, return without further processing if (!fromNext) { return; } - // If the module is from Next.js or React, return the original result const req = fromNext.split(' ')[1]; - // Check if the module should not be treated as external if ( ctx.request && (ctx.request.includes('@module-federation/utilities') || Object.keys(options.shared || {}).some((key) => { + const sharedOptions = options.shared as Record< + string, + { import: boolean } + >; return ( - //@ts-ignore - options.shared?.[key]?.import !== false && + sharedOptions[key]?.import !== false && (key.endsWith('/') ? req.includes(key) : req === key) ); }) || ctx.request.includes('@module-federation/')) ) { - // If the module should not be treated as external, return without calling the original externals function return; } if ( req.startsWith('next') || - // make sure we dont screw up package names that start with react - // like react-carousel or react-spring req.startsWith('react/') || req.startsWith('react-dom/') || req === 'react' || @@ -139,38 +158,25 @@ export function handleServerExternals( ) { return fromNext; } - // Otherwise, return (null) to treat the module as internalizable return; }; } } /** - * This function configures server-specific compiler options. + * Configures server-specific compiler options. * * @param {Compiler} compiler - The Webpack compiler instance. - * - * @returns {void} - * - * @remarks - * This function configures the compiler options for server builds. It turns off the compiler target on node - * builds because it adds its own chunk loading runtime module with NodeFederationPlugin and StreamingTargetPlugin. - * It also disables split chunks to prevent conflicts from occurring in the graph. - * */ export function configureServerCompilerOptions(compiler: Compiler): void { - // Turn off the compiler target on node builds because we add our own chunk loading runtime module - // with NodeFederationPlugin and StreamingTargetPlugin + // Disable the global option in node builds and set the target to "async-node" compiler.options.node = { ...compiler.options.node, global: false, }; compiler.options.target = 'async-node'; - // no custom chunk rules - compiler.options.optimization.splitChunks = undefined; - // solves strange issues where next doesnt create a runtime chunk - // might be related to if an api route exists or not + // Ensure a runtime chunk is created compiler.options.optimization.runtimeChunk = { name: 'webpack-runtime', }; diff --git a/packages/nextjs-mf/src/plugins/NextFederationPlugin/index.ts b/packages/nextjs-mf/src/plugins/NextFederationPlugin/index.ts index 1c9a1fb1c2..09f0dd4f0a 100644 --- a/packages/nextjs-mf/src/plugins/NextFederationPlugin/index.ts +++ b/packages/nextjs-mf/src/plugins/NextFederationPlugin/index.ts @@ -20,6 +20,7 @@ import { validatePluginOptions, } from './validate-options'; import { + modifyEntry, applyServerPlugins, configureServerCompilerOptions, configureServerLibraryAndFilename, @@ -60,15 +61,28 @@ export class NextFederationPlugin { if (!this.validateOptions(compiler)) return; const isServer = this.isServerCompiler(compiler); new CopyFederationPlugin(isServer).apply(compiler); - this.applyConditionalPlugins(compiler, isServer); const normalFederationPluginOptions = this.getNormalFederationPluginOptions( compiler, isServer, ); - // ContainerPlugin will get NextFederationPlugin._options, so NextFederationPlugin._options should be the same as normalFederationPluginOptions this._options = normalFederationPluginOptions; - new ModuleFederationPlugin(normalFederationPluginOptions).apply(compiler); + this.applyConditionalPlugins(compiler, isServer); + new ModuleFederationPlugin(normalFederationPluginOptions).apply(compiler); + modifyEntry({ + compiler, + prependEntry: (entry) => { + Object.keys(entry).forEach((entryName) => { + const entryItem = entry[entryName]; + if (!entryName.startsWith('pages/api')) return; + if (!entryItem.import) return; + // unpatch entry of webpack api runtime + entryItem.import = entryItem.import.filter((i) => { + return !i.includes('.federation/entry'); + }); + }); + }, + }); const runtimeESMPath = require.resolve( '@module-federation/runtime/dist/index.esm.js', ); @@ -81,7 +95,8 @@ export class NextFederationPlugin { compiler.hooks.afterPlugins.tap('PatchAliasWebpackPlugin', () => { compiler.options.resolve.alias = { ...compiler.options.resolve.alias, - '@module-federation/runtime$': runtimeESMPath, + //useing embedded runtime + // '@module-federation/runtime$': runtimeESMPath, }; }); } @@ -131,7 +146,7 @@ export class NextFederationPlugin { asyncFunction: true, }; - applyPathFixes(compiler, this._extraOptions); + applyPathFixes(compiler, this._options, this._extraOptions); if (this._extraOptions.debug) { compiler.options.devtool = false; } @@ -174,9 +189,10 @@ export class NextFederationPlugin { ...(isServer ? [require.resolve('@module-federation/node/runtimePlugin')] : []), + //disable loaders on internal plugins require.resolve(path.join(__dirname, '../container/runtimePlugin')), ...(this._options.runtimePlugins || []), - ], + ].map((plugin) => plugin + '?runtimePlugin'), //@ts-ignore exposes: { ...defaultExpose, @@ -198,6 +214,7 @@ export class NextFederationPlugin { // nextjs project needs to add config.watchOptions = ['**/node_modules/**', '**/@mf-types/**'] to prevent loop types update dts: this._options.dts ?? false, shareStrategy: this._options.shareStrategy ?? 'loaded-first', + embedRuntime: true, }; } diff --git a/packages/nextjs-mf/src/plugins/NextFederationPlugin/next-fragments.ts b/packages/nextjs-mf/src/plugins/NextFederationPlugin/next-fragments.ts index c447fab074..ef3346eedc 100644 --- a/packages/nextjs-mf/src/plugins/NextFederationPlugin/next-fragments.ts +++ b/packages/nextjs-mf/src/plugins/NextFederationPlugin/next-fragments.ts @@ -1,14 +1,16 @@ -import type { container, Compiler } from 'webpack'; -import type { - ModuleFederationPluginOptions, - SharedObject, -} from '@module-federation/utilities'; +import type { Compiler, RuleSetRule } from 'webpack'; +import type { moduleFederationPlugin } from '@module-federation/sdk'; +import type { SharedObject } from '@module-federation/utilities'; import { DEFAULT_SHARE_SCOPE, DEFAULT_SHARE_SCOPE_BROWSER, - getDelegates, } from '../../internal'; -import { hasLoader, injectRuleLoader } from '../../loaders/helpers'; +import { + hasLoader, + injectRuleLoader, + findLoaderForResource, +} from '../../loaders/helpers'; +import path from 'path'; /** * Set up default shared values based on the environment. @@ -25,47 +27,116 @@ export const retrieveDefaultShared = (isServer: boolean): SharedObject => { return DEFAULT_SHARE_SCOPE_BROWSER; }; -/** - * Apply path fixes. - * - * This function applies fixes to the path for certain loaders. It checks if the fix is enabled in the options - * and if the loader is present in the rule. If both conditions are met, it injects the fix loader. - * - * @param {Compiler} compiler - The Webpack compiler instance. - * @param {any} options - The ModuleFederationPluginOptions instance. - */ -export const applyPathFixes = (compiler: Compiler, options: any) => { - //@ts-ignore - compiler.options.module.rules.forEach((rule) => { +export const applyPathFixes = ( + compiler: Compiler, + pluginOptions: moduleFederationPlugin.ModuleFederationPluginOptions, + options: any, +) => { + const match = findLoaderForResource(compiler.options.module.rules, { + path: path.join(compiler.context, '/something/thing.js'), + issuerLayer: undefined, + layer: undefined, + }); + + // Get ruleset from normalModuleFactory + // compiler.hooks.normalModuleFactory.tap('NextFederationPlugin', (nmf) => { + // const ruleSet = nmf.ruleSet; + // return; + // console.log(runtimeModulePath); + // const result = ruleSet.exec({ + // resource: runtimeModulePath, + // realResource: runtimeModulePath, + // resourceQuery: undefined, + // resourceFragment: undefined, + // scheme: getScheme(runtimeModulePath), + // assertions: undefined, + // mimetype: 'text/javascript', + // dependency: 'commonjs', + // descriptionData: undefined, + // issuer: undefined, + // compiler: compiler.name, + // issuerLayer: '' + // }); + // console.log(result); + // debugger; + // }); + + compiler.options.module.rules.forEach((rule: RuleSetRule) => { // next-image-loader fix which adds remote's hostname to the assets url - //@ts-ignore if (options.enableImageLoaderFix && hasLoader(rule, 'next-image-loader')) { - // childCompiler.options.module.parser.javascript?.url = 'relative'; - //@ts-ignore injectRuleLoader(rule, { loader: require.resolve('../../loaders/fixImageLoader'), }); } // url-loader fix for which adds remote's hostname to the assets url - //@ts-ignore if (options.enableUrlLoaderFix && hasLoader(rule, 'url-loader')) { - injectRuleLoader({ + injectRuleLoader(rule, { loader: require.resolve('../../loaders/fixUrlLoader'), }); } - //@ts-ignore - if (rule?.oneOf) { - //@ts-ignore - rule.oneOf.forEach((oneOfRule) => { - if (hasLoader(oneOfRule, 'react-refresh-utils')) { - oneOfRule.exclude = [ - oneOfRule.exclude, - /universe\/packages/, - /core\/packages/, - ].filter((i) => i); + }); + if (match) { + let matchCopy: RuleSetRule; + + if (match.use) { + matchCopy = { ...match }; + + if (Array.isArray(match.use)) { + matchCopy.use = match.use.filter((loader: any) => { + return ( + typeof loader === 'object' && + loader.loader && + !loader.loader.includes('react') + ); + }); + } else if (typeof match.use === 'string') { + if (match.use.includes('react')) { + matchCopy.use = ''; + } else { + matchCopy.use = match.use; + } + } else if (typeof match.use === 'object' && match.use !== null) { + if (match.use.loader && match.use.loader.includes('react')) { + matchCopy.use = {}; + } else { + matchCopy.use = match.use; } + } + } else { + matchCopy = { ...match }; + } + + // Create the first new rule using descriptionData + const descriptionDataRule: RuleSetRule = { + ...matchCopy, + descriptionData: { + name: /^(@module-federation)/, + }, + exclude: undefined, + include: undefined, + }; + + // Create the second new rule using test on regex for /runtimePlugin/ + const testRule: RuleSetRule = { + ...matchCopy, + resourceQuery: /runtimePlugin/, + exclude: undefined, + include: undefined, + }; + + const oneOfRule = compiler.options.module.rules.find( + (rule: RuleSetRule) => { + return rule && typeof rule === 'object' && 'oneOf' in rule; + }, + ) as RuleSetRule; + + if (!oneOfRule) { + compiler.options.module.rules.unshift({ + oneOf: [descriptionDataRule, testRule], }); + } else if (oneOfRule.oneOf) { + oneOfRule.oneOf.unshift(descriptionDataRule, testRule); } - }); + } }; diff --git a/packages/nextjs-mf/src/plugins/container/InvertedContainerPlugin.ts b/packages/nextjs-mf/src/plugins/container/InvertedContainerPlugin.ts index f4ab382e44..2f13607d31 100644 --- a/packages/nextjs-mf/src/plugins/container/InvertedContainerPlugin.ts +++ b/packages/nextjs-mf/src/plugins/container/InvertedContainerPlugin.ts @@ -1,6 +1,5 @@ import type { Compiler } from 'webpack'; import EmbeddedContainerPlugin from './EmbeddedContainerPlugin'; -import { AsyncBoundaryPlugin } from '@module-federation/enhanced'; interface InvertedContainerOptions { container?: string; @@ -21,15 +20,6 @@ class InvertedContainerPlugin { runtime: this.options.runtime, container: this.options.container, }).apply(compiler); - - new AsyncBoundaryPlugin({ - excludeChunk: (chunk) => - chunk.name === this.options.container || - chunk.name === this.options.container + '_partial', - // @ts-ignore - eager: (module) => /\.federation/.test(module?.request || ''), - }).apply(compiler); } } - export default InvertedContainerPlugin; diff --git a/packages/nextjs-mf/src/plugins/container/runtimePlugin.ts b/packages/nextjs-mf/src/plugins/container/runtimePlugin.ts index fd0e7f8ff5..67dd9b5210 100644 --- a/packages/nextjs-mf/src/plugins/container/runtimePlugin.ts +++ b/packages/nextjs-mf/src/plugins/container/runtimePlugin.ts @@ -3,46 +3,65 @@ import { FederationRuntimePlugin } from '@module-federation/runtime/types'; export default function (): FederationRuntimePlugin { return { name: 'next-internal-plugin', - //@ts-ignore - createScript({ url, attrs }) { + createScript: function (args: { + url: string; + attrs?: Record; + }) { + // Updated type + var url = args.url; + var attrs = args.attrs; if (typeof window !== 'undefined') { - const script = document.createElement('script'); + var script = document.createElement('script'); script.src = url; script.async = true; - //@ts-ignore - delete attrs.crossorigin; + delete attrs?.['crossorigin']; - return { script, timeout: 8000 }; + return { script: script, timeout: 8000 }; } return undefined; }, - errorLoadRemote({ id, error, from, origin }) { + errorLoadRemote: function (args: { + id: string; + error: any; + from: string; + origin: any; + }) { + var id = args.id; + var error = args.error; + var from = args.from; console.error(id, 'offline'); - const pg = function () { + var pg = function () { console.error(id, 'offline', error); return null; }; - pg.getInitialProps = function (ctx: any) { + (pg as any).getInitialProps = function (ctx: any) { + // Type assertion to add getInitialProps return {}; }; - let mod; + var mod; if (from === 'build') { - mod = () => ({ - __esModule: true, - default: pg, - getServerSideProps: () => ({ props: {} }), - }); + mod = function () { + return { + __esModule: true, + default: pg, + getServerSideProps: function () { + return { props: {} }; + }, + }; + }; } else { mod = { default: pg, - getServerSideProps: () => ({ props: {} }), + getServerSideProps: function () { + return { props: {} }; + }, }; } return mod; }, - beforeInit(args) { + beforeInit: function (args) { if (!globalThis.usedChunks) globalThis.usedChunks = new Set(); if ( typeof __webpack_runtime_id__ === 'string' && @@ -51,72 +70,73 @@ export default function (): FederationRuntimePlugin { return args; } - // if (__webpack_runtime_id__ && !__webpack_runtime_id__.startsWith('webpack')) return args; - const { moduleCache, name } = args.origin; - const gs = new Function('return globalThis')(); - const attachedRemote = gs[name]; + var moduleCache = args.origin.moduleCache; + var name = args.origin.name; + var gs = new Function('return globalThis')(); + var attachedRemote = gs[name]; if (attachedRemote) { moduleCache.set(name, attachedRemote); } return args; }, - init(args) { + init: function (args: any) { return args; }, - beforeRequest: (args) => { - const { options, id } = args; - const remoteName = id.split('/').shift(); - const remote = options.remotes.find( - (remote) => remote.name === remoteName, - ); + beforeRequest: function (args: any) { + var options = args.options; + var id = args.id; + var remoteName = id.split('/').shift(); + var remote = options.remotes.find(function (remote: any) { + return remote.name === remoteName; + }); if (!remote) return args; - //@ts-ignore - if (remote?.entry?.includes('?t=')) { + if (remote && remote.entry && remote.entry.includes('?t=')) { return args; } - //@ts-ignore - remote.entry = `${remote?.entry}?t=${Date.now()}`; + remote.entry = remote.entry + '?t=' + Date.now(); return args; }, - afterResolve(args) { + afterResolve: function (args: any) { return args; }, - onLoad(args) { - const { exposeModuleFactory, exposeModule, id } = args; - const moduleOrFactory = exposeModuleFactory || exposeModule; + onLoad: function (args: any) { + var exposeModuleFactory = args.exposeModuleFactory; + var exposeModule = args.exposeModule; + var id = args.id; + var moduleOrFactory = exposeModuleFactory || exposeModule; if (!moduleOrFactory) return args; // Ensure moduleOrFactory is defined if (typeof window === 'undefined') { - let exposedModuleExports: any; + var exposedModuleExports: any; try { exposedModuleExports = moduleOrFactory(); } catch (e) { exposedModuleExports = moduleOrFactory; } - const handler: ProxyHandler = { - get(target, prop, receiver) { + var handler: ProxyHandler = { + get: function (target, prop, receiver) { // Check if accessing a static property of the function itself if ( target === exposedModuleExports && typeof exposedModuleExports[prop] === 'function' ) { - return function (this: unknown, ...args: any[]) { + return function (this: unknown) { globalThis.usedChunks.add(id); - return exposedModuleExports[prop].apply(this, args); + return exposedModuleExports[prop].apply(this, arguments); }; } - const originalMethod = target[prop]; + var originalMethod = target[prop]; if (typeof originalMethod === 'function') { - const proxiedFunction = function (this: unknown, ...args: any[]) { + var proxiedFunction = function (this: unknown) { globalThis.usedChunks.add(id); - return originalMethod.apply(this, args); + return originalMethod.apply(this, arguments); }; // Copy all enumerable properties from the original method to the proxied function - Object.keys(originalMethod).forEach((prop) => { + Object.keys(originalMethod).forEach(function (prop) { Object.defineProperty(proxiedFunction, prop, { value: originalMethod[prop], writable: true, @@ -138,8 +158,8 @@ export default function (): FederationRuntimePlugin { exposedModuleExports = new Proxy(exposedModuleExports, handler); // Proxy static properties specifically - const staticProps = Object.getOwnPropertyNames(exposedModuleExports); - staticProps.forEach((prop) => { + var staticProps = Object.getOwnPropertyNames(exposedModuleExports); + staticProps.forEach(function (prop) { if (typeof exposedModuleExports[prop] === 'function') { exposedModuleExports[prop] = new Proxy( exposedModuleExports[prop], @@ -147,7 +167,9 @@ export default function (): FederationRuntimePlugin { ); } }); - return () => exposedModuleExports; + return function () { + return exposedModuleExports; + }; } else { // For objects, just wrap the exported object itself exposedModuleExports = new Proxy(exposedModuleExports, handler); @@ -159,7 +181,7 @@ export default function (): FederationRuntimePlugin { return args; }, - resolveShare(args) { + resolveShare: function (args: any) { if ( args.pkgName !== 'react' && args.pkgName !== 'react-dom' && @@ -167,8 +189,12 @@ export default function (): FederationRuntimePlugin { ) { return args; } - const { shareScopeMap, scope, pkgName, version, GlobalFederation } = args; - const host = GlobalFederation['__INSTANCES__'][0]; + var shareScopeMap = args.shareScopeMap; + var scope = args.scope; + var pkgName = args.pkgName; + var version = args.version; + var GlobalFederation = args.GlobalFederation; + var host = GlobalFederation['__INSTANCES__'][0]; if (!host) { return args; } @@ -176,7 +202,7 @@ export default function (): FederationRuntimePlugin { if (!host.options.shared[pkgName]) { return args; } - + //handle react host next remote, disable resolving when not next host args.resolver = function () { shareScopeMap[scope][pkgName][version] = host.options.shared[pkgName][0]; // replace local share scope manually with desired module @@ -184,7 +210,7 @@ export default function (): FederationRuntimePlugin { }; return args; }, - async beforeLoadShare(args) { + beforeLoadShare: async function (args: any) { return args; }, }; diff --git a/packages/nextjs-mf/utils/index.ts b/packages/nextjs-mf/utils/index.ts index 13af6e110a..f3f49da9bc 100644 --- a/packages/nextjs-mf/utils/index.ts +++ b/packages/nextjs-mf/utils/index.ts @@ -4,8 +4,8 @@ */ export { extractUrlAndGlobal } from '@module-federation/utilities'; import { injectScript as injectLegacy } from '@module-federation/utilities'; -//@ts-ignore -export const injectScript = (args) => { + +export const injectScript = function (args: any): any { console.error( '@module-federation/utilities injectScript is deprecated, use module-federation/runtime {init,loadRemote}', ); @@ -16,7 +16,6 @@ export const injectScript = (args) => { * Flushes chunks from the module federation node utilities. * @module @module-federation/node/utils */ -// @ts-ignore export { flushChunks } from '@module-federation/node/utils'; /** @@ -35,16 +34,15 @@ export type { FlushedChunksProps } from './flushedChunks'; * If the function is called on the server side, it imports the revalidate function from the module federation node utilities and returns the result of calling that function. * @returns {Promise} A promise that resolves with a boolean. */ -export const revalidate = ( +export const revalidate = function ( fetchModule: any = undefined, force: boolean = false, -) => { +): Promise { if (typeof window !== 'undefined') { console.error('revalidate should only be called server-side'); return Promise.resolve(false); } - // @ts-ignore - return import('@module-federation/node/utils').then((utils) => { + return import('@module-federation/node/utils').then(function (utils) { return utils.revalidate(fetchModule, force); }); }; diff --git a/packages/node/src/plugins/ChunkCorrelationPlugin.js b/packages/node/src/plugins/ChunkCorrelationPlugin.js index e3412273ef..29d4a3ccfc 100644 --- a/packages/node/src/plugins/ChunkCorrelationPlugin.js +++ b/packages/node/src/plugins/ChunkCorrelationPlugin.js @@ -413,7 +413,6 @@ class FederationStatsPlugin { ); if (!federationPlugins || federationPlugins.length === 0) { - console.error('No ModuleFederationPlugin(s) found.'); return; } diff --git a/packages/runtime/.swcrc b/packages/runtime/.swcrc index 2b61b980ff..28e88ec1cd 100644 --- a/packages/runtime/.swcrc +++ b/packages/runtime/.swcrc @@ -11,7 +11,7 @@ "legacyDecorator": true }, "keepClassNames": true, - "externalHelpers": false, + "externalHelpers": true, "loose": true }, "module": { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 00726123ea..cb3e9579df 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -34,6 +34,11 @@ "import": "./dist/retry-plugin.esm.js", "require": "./dist/retry-plugin.cjs.js" }, + "./embedded": { + "types": "./dist/embedded.cjs.d.ts", + "import": "./dist/embedded.esm.js", + "require": "./dist/embedded.cjs.js" + }, "./*": "./*" }, "typesVersions": { diff --git a/packages/runtime/project.json b/packages/runtime/project.json index 2e76d95899..ebdcc7904a 100644 --- a/packages/runtime/project.json +++ b/packages/runtime/project.json @@ -13,7 +13,8 @@ "main": "packages/runtime/src/index.ts", "additionalEntryPoints": [ "packages/runtime/src/types.ts", - "packages/runtime/src/helpers.ts" + "packages/runtime/src/helpers.ts", + "packages/runtime/src/embedded.ts" ], "tsConfig": "packages/runtime/tsconfig.lib.json", "assets": [], @@ -21,9 +22,7 @@ "compiler": "swc", "rollupConfig": "packages/runtime/rollup.config.js", "format": ["cjs", "esm"], - "external": ["@module-federation/*"], - "buildableProjectDepsInPackageJsonType": "dependencies", - "updateBuildableProjectDepsInPackageJson": true + "external": ["@module-federation/", "@swc"] }, "dependsOn": [ { diff --git a/packages/runtime/rollup.config.js b/packages/runtime/rollup.config.js index 58d7060c21..9b45240e36 100644 --- a/packages/runtime/rollup.config.js +++ b/packages/runtime/rollup.config.js @@ -9,6 +9,7 @@ module.exports = (rollupConfig, projectOptions) => { types: 'packages/runtime/src/types.ts', helpers: 'packages/runtime/src/helpers.ts', 'retry-plugin': 'packages/runtime/src/plugins/retry-plugin.ts', + embedded: 'packages/runtime/src/embedded.ts', }; const project = projectOptions.project; diff --git a/packages/runtime/src/embedded.ts b/packages/runtime/src/embedded.ts new file mode 100644 index 0000000000..0c628c4f8e --- /dev/null +++ b/packages/runtime/src/embedded.ts @@ -0,0 +1,34 @@ +const { + FederationHost, + registerGlobalPlugins, + getRemoteEntry, + getRemoteInfo, + loadScript, + loadScriptNode, + init, + loadRemote, + loadShare, + loadShareSync, + preloadRemote, + registerRemotes, + registerPlugins, + getInstance, + //@ts-ignore +} = __webpack_require__.federation.runtime; + +export { + FederationHost, + registerGlobalPlugins, + getRemoteEntry, + getRemoteInfo, + loadScript, + loadScriptNode, + init, + loadRemote, + loadShare, + loadShareSync, + preloadRemote, + registerRemotes, + registerPlugins, + getInstance, +}; diff --git a/packages/runtime/src/utils/env.ts b/packages/runtime/src/utils/env.ts index 40cd0d8544..94fa980ebb 100644 --- a/packages/runtime/src/utils/env.ts +++ b/packages/runtime/src/utils/env.ts @@ -1,3 +1,5 @@ +export { isBrowserEnv, isDebugMode } from '@module-federation/sdk'; + export function isDevelopmentMode(): boolean { return true; } diff --git a/packages/sdk/project.json b/packages/sdk/project.json index 5c889f5fa7..2a52a0fb98 100644 --- a/packages/sdk/project.json +++ b/packages/sdk/project.json @@ -12,7 +12,7 @@ "main": "packages/sdk/src/index.ts", "tsConfig": "packages/sdk/tsconfig.lib.json", "assets": [], - "external": ["@module-federation/*"], + "external": ["@module-federation/*", "@swc"], "project": "packages/sdk/package.json", "additionalEntryPoints": ["packages/sdk/src/normalize-webpack-path.ts"], "rollupConfig": "packages/sdk/rollup.config.js", diff --git a/packages/sdk/src/types/plugins/ModuleFederationPlugin.ts b/packages/sdk/src/types/plugins/ModuleFederationPlugin.ts index 70ebceee0e..ee32841b08 100644 --- a/packages/sdk/src/types/plugins/ModuleFederationPlugin.ts +++ b/packages/sdk/src/types/plugins/ModuleFederationPlugin.ts @@ -228,6 +228,7 @@ export interface ModuleFederationPluginOptions { dts?: boolean | PluginDtsOptions; async?: boolean | AsyncBoundaryOptions; virtualRuntimeEntry?: boolean; + embedRuntime?: boolean; } /** * Modules that should be exposed by this container. Property names are used as public paths. diff --git a/packages/typescript/src/plugins/FederatedTypesPlugin.ts b/packages/typescript/src/plugins/FederatedTypesPlugin.ts index 685c3a6031..34b5dc74de 100644 --- a/packages/typescript/src/plugins/FederatedTypesPlugin.ts +++ b/packages/typescript/src/plugins/FederatedTypesPlugin.ts @@ -109,7 +109,6 @@ export class FederatedTypesPlugin { compiler.hooks.watchRun.tap(PLUGIN_NAME, () => { isServe = true; }); - compiler.hooks.beforeCompile.tapAsync( PLUGIN_NAME, async (params: unknown, callback: InnerCallback) => { diff --git a/packages/typescript/tsconfig.json b/packages/typescript/tsconfig.json index bdfa011dff..12b73bad73 100644 --- a/packages/typescript/tsconfig.json +++ b/packages/typescript/tsconfig.json @@ -4,7 +4,6 @@ "module": "commonjs", "forceConsistentCasingInFileNames": true, "strict": true, - "force": true, "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": true, "noImplicitReturns": true, diff --git a/packages/utilities/src/plugins/DelegateModulesPlugin.ts b/packages/utilities/src/plugins/DelegateModulesPlugin.ts index a8ac7a5cef..59bb37408d 100644 --- a/packages/utilities/src/plugins/DelegateModulesPlugin.ts +++ b/packages/utilities/src/plugins/DelegateModulesPlugin.ts @@ -1,4 +1,10 @@ -import type { Compiler, Compilation, Chunk, NormalModule } from 'webpack'; +import type { + Compiler, + Compilation, + Chunk, + NormalModule, + Module, +} from 'webpack'; class DelegateModulesPlugin { options: { debug: boolean; [key: string]: any }; @@ -62,14 +68,13 @@ class DelegateModulesPlugin { } } } - apply(compiler: Compiler): void { compiler.hooks.thisCompilation.tap( 'DelegateModulesPlugin', (compilation: Compilation) => { compilation.hooks.finishModules.tapAsync( 'DelegateModulesPlugin', - (modules, callback) => { + (modules: Iterable, callback: () => void) => { const { remotes } = this.options; const knownDelegates = new Set( remotes @@ -103,7 +108,7 @@ class DelegateModulesPlugin { compilation.hooks.optimizeChunks.tap( 'DelegateModulesPlugin', - (chunks) => { + (chunks: Iterable) => { const { runtime, container } = this.options; const runtimeChunk = this.getChunkByName(chunks, runtime); if (!runtimeChunk || !runtimeChunk.hasRuntime()) { diff --git a/packages/webpack-bundler-runtime/package.json b/packages/webpack-bundler-runtime/package.json index d59aed1653..7fd9b84844 100644 --- a/packages/webpack-bundler-runtime/package.json +++ b/packages/webpack-bundler-runtime/package.json @@ -36,6 +36,10 @@ "import": "./dist/container.esm.js", "require": "./dist/container.cjs.js" }, + "./vendor": { + "import": "./dist/vendored.esm.js", + "require": "./dist/vendored.cjs.js" + }, "./*": "./*" }, "typesVersions": { @@ -49,6 +53,9 @@ } }, "devDependencies": { - "@module-federation/runtime": "workspace:*" + "@module-federation/runtime": "workspace:*", + "@rollup/plugin-swc": "^0.3.1", + "rollup-plugin-cleanup": "^3.2.1", + "rollup": "4.21.0" } } diff --git a/packages/webpack-bundler-runtime/project.json b/packages/webpack-bundler-runtime/project.json index 62e684b30e..9c99d1fd08 100644 --- a/packages/webpack-bundler-runtime/project.json +++ b/packages/webpack-bundler-runtime/project.json @@ -20,7 +20,7 @@ "packages/webpack-bundler-runtime/src/constant.ts", "packages/webpack-bundler-runtime/src/container.ts" ], - "external": ["@module-federation/*"], + "external": ["@module-federation/*", "@swc"], "buildableProjectDepsInPackageJsonType": "dependencies", "updateBuildableProjectDepsInPackageJson": true, "rollupConfig": "packages/webpack-bundler-runtime/rollup.config.js" diff --git a/packages/webpack-bundler-runtime/rollup.config.js b/packages/webpack-bundler-runtime/rollup.config.js index 11a4a6cf68..ed3eebb34f 100644 --- a/packages/webpack-bundler-runtime/rollup.config.js +++ b/packages/webpack-bundler-runtime/rollup.config.js @@ -1,5 +1,8 @@ const copy = require('rollup-plugin-copy'); - +const { rollup } = require('rollup'); +const swc = require('@rollup/plugin-swc'); +const cleanup = require('rollup-plugin-cleanup'); +const fs = require('fs'); module.exports = (rollupConfig, projectOptions) => { rollupConfig.plugins.push( copy({ @@ -12,5 +15,77 @@ module.exports = (rollupConfig, projectOptions) => { }), ); + const currentPlugins = Array.from( + rollupConfig.plugins.filter( + (p) => + p.name !== 'nx-swc' && + p.name !== 'dts-bundle' && + p.name !== 'peer-deps-external', + ), + ); + currentPlugins.push( + swc({ + swc: { + jsc: { + parser: { + syntax: 'typescript', + jsx: false, + dynamicImport: true, + privateMethod: false, + functionBind: false, + exportDefaultFrom: false, + exportNamespaceFrom: false, + decorators: false, + decoratorsBeforeExport: false, + topLevelAwait: false, + importMeta: false, + preserveAllComments: false, + }, + transform: null, + target: rollupConfig.output.format === 'cjs' ? 'es5' : 'es2020', + loose: false, + externalHelpers: true, + // Requires v1.2.50 or upper and requires target to be es2016 or upper. + keepClassNames: false, + minify: { + compress: false, + format: { + comments: false, + }, + }, + }, + env: null, + minify: false, + }, + }), + ); + // Custom plugin to add a new child compile + rollupConfig.plugins.push({ + name: 'child-compile', + buildEnd: async () => { + const childConfig = { + ...rollupConfig, + input: projectOptions.main, + output: { + file: + projectOptions.outputPath + + `/vendored.${rollupConfig.output.format}.js`, + format: rollupConfig.output.format, + }, + plugins: currentPlugins, + external: [/@swc/], + }; + const bundle = await rollup(childConfig); + await bundle.write(childConfig.output); + let content = fs.readFileSync( + projectOptions.outputPath + + `/vendored.${rollupConfig.output.format}.js`, + 'utf-8', + ); + content = content.replace(/\/\*[\s\S]*?\*\//g, ''); + fs.writeFileSync(projectOptions.outputPath + '/vendored.js', content); + }, + }); + return rollupConfig; }; diff --git a/packages/webpack-bundler-runtime/src/index.ts b/packages/webpack-bundler-runtime/src/index.ts index 656367d38b..f206ea7076 100644 --- a/packages/webpack-bundler-runtime/src/index.ts +++ b/packages/webpack-bundler-runtime/src/index.ts @@ -6,7 +6,6 @@ import { initializeSharing } from './initializeSharing'; import { installInitialConsumes } from './installInitialConsumes'; import { attachShareScopeMap } from './attachShareScopeMap'; import { initContainerEntry } from './initContainerEntry'; - export * from './types'; const federation: Federation = { @@ -24,4 +23,5 @@ const federation: Federation = { attachShareScopeMap, bundlerRuntimeOptions: {}, }; + export default federation; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fb3e37faa9..8db785c1ba 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -574,6 +574,9 @@ importers: '@module-federation/nextjs-mf': specifier: workspace:* version: link:../../packages/nextjs-mf + '@module-federation/runtime': + specifier: workspace:* + version: link:../../packages/runtime '@module-federation/utilities': specifier: workspace:* version: link:../../packages/utilities @@ -2212,6 +2215,9 @@ importers: '@types/btoa': specifier: ^1.2.5 version: 1.2.5 + '@types/enhanced-resolve': + specifier: ^5.0.0 + version: 5.0.0 packages/esbuild: dependencies: @@ -2422,6 +2428,9 @@ importers: '@module-federation/utilities': specifier: workspace:* version: link:../utilities + '@module-federation/webpack-bundler-runtime': + specifier: workspace:* + version: link:../webpack-bundler-runtime fast-glob: specifier: ^3.2.11 version: 3.3.2 @@ -2609,6 +2618,16 @@ importers: '@module-federation/sdk': specifier: workspace:* version: link:../sdk + devDependencies: + '@rollup/plugin-swc': + specifier: ^0.3.1 + version: 0.3.1(@swc/core@1.6.13)(rollup@4.21.0) + rollup: + specifier: 4.21.0 + version: 4.21.0 + rollup-plugin-cleanup: + specifier: ^3.2.1 + version: 3.2.1(rollup@4.21.0) webpack: dependencies: @@ -14111,6 +14130,22 @@ packages: magic-string: 0.30.10 rollup: 2.79.1 + /@rollup/plugin-swc@0.3.1(@swc/core@1.6.13)(rollup@4.21.0): + resolution: {integrity: sha512-oqHt6W2J3CoIrdWpbLiRXdRkepEv+qwCgHMnSmh7waPFxaEeO3tocA1xy2p2qoJmk1zygDoxtPeP95z8bsJ+fA==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@swc/core': ^1.3.0 + rollup: ^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@rollup/pluginutils': 5.1.0(rollup@4.21.0) + '@swc/core': 1.6.13(@swc/helpers@0.5.12) + rollup: 4.21.0 + smob: 1.5.0 + dev: true + /@rollup/pluginutils@3.1.0(rollup@2.79.1): resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==} engines: {node: '>= 8.0.0'} @@ -14152,6 +14187,21 @@ packages: picomatch: 2.3.1 rollup: 2.79.1 + /@rollup/pluginutils@5.1.0(rollup@4.21.0): + resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@types/estree': 1.0.5 + estree-walker: 2.0.2 + picomatch: 2.3.1 + rollup: 4.21.0 + dev: true + /@rollup/rollup-android-arm-eabi@4.19.0: resolution: {integrity: sha512-JlPfZ/C7yn5S5p0yKk7uhHTTnFlvTgLetl2VxqE518QgyM7C9bSfFTYvB/Q/ftkq0RIPY4ySxTz+/wKJ/dXC0w==} cpu: [arm] @@ -14159,6 +14209,14 @@ packages: requiresBuild: true optional: true + /@rollup/rollup-android-arm-eabi@4.21.0: + resolution: {integrity: sha512-WTWD8PfoSAJ+qL87lE7votj3syLavxunWhzCnx3XFxFiI/BA/r3X7MUM8dVrH8rb2r4AiO8jJsr3ZjdaftmnfA==} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + /@rollup/rollup-android-arm64@4.19.0: resolution: {integrity: sha512-RDxUSY8D1tWYfn00DDi5myxKgOk6RvWPxhmWexcICt/MEC6yEMr4HNCu1sXXYLw8iAsg0D44NuU+qNq7zVWCrw==} cpu: [arm64] @@ -14166,6 +14224,14 @@ packages: requiresBuild: true optional: true + /@rollup/rollup-android-arm64@4.21.0: + resolution: {integrity: sha512-a1sR2zSK1B4eYkiZu17ZUZhmUQcKjk2/j9Me2IDjk1GHW7LB5Z35LEzj9iJch6gtUfsnvZs1ZNyDW2oZSThrkA==} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + /@rollup/rollup-darwin-arm64@4.19.0: resolution: {integrity: sha512-emvKHL4B15x6nlNTBMtIaC9tLPRpeA5jMvRLXVbl/W9Ie7HhkrE7KQjvgS9uxgatL1HmHWDXk5TTS4IaNJxbAA==} cpu: [arm64] @@ -14173,6 +14239,14 @@ packages: requiresBuild: true optional: true + /@rollup/rollup-darwin-arm64@4.21.0: + resolution: {integrity: sha512-zOnKWLgDld/svhKO5PD9ozmL6roy5OQ5T4ThvdYZLpiOhEGY+dp2NwUmxK0Ld91LrbjrvtNAE0ERBwjqhZTRAA==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + /@rollup/rollup-darwin-x64@4.19.0: resolution: {integrity: sha512-fO28cWA1dC57qCd+D0rfLC4VPbh6EOJXrreBmFLWPGI9dpMlER2YwSPZzSGfq11XgcEpPukPTfEVFtw2q2nYJg==} cpu: [x64] @@ -14180,6 +14254,14 @@ packages: requiresBuild: true optional: true + /@rollup/rollup-darwin-x64@4.21.0: + resolution: {integrity: sha512-7doS8br0xAkg48SKE2QNtMSFPFUlRdw9+votl27MvT46vo44ATBmdZdGysOevNELmZlfd+NEa0UYOA8f01WSrg==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + /@rollup/rollup-linux-arm-gnueabihf@4.19.0: resolution: {integrity: sha512-2Rn36Ubxdv32NUcfm0wB1tgKqkQuft00PtM23VqLuCUR4N5jcNWDoV5iBC9jeGdgS38WK66ElncprqgMUOyomw==} cpu: [arm] @@ -14187,6 +14269,14 @@ packages: requiresBuild: true optional: true + /@rollup/rollup-linux-arm-gnueabihf@4.21.0: + resolution: {integrity: sha512-pWJsfQjNWNGsoCq53KjMtwdJDmh/6NubwQcz52aEwLEuvx08bzcy6tOUuawAOncPnxz/3siRtd8hiQ32G1y8VA==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@rollup/rollup-linux-arm-musleabihf@4.19.0: resolution: {integrity: sha512-gJuzIVdq/X1ZA2bHeCGCISe0VWqCoNT8BvkQ+BfsixXwTOndhtLUpOg0A1Fcx/+eA6ei6rMBzlOz4JzmiDw7JQ==} cpu: [arm] @@ -14194,6 +14284,14 @@ packages: requiresBuild: true optional: true + /@rollup/rollup-linux-arm-musleabihf@4.21.0: + resolution: {integrity: sha512-efRIANsz3UHZrnZXuEvxS9LoCOWMGD1rweciD6uJQIx2myN3a8Im1FafZBzh7zk1RJ6oKcR16dU3UPldaKd83w==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@rollup/rollup-linux-arm64-gnu@4.19.0: resolution: {integrity: sha512-0EkX2HYPkSADo9cfeGFoQ7R0/wTKb7q6DdwI4Yn/ULFE1wuRRCHybxpl2goQrx4c/yzK3I8OlgtBu4xvted0ug==} cpu: [arm64] @@ -14201,6 +14299,14 @@ packages: requiresBuild: true optional: true + /@rollup/rollup-linux-arm64-gnu@4.21.0: + resolution: {integrity: sha512-ZrPhydkTVhyeGTW94WJ8pnl1uroqVHM3j3hjdquwAcWnmivjAwOYjTEAuEDeJvGX7xv3Z9GAvrBkEzCgHq9U1w==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@rollup/rollup-linux-arm64-musl@4.19.0: resolution: {integrity: sha512-GlIQRj9px52ISomIOEUq/IojLZqzkvRpdP3cLgIE1wUWaiU5Takwlzpz002q0Nxxr1y2ZgxC2obWxjr13lvxNQ==} cpu: [arm64] @@ -14208,6 +14314,14 @@ packages: requiresBuild: true optional: true + /@rollup/rollup-linux-arm64-musl@4.21.0: + resolution: {integrity: sha512-cfaupqd+UEFeURmqNP2eEvXqgbSox/LHOyN9/d2pSdV8xTrjdg3NgOFJCtc1vQ/jEke1qD0IejbBfxleBPHnPw==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@rollup/rollup-linux-powerpc64le-gnu@4.19.0: resolution: {integrity: sha512-N6cFJzssruDLUOKfEKeovCKiHcdwVYOT1Hs6dovDQ61+Y9n3Ek4zXvtghPPelt6U0AH4aDGnDLb83uiJMkWYzQ==} cpu: [ppc64] @@ -14215,6 +14329,14 @@ packages: requiresBuild: true optional: true + /@rollup/rollup-linux-powerpc64le-gnu@4.21.0: + resolution: {integrity: sha512-ZKPan1/RvAhrUylwBXC9t7B2hXdpb/ufeu22pG2psV7RN8roOfGurEghw1ySmX/CmDDHNTDDjY3lo9hRlgtaHg==} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@rollup/rollup-linux-riscv64-gnu@4.19.0: resolution: {integrity: sha512-2DnD3mkS2uuam/alF+I7M84koGwvn3ZVD7uG+LEWpyzo/bq8+kKnus2EVCkcvh6PlNB8QPNFOz6fWd5N8o1CYg==} cpu: [riscv64] @@ -14222,6 +14344,14 @@ packages: requiresBuild: true optional: true + /@rollup/rollup-linux-riscv64-gnu@4.21.0: + resolution: {integrity: sha512-H1eRaCwd5E8eS8leiS+o/NqMdljkcb1d6r2h4fKSsCXQilLKArq6WS7XBLDu80Yz+nMqHVFDquwcVrQmGr28rg==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@rollup/rollup-linux-s390x-gnu@4.19.0: resolution: {integrity: sha512-D6pkaF7OpE7lzlTOFCB2m3Ngzu2ykw40Nka9WmKGUOTS3xcIieHe82slQlNq69sVB04ch73thKYIWz/Ian8DUA==} cpu: [s390x] @@ -14229,6 +14359,14 @@ packages: requiresBuild: true optional: true + /@rollup/rollup-linux-s390x-gnu@4.21.0: + resolution: {integrity: sha512-zJ4hA+3b5tu8u7L58CCSI0A9N1vkfwPhWd/puGXwtZlsB5bTkwDNW/+JCU84+3QYmKpLi+XvHdmrlwUwDA6kqw==} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@rollup/rollup-linux-x64-gnu@4.19.0: resolution: {integrity: sha512-HBndjQLP8OsdJNSxpNIN0einbDmRFg9+UQeZV1eiYupIRuZsDEoeGU43NQsS34Pp166DtwQOnpcbV/zQxM+rWA==} cpu: [x64] @@ -14236,6 +14374,14 @@ packages: requiresBuild: true optional: true + /@rollup/rollup-linux-x64-gnu@4.21.0: + resolution: {integrity: sha512-e2hrvElFIh6kW/UNBQK/kzqMNY5mO+67YtEh9OA65RM5IJXYTWiXjX6fjIiPaqOkBthYF1EqgiZ6OXKcQsM0hg==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@rollup/rollup-linux-x64-musl@4.19.0: resolution: {integrity: sha512-HxfbvfCKJe/RMYJJn0a12eiOI9OOtAUF4G6ozrFUK95BNyoJaSiBjIOHjZskTUffUrB84IPKkFG9H9nEvJGW6A==} cpu: [x64] @@ -14243,6 +14389,14 @@ packages: requiresBuild: true optional: true + /@rollup/rollup-linux-x64-musl@4.21.0: + resolution: {integrity: sha512-1vvmgDdUSebVGXWX2lIcgRebqfQSff0hMEkLJyakQ9JQUbLDkEaMsPTLOmyccyC6IJ/l3FZuJbmrBw/u0A0uCQ==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@rollup/rollup-win32-arm64-msvc@4.19.0: resolution: {integrity: sha512-HxDMKIhmcguGTiP5TsLNolwBUK3nGGUEoV/BO9ldUBoMLBssvh4J0X8pf11i1fTV7WShWItB1bKAKjX4RQeYmg==} cpu: [arm64] @@ -14250,6 +14404,14 @@ packages: requiresBuild: true optional: true + /@rollup/rollup-win32-arm64-msvc@4.21.0: + resolution: {integrity: sha512-s5oFkZ/hFcrlAyBTONFY1TWndfyre1wOMwU+6KCpm/iatybvrRgmZVM+vCFwxmC5ZhdlgfE0N4XorsDpi7/4XQ==} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@rollup/rollup-win32-ia32-msvc@4.19.0: resolution: {integrity: sha512-xItlIAZZaiG/u0wooGzRsx11rokP4qyc/79LkAOdznGRAbOFc+SfEdfUOszG1odsHNgwippUJavag/+W/Etc6Q==} cpu: [ia32] @@ -14257,6 +14419,14 @@ packages: requiresBuild: true optional: true + /@rollup/rollup-win32-ia32-msvc@4.21.0: + resolution: {integrity: sha512-G9+TEqRnAA6nbpqyUqgTiopmnfgnMkR3kMukFBDsiyy23LZvUCpiUwjTRx6ezYCjJODXrh52rBR9oXvm+Fp5wg==} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@rollup/rollup-win32-x64-msvc@4.19.0: resolution: {integrity: sha512-xNo5fV5ycvCCKqiZcpB65VMR11NJB+StnxHz20jdqRAktfdfzhgjTiJ2doTDQE/7dqGaV5I7ZGqKpgph6lCIag==} cpu: [x64] @@ -14264,6 +14434,14 @@ packages: requiresBuild: true optional: true + /@rollup/rollup-win32-x64-msvc@4.21.0: + resolution: {integrity: sha512-2jsCDZwtQvRhejHLfZ1JY6w6kEuEtfF9nzYsZxzSlNVKDX+DpsDJ+Rbjkm74nvg2rdx0gwBS+IMdvwJuq3S9pQ==} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@rsbuild/babel-preset@0.3.4(@rsbuild/core@0.3.11)(@swc/helpers@0.5.3): resolution: {integrity: sha512-lGYVxjuf5SmWt10cBu/agYxpXNfFrvgcl7r9pnObWF9bRwsuaI1S+EuigjFeBUVPdNs4OMQy46sQaTpMfp4p0A==} dependencies: @@ -14974,8 +15152,8 @@ packages: dependencies: '@rsbuild/core': 0.6.15 '@rsbuild/shared': 0.6.15(@swc/helpers@0.5.12) - vue-loader: 17.4.2(vue@3.4.34)(webpack@5.93.0) - webpack: 5.93.0(@swc/core@1.6.13)(esbuild@0.23.0) + vue-loader: 17.4.2(vue@3.4.34)(webpack@5.99.1) + webpack: github.com/ScriptedAlchemy/webpack/df61a5c271f36ad32c8c575ff843424e4906378a(@swc/core@1.6.13)(esbuild@0.23.0) transitivePeerDependencies: - '@swc/core' - '@swc/helpers' @@ -15874,7 +16052,7 @@ packages: '@rspack/binding': 1.0.0-alpha.3 '@rspack/lite-tapable': 1.0.0-alpha.3 '@swc/helpers': 0.5.11 - caniuse-lite: 1.0.30001643 + caniuse-lite: 1.0.30001649 dev: false /@rspack/core@1.0.0-alpha.5(@swc/helpers@0.5.11): @@ -19153,6 +19331,13 @@ packages: resolution: {integrity: sha512-cFq+fO/isvhvmuP/+Sl4K4jtU6E23DoivtbO4r50e3odaxAiVdbfSYRDdJ4gCdxx+3aRjhphS5ZMwIH4hFy/Cw==} dev: true + /@types/enhanced-resolve@5.0.0: + resolution: {integrity: sha512-jrgQjVFFHk7lK+Dyv8Lb5agHaCzL9OQdT9aciA61fxs6KI89u5yfEM7qubHq8DdN3UnTcho+S3Lzyr5mfdLUGg==} + deprecated: This is a stub types definition. enhanced-resolve provides its own type definitions, so you do not need this installed. + dependencies: + enhanced-resolve: 5.17.1 + dev: true + /@types/escodegen@0.0.6: resolution: {integrity: sha512-AjwI4MvWx3HAOaZqYsjKWyEObT9lcVV0Y0V8nXo6cXzN8ZiMxVhf6F3d/UNvXVGKrEzL/Dluc5p+y9GkzlTWig==} dev: true @@ -22667,13 +22852,14 @@ packages: electron-to-chromium: 1.5.1 node-releases: 2.0.18 update-browserslist-db: 1.1.0(browserslist@4.23.1) + dev: true /browserslist@4.23.2: resolution: {integrity: sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001643 + caniuse-lite: 1.0.30001649 electron-to-chromium: 1.5.1 node-releases: 2.0.18 update-browserslist-db: 1.1.0(browserslist@4.23.2) @@ -22928,7 +23114,7 @@ packages: /caniuse-api@3.0.0: resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} dependencies: - browserslist: 4.23.1 + browserslist: 4.23.2 caniuse-lite: 1.0.30001649 lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 @@ -25613,6 +25799,7 @@ packages: /errno@0.1.8: resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==} hasBin: true + requiresBuild: true dependencies: prr: 1.0.1 dev: true @@ -30955,6 +31142,15 @@ packages: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} + /js-cleanup@1.2.0: + resolution: {integrity: sha512-JeDD0yiiSt80fXzAVa/crrS0JDPQljyBG/RpOtaSbyDq03VHa9szJWMaWOYU/bcTn412uMN2MxApXq8v79cUiQ==} + engines: {node: ^10.14.2 || >=12.0.0} + dependencies: + magic-string: 0.25.9 + perf-regexes: 1.0.1 + skip-regex: 1.0.2 + dev: true + /js-cookie@2.2.1: resolution: {integrity: sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==} dev: false @@ -35047,6 +35243,11 @@ packages: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} dev: true + /perf-regexes@1.0.1: + resolution: {integrity: sha512-L7MXxUDtqr4PUaLFCDCXBfGV/6KLIuSEccizDI7JxT+c9x1G1v04BQ4+4oag84SHaCdrBgQAIs/Cqn+flwFPng==} + engines: {node: '>=6.14'} + dev: true + /performance-now@2.1.0: resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} dev: true @@ -39881,7 +40082,7 @@ packages: /reduce-configs@1.0.0: resolution: {integrity: sha512-/JCYSgL/QeXXsq0Lv/7kOZfqvof7vyzHWfyNQPt3c6vc73mU4WRyT8RJ6ZH5Ci08vUOqXwk7jkZy6BycHTDD9w==} dependencies: - browserslist: 4.23.1 + browserslist: 4.23.2 /reduce-flatten@2.0.0: resolution: {integrity: sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==} @@ -40419,6 +40620,17 @@ packages: inherits: 2.0.4 dev: true + /rollup-plugin-cleanup@3.2.1(rollup@4.21.0): + resolution: {integrity: sha512-zuv8EhoO3TpnrU8MX8W7YxSbO4gmOR0ny06Lm3nkFfq0IVKdBUtHwhVzY1OAJyNCIAdLiyPnOrU0KnO0Fri1GQ==} + engines: {node: ^10.14.2 || >=12.0.0} + peerDependencies: + rollup: '>=2.0' + dependencies: + js-cleanup: 1.2.0 + rollup: 4.21.0 + rollup-pluginutils: 2.8.2 + dev: true + /rollup-plugin-copy@3.5.0: resolution: {integrity: sha512-wI8D5dvYovRMx/YYKtUNt3Yxaw4ORC9xo6Gt9t22kveWz1enG9QrhVlagzwrxSC455xD1dHMKhIJkbsQ7d48BA==} engines: {node: '>=8.3'} @@ -40533,6 +40745,32 @@ packages: '@rollup/rollup-win32-x64-msvc': 4.19.0 fsevents: 2.3.3 + /rollup@4.21.0: + resolution: {integrity: sha512-vo+S/lfA2lMS7rZ2Qoubi6I5hwZwzXeUIctILZLbHI+laNtvhhOIon2S1JksA5UEDQ7l3vberd0fxK44lTYjbQ==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + dependencies: + '@types/estree': 1.0.5 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.21.0 + '@rollup/rollup-android-arm64': 4.21.0 + '@rollup/rollup-darwin-arm64': 4.21.0 + '@rollup/rollup-darwin-x64': 4.21.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.21.0 + '@rollup/rollup-linux-arm-musleabihf': 4.21.0 + '@rollup/rollup-linux-arm64-gnu': 4.21.0 + '@rollup/rollup-linux-arm64-musl': 4.21.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.21.0 + '@rollup/rollup-linux-riscv64-gnu': 4.21.0 + '@rollup/rollup-linux-s390x-gnu': 4.21.0 + '@rollup/rollup-linux-x64-gnu': 4.21.0 + '@rollup/rollup-linux-x64-musl': 4.21.0 + '@rollup/rollup-win32-arm64-msvc': 4.21.0 + '@rollup/rollup-win32-ia32-msvc': 4.21.0 + '@rollup/rollup-win32-x64-msvc': 4.21.0 + fsevents: 2.3.3 + dev: true + /rrweb-cssom@0.6.0: resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==} dev: true @@ -41405,6 +41643,11 @@ packages: unicode-emoji-modifier-base: 1.0.0 dev: true + /skip-regex@1.0.2: + resolution: {integrity: sha512-pEjMUbwJ5Pl/6Vn6FsamXHXItJXSRftcibixDmNCWbWhic0hzHrwkMZo0IZ7fMRH9KxcWDFSkzhccB4285PutA==} + engines: {node: '>=4.2'} + dev: true + /slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -41445,6 +41688,10 @@ packages: is-fullwidth-code-point: 4.0.0 dev: true + /smob@1.5.0: + resolution: {integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==} + dev: true + /snake-case@3.0.4: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} dependencies: @@ -42853,6 +43100,32 @@ packages: terser: 5.31.3 webpack: 5.93.0(@swc/core@1.6.13)(esbuild@0.23.0) + /terser-webpack-plugin@5.3.10(@swc/core@1.6.13)(esbuild@0.23.0)(webpack@5.99.1): + resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@swc/core': '*' + esbuild: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@swc/core': + optional: true + esbuild: + optional: true + uglify-js: + optional: true + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + '@swc/core': 1.6.13(@swc/helpers@0.5.12) + esbuild: 0.23.0 + jest-worker: 27.5.1 + schema-utils: 3.3.0 + serialize-javascript: 6.0.2 + terser: 5.31.3 + webpack: github.com/ScriptedAlchemy/webpack/df61a5c271f36ad32c8c575ff843424e4906378a(@swc/core@1.6.13)(esbuild@0.23.0) + dev: true + /terser-webpack-plugin@5.3.9(@swc/core@1.6.13)(esbuild@0.17.19)(webpack@5.93.0): resolution: {integrity: sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==} engines: {node: '>= 10.13.0'} @@ -44216,6 +44489,7 @@ packages: browserslist: 4.23.1 escalade: 3.1.2 picocolors: 1.0.1 + dev: true /update-browserslist-db@1.1.0(browserslist@4.23.2): resolution: {integrity: sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==} @@ -44952,7 +45226,7 @@ packages: - supports-color dev: true - /vue-loader@17.4.2(vue@3.4.34)(webpack@5.93.0): + /vue-loader@17.4.2(vue@3.4.34)(webpack@5.99.1): resolution: {integrity: sha512-yTKOA4R/VN4jqjw4y5HrynFL8AK0Z3/Jt7eOJXEitsm0GMRHDBjCfCiuTiLP7OESvsZYo2pATCWhDqxC5ZrM6w==} peerDependencies: '@vue/compiler-sfc': '*' @@ -44968,7 +45242,7 @@ packages: hash-sum: 2.0.0 vue: 3.4.34(typescript@5.5.2) watchpack: 2.4.1 - webpack: 5.93.0(@swc/core@1.6.13)(esbuild@0.23.0) + webpack: github.com/ScriptedAlchemy/webpack/df61a5c271f36ad32c8c575ff843424e4906378a(@swc/core@1.6.13)(esbuild@0.23.0) dev: true /vue-router@4.3.2(vue@3.4.34): @@ -46153,3 +46427,47 @@ packages: /zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + + github.com/ScriptedAlchemy/webpack/df61a5c271f36ad32c8c575ff843424e4906378a(@swc/core@1.6.13)(esbuild@0.23.0): + resolution: {tarball: https://codeload.github.com/ScriptedAlchemy/webpack/tar.gz/df61a5c271f36ad32c8c575ff843424e4906378a} + id: github.com/ScriptedAlchemy/webpack/df61a5c271f36ad32c8c575ff843424e4906378a + name: webpack + version: 5.99.1 + engines: {node: '>=10.13.0'} + hasBin: true + requiresBuild: true + peerDependencies: + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.5 + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/wasm-edit': 1.12.1 + '@webassemblyjs/wasm-parser': 1.12.1 + acorn: 8.12.1 + acorn-import-attributes: 1.9.5(acorn@8.12.1) + browserslist: 4.23.2 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.17.1 + es-module-lexer: 1.5.4 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.0 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 3.3.0 + tapable: 2.2.1 + terser-webpack-plugin: 5.3.10(@swc/core@1.6.13)(esbuild@0.23.0)(webpack@5.99.1) + watchpack: 2.4.1 + webpack-sources: 3.2.3 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + dev: true diff --git a/webpack/lib/NormalModule.d.ts b/webpack/lib/NormalModule.d.ts index e6cb8f59b9..c709f78f33 100644 --- a/webpack/lib/NormalModule.d.ts +++ b/webpack/lib/NormalModule.d.ts @@ -36,12 +36,14 @@ declare class NormalModule extends Module { rawRequest: string; /** @type {boolean} */ binary: boolean; - /** @type {Parser} */ - parser: Parser; - parserOptions: Record; - /** @type {Generator} */ - generator: Generator; - generatorOptions: Record; + /** @type {undefined | Parser} */ + parser: undefined | Parser; + /** @type {undefined | ParserOptions} */ + parserOptions: undefined | ParserOptions; + /** @type {undefined | Generator} */ + generator: undefined | Generator; + /** @type {undefined | GeneratorOptions} */ + generatorOptions: undefined | GeneratorOptions; /** @type {string} */ resource: string; resourceResolveData: Record; @@ -49,13 +51,22 @@ declare class NormalModule extends Module { matchResource: string | undefined; /** @type {LoaderItem[]} */ loaders: LoaderItem[]; - /** @type {(WebpackError | null)=} */ - error: (WebpackError | null) | undefined; - /** @private @type {Source=} */ + /** @type {WebpackError | null} */ + error: WebpackError | null; + /** + * @private + * @type {Source | null} + */ private _source; - /** @private @type {Map | undefined} **/ + /** + * @private + * @type {Map | undefined} + */ private _sourceSizes; - /** @private @type {Set} */ + /** + * @private + * @type {undefined | SourceTypes} + */ private _sourceTypes; _lastSuccessfulBuildMeta: {}; _forceBuild: boolean; @@ -64,52 +75,60 @@ declare class NormalModule extends Module { _addedSideEffectsBailout: WeakSet | undefined; /** @type {Map} */ _codeGeneratorData: Map; - context: any; - restoreFromUnsafeCache(unsafeCacheData: any, normalModuleFactory: any): void; + /** + * restore unsafe cache data + * @param {NormalModuleUnsafeCacheData} unsafeCacheData data from getUnsafeCacheData + * @param {NormalModuleFactory} normalModuleFactory the normal module factory handling the unsafe caching + */ + restoreFromUnsafeCache( + unsafeCacheData: NormalModuleUnsafeCacheData, + normalModuleFactory: NormalModuleFactory, + ): void; /** * @param {string} context the compilation context * @param {string} name the asset name - * @param {string} content the content - * @param {string | TODO} sourceMap an optional source map - * @param {Object=} associatedObjectForCache object for caching + * @param {string | Buffer} content the content + * @param {(string | SourceMap)=} sourceMap an optional source map + * @param {object=} associatedObjectForCache object for caching * @returns {Source} the created source */ createSourceForAsset( context: string, name: string, - content: string, - sourceMap: string | TODO, - associatedObjectForCache?: any | undefined, - ): any; + content: string | Buffer, + sourceMap?: (string | SourceMap) | undefined, + associatedObjectForCache?: object | undefined, + ): Source; /** + * @private + * @template T * @param {ResolverWithOptions} resolver a resolver * @param {WebpackOptions} options webpack options * @param {Compilation} compilation the compilation * @param {InputFileSystem} fs file system from reading * @param {NormalModuleCompilationHooks} hooks the hooks - * @returns {NormalModuleLoaderContext} loader context + * @returns {import("../declarations/LoaderContext").NormalModuleLoaderContext} loader context */ - _createLoaderContext( - resolver: ResolverWithOptions, - options: WebpackOptions, - compilation: Compilation, - fs: InputFileSystem, - hooks: NormalModuleCompilationHooks, - ): import('../declarations/LoaderContext').NormalModuleLoaderContext; - getCurrentLoader(loaderContext: any, index?: any): LoaderItem; + private _createLoaderContext; + /** + * @param {TODO} loaderContext loader context + * @param {number} index index + * @returns {LoaderItem | null} loader + */ + getCurrentLoader(loaderContext: TODO, index?: number): LoaderItem | null; /** * @param {string} context the compilation context * @param {string | Buffer} content the content - * @param {string | TODO} sourceMap an optional source map - * @param {Object=} associatedObjectForCache object for caching + * @param {(string | SourceMapSource | null)=} sourceMap an optional source map + * @param {object=} associatedObjectForCache object for caching * @returns {Source} the created source */ createSource( context: string, content: string | Buffer, - sourceMap: string | TODO, - associatedObjectForCache?: any | undefined, - ): any; + sourceMap?: (string | SourceMapSource | null) | undefined, + associatedObjectForCache?: object | undefined, + ): Source; /** * @param {WebpackOptions} options webpack options * @param {Compilation} compilation the compilation @@ -133,9 +152,23 @@ declare class NormalModule extends Module { * @returns {void} */ markModuleAsErrored(error: WebpackError): void; - applyNoParseRule(rule: any, content: any): any; - shouldPreventParsing(noParseRule: any, request: any): any; - _initBuildHash(compilation: any): void; + /** + * @param {TODO} rule rule + * @param {string} content content + * @returns {boolean} result + */ + applyNoParseRule(rule: TODO, content: string): boolean; + /** + * @param {TODO} noParseRule no parse rule + * @param {string} request request + * @returns {boolean} check if module should not be parsed, returns "true" if the module should !not! be parsed, returns "false" if the module !must! be parsed + */ + shouldPreventParsing(noParseRule: TODO, request: string): boolean; + /** + * @param {Compilation} compilation compilation + * @private + */ + private _initBuildHash; /** * @param {Hash} hash the hash used to track dependencies * @param {UpdateHashContext} context context @@ -146,7 +179,6 @@ declare class NormalModule extends Module { declare namespace NormalModule { export { Source, - NormalModuleLoaderContext, Mode, ResolveOptions, WebpackOptions, @@ -155,17 +187,23 @@ declare namespace NormalModule { UpdateHashContext, DependencyTemplates, Generator, + BuildInfo, + BuildMeta, CodeGenerationContext, CodeGenerationResult, ConcatenationBailoutReasonContext, + KnownBuildInfo, LibIdentOptions, NeedBuildContext, + SourceTypes, + UnsafeCacheData, ModuleGraph, ConnectionState, JavaScriptModuleTypes, NormalModuleFactory, Parser, RequestShortener, + ResolveContext, ResolverWithOptions, RuntimeTemplate, WebpackLogger, @@ -174,6 +212,13 @@ declare namespace NormalModule { Hash, InputFileSystem, RuntimeSpec, + Algorithm, + FakeHook, + ParserOptions, + GeneratorOptions, + NormalModuleUnsafeCacheData, + LoaderContext, + NormalModuleLoaderContext, SourceMap, LoaderItem, NormalModuleCompilationHooks, @@ -181,34 +226,96 @@ declare namespace NormalModule { }; } import Module = require('./Module'); -type Parser = import('./Parser'); +import WebpackError = require('./WebpackError'); +import { SourceMapSource } from 'webpack-sources'; +import Compilation = require('./Compilation'); +type Source = import('webpack-sources').Source; +type Mode = import('../declarations/WebpackOptions').Mode; +type ResolveOptions = import('../declarations/WebpackOptions').ResolveOptions; +type WebpackOptions = + import('../declarations/WebpackOptions').WebpackOptionsNormalized; +type ChunkGraph = import('./ChunkGraph'); +type Compiler = import('./Compiler'); +type UpdateHashContext = import('./Dependency').UpdateHashContext; +type DependencyTemplates = import('./DependencyTemplates'); type Generator = import('./Generator'); +type BuildInfo = import('./Module').BuildInfo; +type BuildMeta = import('./Module').BuildMeta; +type CodeGenerationContext = import('./Module').CodeGenerationContext; +type CodeGenerationResult = import('./Module').CodeGenerationResult; +type ConcatenationBailoutReasonContext = + import('./Module').ConcatenationBailoutReasonContext; +type KnownBuildInfo = import('./Module').KnownBuildInfo; +type LibIdentOptions = import('./Module').LibIdentOptions; +type NeedBuildContext = import('./Module').NeedBuildContext; +type SourceTypes = import('./Module').SourceTypes; +type UnsafeCacheData = import('./Module').UnsafeCacheData; +type ModuleGraph = import('./ModuleGraph'); +type ConnectionState = import('./ModuleGraphConnection').ConnectionState; +type JavaScriptModuleTypes = + import('./ModuleTypeConstants').JavaScriptModuleTypes; +type NormalModuleFactory = import('./NormalModuleFactory'); +type Parser = import('./Parser'); +type RequestShortener = import('./RequestShortener'); +type ResolveContext = import('./ResolverFactory').ResolveContext; +type ResolverWithOptions = import('./ResolverFactory').ResolverWithOptions; +type RuntimeTemplate = import('./RuntimeTemplate'); +type WebpackLogger = import('./logging/Logger').Logger; +type ObjectDeserializerContext = + import('./serialization/ObjectMiddleware').ObjectDeserializerContext; +type ObjectSerializerContext = + import('./serialization/ObjectMiddleware').ObjectSerializerContext; +type Hash = import('./util/Hash'); +type InputFileSystem = import('./util/fs').InputFileSystem; +type RuntimeSpec = import('./util/runtime').RuntimeSpec; +type Algorithm = import('./util/createHash').Algorithm; +type FakeHook = import('./util/deprecation').FakeHook; +type ParserOptions = { + [k: string]: any; +}; +type GeneratorOptions = { + [k: string]: any; +}; +type NormalModuleUnsafeCacheData = UnsafeCacheData & { + parser: undefined | Parser; + parserOptions: undefined | ParserOptions; + generator: undefined | Generator; + generatorOptions: undefined | GeneratorOptions; +}; +type LoaderContext = + import('../declarations/LoaderContext').LoaderContext; +type NormalModuleLoaderContext = + import('../declarations/LoaderContext').NormalModuleLoaderContext; +type SourceMap = { + version: number; + sources: string[]; + mappings: string; + file?: string | undefined; + sourceRoot?: string | undefined; + sourcesContent?: string[] | undefined; + names?: string[] | undefined; +}; type LoaderItem = { loader: string; options: any; ident: string | null; type: string | null; }; -import WebpackError = require('./WebpackError'); -type ModuleGraph = import('./ModuleGraph'); -type ResolverWithOptions = import('./ResolverFactory').ResolverWithOptions; -type WebpackOptions = - import('../declarations/WebpackOptions').WebpackOptionsNormalized; -import Compilation = require('./Compilation'); -type InputFileSystem = import('./util/fs').InputFileSystem; type NormalModuleCompilationHooks = { - loader: SyncHook<[object, NormalModule]>; - beforeLoaders: SyncHook<[LoaderItem[], NormalModule, object]>; + loader: SyncHook<[LoaderContext, NormalModule]>; + beforeLoaders: SyncHook<[LoaderItem[], NormalModule, LoaderContext]>; beforeParse: SyncHook<[NormalModule]>; beforeSnapshot: SyncHook<[NormalModule]>; readResourceForScheme: HookMap< - AsyncSeriesBailHook<[string, NormalModule], string | Buffer> + FakeHook< + AsyncSeriesBailHook<[string, NormalModule], string | Buffer | null> + > + >; + readResource: HookMap< + AsyncSeriesBailHook<[LoaderContext], string | Buffer | null> >; - readResource: HookMap>; needBuild: AsyncSeriesBailHook<[NormalModule, NeedBuildContext], boolean>; }; -type Hash = import('./util/Hash'); -type UpdateHashContext = import('./Dependency').UpdateHashContext; type NormalModuleCreateData = { /** * an optional layer in which the module is @@ -257,7 +364,7 @@ type NormalModuleCreateData = { /** * the options of the parser used */ - parserOptions?: Record | undefined; + parserOptions?: ParserOptions | undefined; /** * the generator used */ @@ -265,47 +372,12 @@ type NormalModuleCreateData = { /** * the options of the generator used */ - generatorOptions?: Record | undefined; + generatorOptions?: GeneratorOptions | undefined; /** * options used for resolving requests from this module */ resolveOptions?: ResolveOptions | undefined; }; -type Source = any; -type NormalModuleLoaderContext = - import('../declarations/LoaderContext').NormalModuleLoaderContext; -type Mode = import('../declarations/WebpackOptions').Mode; -type ResolveOptions = import('../declarations/WebpackOptions').ResolveOptions; -type ChunkGraph = import('./ChunkGraph'); -type Compiler = import('./Compiler'); -type DependencyTemplates = import('./DependencyTemplates'); -type CodeGenerationContext = import('./Module').CodeGenerationContext; -type CodeGenerationResult = import('./Module').CodeGenerationResult; -type ConcatenationBailoutReasonContext = - import('./Module').ConcatenationBailoutReasonContext; -type LibIdentOptions = import('./Module').LibIdentOptions; -type NeedBuildContext = import('./Module').NeedBuildContext; -type ConnectionState = import('./ModuleGraphConnection').ConnectionState; -type JavaScriptModuleTypes = - import('./ModuleTypeConstants').JavaScriptModuleTypes; -type NormalModuleFactory = import('./NormalModuleFactory'); -type RequestShortener = import('./RequestShortener'); -type RuntimeTemplate = import('./RuntimeTemplate'); -type WebpackLogger = import('./logging/Logger').Logger; -type ObjectDeserializerContext = - import('./serialization/ObjectMiddleware').ObjectDeserializerContext; -type ObjectSerializerContext = - import('./serialization/ObjectMiddleware').ObjectSerializerContext; -type RuntimeSpec = import('./util/runtime').RuntimeSpec; -type SourceMap = { - version: number; - sources: string[]; - mappings: string; - file?: string | undefined; - sourceRoot?: string | undefined; - sourcesContent?: string[] | undefined; - names?: string[] | undefined; -}; import { SyncHook } from 'tapable'; import { HookMap } from 'tapable'; import { AsyncSeriesBailHook } from 'tapable'; diff --git a/webpack/types.d.ts b/webpack/types.d.ts index 986bf72e69..3e64eed797 100644 --- a/webpack/types.d.ts +++ b/webpack/types.d.ts @@ -5,6 +5,7 @@ */ import Compilation from './lib/Compilation'; import Stats from './lib/Stats'; +import NormalModuleReplacementPlugin from './lib/NormalModuleReplacementPlugin'; import MultiCompiler from './lib/MultiCompiler'; import Parser from './lib/Parser'; import ModuleDependency from './lib/dependencies/ModuleDependency'; @@ -3178,24 +3179,6 @@ declare interface NormalModuleLoaderContext { _compiler?: Compiler; } -declare class NormalModuleReplacementPlugin { - /** - * Create an instance of the plugin - */ - constructor( - resourceRegExp: RegExp, - newResource: string | ((arg0: ResolveData) => void), - ); - - resourceRegExp: RegExp; - newResource: string | ((arg0: ResolveData) => void); - - /** - * Apply the plugin - */ - apply(compiler: Compiler): void; -} - declare class NullDependency extends Dependency { constructor();