diff --git a/.changeset/ai-hungry-bear.md b/.changeset/ai-hungry-bear.md new file mode 100644 index 00000000000..444a92082bf --- /dev/null +++ b/.changeset/ai-hungry-bear.md @@ -0,0 +1,9 @@ +"@module-federation/enhanced": minor +--- + +Enhancements to layer handling in module federation tests and configuration. + +- Improved handling of `shareKey` for layers within `ConsumeSharedPlugin` and `ProvideSharedPlugin`. + - Conditionally prepend the `shareKey` with the `layer` if applicable. +- Introduced new layer configurations to support more nuanced federation scenarios that consider multiple layers of dependency. +``` diff --git a/.changeset/ai-sleepy-fox.md b/.changeset/ai-sleepy-fox.md new file mode 100644 index 00000000000..36d84f71503 --- /dev/null +++ b/.changeset/ai-sleepy-fox.md @@ -0,0 +1,9 @@ +--- +"@module-federation/enhanced": patch +--- + +Refactored module sharing configuration handling. + +- Simplified plugin schema for better maintainability +- Improved layer-based module sharing test coverage +- Removed redundant plugin exports diff --git a/.changeset/ai-sleepy-tiger.md b/.changeset/ai-sleepy-tiger.md new file mode 100644 index 00000000000..6c11ece998e --- /dev/null +++ b/.changeset/ai-sleepy-tiger.md @@ -0,0 +1,6 @@ +--- +"@module-federation/runtime": minor +--- + +- Added a new property 'layer' of type string or null to SharedConfig. +``` diff --git a/.changeset/brown-badgers-fetch.md b/.changeset/brown-badgers-fetch.md new file mode 100644 index 00000000000..00d28f1f096 --- /dev/null +++ b/.changeset/brown-badgers-fetch.md @@ -0,0 +1,5 @@ +--- +'@module-federation/enhanced': minor +--- + +support request option on ConsumeSharePlugin. Allows matching requests like the object key of shared does diff --git a/.changeset/shy-snails-battle.md b/.changeset/shy-snails-battle.md new file mode 100644 index 00000000000..8d4fb5ec2f1 --- /dev/null +++ b/.changeset/shy-snails-battle.md @@ -0,0 +1,5 @@ +--- +'@module-federation/enhanced': minor +--- + +Layer support for Provide Share Plugin diff --git a/apps/next-app-router/next-app-router-4001/app/layout.tsx b/apps/next-app-router/next-app-router-4001/app/layout.tsx index ba6662a2c6a..0a34ae994e1 100644 --- a/apps/next-app-router/next-app-router-4001/app/layout.tsx +++ b/apps/next-app-router/next-app-router-4001/app/layout.tsx @@ -1,8 +1,8 @@ import '#/styles/globals.css'; -import { AddressBar } from '#/ui/address-bar'; -import Byline from '#/ui/byline'; +// import { AddressBar } from '#/ui/address-bar'; +// import Byline from '#/ui/byline'; import { GlobalNav } from '#/ui/global-nav'; -import { Metadata } from 'next'; +// import { Metadata } from 'next'; export const metadata: Metadata = { title: { @@ -36,15 +36,13 @@ export default function RootLayout({
-
- -
+
{/**/}
{children}
- + {/**/}
diff --git a/apps/next-app-router/next-app-router-4001/next.config.js b/apps/next-app-router/next-app-router-4001/next.config.js index 714745c649c..7c5ff89e450 100644 --- a/apps/next-app-router/next-app-router-4001/next.config.js +++ b/apps/next-app-router/next-app-router-4001/next.config.js @@ -23,10 +23,10 @@ const nextConfig = { // Core UI Components './Button': './ui/button', // './Header': isServer ? './ui/header?rsc' : './ui/header?shared', - './Footer': './ui/footer', + // './Footer': './ui/footer', // './GlobalNav(rsc)': isServer ? './ui/global-nav?rsc' : './ui/global-nav', // './GlobalNav(ssr)': isServer ? './ui/global-nav?ssr' : './ui/global-nav', - './GlobalNav': './ui/global-nav', + // './GlobalNav': './ui/global-nav', // // // Product Related Components // './ProductCard': './ui/product-card', diff --git a/apps/next-app-router/next-app-router-4001/project.json b/apps/next-app-router/next-app-router-4001/project.json index cba17d562e1..cea74e5f6f6 100644 --- a/apps/next-app-router/next-app-router-4001/project.json +++ b/apps/next-app-router/next-app-router-4001/project.json @@ -1,7 +1,7 @@ { "name": "next-app-router-4001", "$schema": "../../node_modules/nx/schemas/project-schema.json", - "sourceRoot": "apps/next-app-router-4001", + "sourceRoot": "apps/next-app-router/next-app-router-4001", "projectType": "application", "tags": [], "targets": { @@ -27,8 +27,8 @@ "serve": { "executor": "nx:run-commands", "options": { - "command": "pnpm dev", - "cwd": "apps/next-app-router-4001" + "command": "npm run dev", + "cwd": "apps/next-app-router/next-app-router-4001" }, "dependsOn": [ { diff --git a/packages/nextjs-mf/src/internal.ts b/packages/nextjs-mf/src/internal.ts index f25ced295bb..7ae4d9df54d 100644 --- a/packages/nextjs-mf/src/internal.ts +++ b/packages/nextjs-mf/src/internal.ts @@ -11,7 +11,7 @@ type ExtendedSharedConfig = sharePlugin.SharedConfig & { shareKey?: string; }; -const WEBPACK_LAYERS_NAMES = { +export const WEBPACK_LAYERS_NAMES = { /** * The layer for the shared code between the client and server bundles. */ @@ -58,7 +58,7 @@ const createSharedConfig = ( ) => { return layers.reduce( (acc, layer) => { - const key = layer ? `${name}-${layer}` : name; + const key = layer ? `${layer}-${name}` : name; acc[key] = { singleton: true, requiredVersion: false, @@ -67,6 +67,7 @@ const createSharedConfig = ( request: options.request ?? name, layer, issuerLayer: layer, + shareScope: layer ? [layer] : undefined, }; return acc; }, @@ -85,11 +86,30 @@ const navigationLayers = [ WEBPACK_LAYERS_NAMES.serverSideRendering, ]; -const reactShares = createSharedConfig('react', defaultLayers); +const reactShares = createSharedConfig('react', defaultLayers, { + request: 'react', + import: undefined, +}); const reactDomShares = createSharedConfig('react', defaultLayers, { request: 'react-dom', }); -const jsxRuntimeShares = createSharedConfig('react/', navigationLayers, { +const jsxRuntimeShares = createSharedConfig( + 'react/jsx-runtime', + navigationLayers, + { + request: 'react/jsx-runtime', + import: undefined, + }, +); +const jsxDevRuntimeShares = createSharedConfig( + 'react/jsx-dev-runtime', + navigationLayers, + { + request: 'react/jsx-dev-runtime', + import: undefined, + }, +); +const prefixReact = createSharedConfig('react/', defaultLayers, { request: 'react/', import: undefined, }); @@ -110,11 +130,83 @@ const nextNavigationShares = createSharedConfig( * @property {string} key.layer - The webpack layer this shared module belongs to. * @property {string|string[]} key.issuerLayer - The webpack layer that can import this shared module. */ -export const DEFAULT_SHARE_SCOPE: moduleFederationPlugin.SharedObject = { - // ...reactShares, - // ...reactDomShares, - // ...nextNavigationShares, - // ...jsxRuntimeShares, +// Group React related packages +const reactGroup = { + react: { + singleton: true, + import: false, + }, + 'ssr-react': { + requiredVersion: false, + request: 'react', + import: 'next/dist/server/route-modules/app-page/vendored/ssr/react.js', + singleton: true, + shareKey: 'react', + layer: WEBPACK_LAYERS_NAMES.serverSideRendering, + issuerLayer: WEBPACK_LAYERS_NAMES.serverSideRendering, + shareScope: [WEBPACK_LAYERS_NAMES.serverSideRendering], + }, + 'rsc-react': { + requiredVersion: false, + singleton: true, + shareKey: 'react', + request: 'react', + import: 'next/dist/server/route-modules/app-page/vendored/rsc/react.js', + layer: WEBPACK_LAYERS_NAMES.reactServerComponents, + issuerLayer: WEBPACK_LAYERS_NAMES.reactServerComponents, + shareScope: [WEBPACK_LAYERS_NAMES.reactServerComponents], + }, +}; + +const reactJsxRuntimeGroup = { + 'react/jsx-dev-runtime': { + singleton: true, + import: false, + }, + // "react/jsx-dev-runtime-ssr": { + // singleton: true, + // shareKey: 'react/jsx-dev-runtime', + // request: 'react/jsx-dev-runtime', + // layer: WEBPACK_LAYERS_NAMES.serverSideRendering, + // issuerLayer: WEBPACK_LAYERS_NAMES.serverSideRendering, + // shareScope: WEBPACK_LAYERS_NAMES.serverSideRendering, + // }, + // "react/jsx-dev-runtime-rsc": { + // request: 'react/jsx-dev-runtime', + // singleton: true, + // shareKey: 'react/jsx-dev-runtime', + // layer: WEBPACK_LAYERS_NAMES.reactServerComponents, + // issuerLayer: WEBPACK_LAYERS_NAMES.reactServerComponents, + // shareScope: WEBPACK_LAYERS_NAMES.reactServerComponents, + // } +}; + +// Group React-DOM related packages +const reactDomGroup = { + // "react-dom": { + // singleton: true, + // import: false, + // }, + // "rsc-react-dom": { + // singleton: true, + // shareKey: 'react-dom', + // request: 'react-dom', + // layer: WEBPACK_LAYERS_NAMES.reactServerComponents, + // issuerLayer: WEBPACK_LAYERS_NAMES.reactServerComponents, + // shareScope: WEBPACK_LAYERS_NAMES.reactServerComponents, + // }, + // "react-dom-ssr": { + // request: 'react-dom', + // singleton: true, + // shareKey: 'react-dom', + // layer: WEBPACK_LAYERS_NAMES.serverSideRendering, + // issuerLayer: WEBPACK_LAYERS_NAMES.serverSideRendering, + // shareScope: WEBPACK_LAYERS_NAMES.serverSideRendering, + // } +}; + +// Group Next.js related packages +const nextGroup = { 'next/dynamic': { requiredVersion: undefined, singleton: true, @@ -145,34 +237,10 @@ export const DEFAULT_SHARE_SCOPE: moduleFederationPlugin.SharedObject = { singleton: true, import: undefined, }, - react: { - singleton: true, - requiredVersion: false, - import: false, - }, - 'react/': { - singleton: true, - requiredVersion: false, - import: false, - }, - 'react-dom/': { - singleton: true, - requiredVersion: false, - import: false, - }, - 'react-dom': { - singleton: true, - requiredVersion: false, - import: false, - }, - 'react/jsx-dev-runtime': { - singleton: true, - requiredVersion: false, - }, - 'react/jsx-runtime': { - singleton: true, - requiredVersion: false, - }, +}; + +// Group styled-jsx related packages +const styledJsxGroup = { 'styled-jsx': { singleton: true, import: undefined, @@ -193,6 +261,15 @@ export const DEFAULT_SHARE_SCOPE: moduleFederationPlugin.SharedObject = { }, }; +//@ts-ignore +export const DEFAULT_SHARE_SCOPE: moduleFederationPlugin.SharedObject = { + ...reactGroup, + ...reactDomGroup, + // ...nextGroup, + // ...styledJsxGroup, + // ...reactJsxRuntimeGroup, +}; + /** * Defines a default share scope for the browser environment. * This function takes the DEFAULT_SHARE_SCOPE and sets eager to undefined and import to undefined for all entries. @@ -206,7 +283,13 @@ export const DEFAULT_SHARE_SCOPE: moduleFederationPlugin.SharedObject = { export const DEFAULT_SHARE_SCOPE_BROWSER: moduleFederationPlugin.SharedObject = Object.entries(DEFAULT_SHARE_SCOPE).reduce((acc, item) => { const [key, value] = item as [string, moduleFederationPlugin.SharedConfig]; - + // if(key.startsWith(WEBPACK_LAYERS_NAMES.reactServerComponents) || key.startsWith(WEBPACK_LAYERS_NAMES.serverSideRendering)) { + // return acc + // } + // + // if(key === 'next-navigation') { + // return acc; + // } // Set eager and import to undefined for all entries, except for the ones specified above acc[key] = { ...value, import: undefined }; diff --git a/packages/nextjs-mf/src/plugins/NextFederationPlugin/index.ts b/packages/nextjs-mf/src/plugins/NextFederationPlugin/index.ts index 416476409b3..057971137de 100644 --- a/packages/nextjs-mf/src/plugins/NextFederationPlugin/index.ts +++ b/packages/nextjs-mf/src/plugins/NextFederationPlugin/index.ts @@ -30,6 +30,7 @@ import { ModuleFederationPlugin } from '@module-federation/enhanced/webpack'; import type { moduleFederationPlugin } from '@module-federation/sdk'; import path from 'path'; +import { WEBPACK_LAYERS_NAMES } from '../../internal'; /** * NextFederationPlugin is a webpack plugin that handles Next.js application federation using Module Federation. */ @@ -108,14 +109,14 @@ export class NextFederationPlugin { p?.constructor?.name === 'BuildManifestPlugin', ); - if (manifestPlugin) { - //@ts-ignore - if (manifestPlugin?.appDirEnabled) { - throw new Error( - 'App Directory is not supported by nextjs-mf. Use only pages directory, do not open git issues about this', - ); - } - } + // if (manifestPlugin) { + // //@ts-ignore + // if (manifestPlugin?.appDirEnabled) { + // throw new Error( + // 'App Directory is not supported by nextjs-mf. Use only pages directory, do not open git issues about this', + // ); + // } + // } const compilerValid = validateCompilerOptions(compiler); const pluginValid = validatePluginOptions(this._options); @@ -215,6 +216,10 @@ export class NextFederationPlugin { remotes: { ...this._options.remotes, }, + shareScope: Object.values({ + ...WEBPACK_LAYERS_NAMES, + default: 'default', + }), shared: { ...defaultShared, ...this._options.shared, diff --git a/packages/nextjs-mf/src/plugins/container/runtimePlugin.ts b/packages/nextjs-mf/src/plugins/container/runtimePlugin.ts index 558dbcc0bb1..5da7e885b6c 100644 --- a/packages/nextjs-mf/src/plugins/container/runtimePlugin.ts +++ b/packages/nextjs-mf/src/plugins/container/runtimePlugin.ts @@ -224,6 +224,7 @@ export default function (): FederationRuntimePlugin { ) { return args; } + console.log(args); const shareScopeMap = args.shareScopeMap; const scope = args.scope; const pkgName = args.pkgName; @@ -237,11 +238,11 @@ export default function (): FederationRuntimePlugin { if (!host.options.shared[pkgName]) { return args; } - args.resolver = function () { - shareScopeMap[scope][pkgName][version] = - host.options.shared[pkgName][0]; - return shareScopeMap[scope][pkgName][version]; - }; + // args.resolver = function () { + // shareScopeMap[scope][pkgName][version] = + // host.options.shared[pkgName][0]; + // return shareScopeMap[scope][pkgName][version]; + // }; return args; }, beforeLoadShare: async function (args: any) {