From 432550a02a2e344afeac975c3f2052707aba7fd6 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Tue, 21 Jan 2025 09:35:24 -0800 Subject: [PATCH] feat(nextjs-mf): update module share for rsc --- .../app/context/context-click-counter.tsx | 3 + .../[subCategorySlug]/error.tsx | 2 +- .../error-handling/[categorySlug]/error.tsx | 2 +- .../app/error-handling/error.tsx | 2 +- apps/next-app-router-4000/app/layout.tsx | 4 +- apps/next-app-router-4000/ui/buggy-button.tsx | 2 +- apps/next-app-router-4001/app/demo/page.tsx | 47 +++++--- apps/next-app-router-4001/next.config.js | 8 +- .../src/lib/sharing/ConsumeSharedPlugin.ts | 5 +- .../src/lib/sharing/ProvideSharedPlugin.ts | 6 +- .../configCases/layers/9-layers-full/App.js | 12 ++ .../layers/9-layers-full/ComponentA.js | 5 + .../configCases/layers/9-layers-full/index.js | 12 ++ .../layers/9-layers-full/layerImport.js | 3 + .../9-layers-full/node_modules/react/index.js | 4 + .../9-layers-full/node_modules/react/other.js | 1 + .../node_modules/react/package.json | 4 + .../layers/9-layers-full/package.json | 12 ++ .../layers/9-layers-full/test.config.js | 6 + .../layers/9-layers-full/webpack.config.js | 87 ++++++++++++++ packages/nextjs-mf/src/internal.ts | 109 ++++++++---------- 21 files changed, 246 insertions(+), 90 deletions(-) create mode 100644 packages/enhanced/test/configCases/layers/9-layers-full/App.js create mode 100644 packages/enhanced/test/configCases/layers/9-layers-full/ComponentA.js create mode 100644 packages/enhanced/test/configCases/layers/9-layers-full/index.js create mode 100644 packages/enhanced/test/configCases/layers/9-layers-full/layerImport.js create mode 100644 packages/enhanced/test/configCases/layers/9-layers-full/node_modules/react/index.js create mode 100644 packages/enhanced/test/configCases/layers/9-layers-full/node_modules/react/other.js create mode 100644 packages/enhanced/test/configCases/layers/9-layers-full/node_modules/react/package.json create mode 100644 packages/enhanced/test/configCases/layers/9-layers-full/package.json create mode 100644 packages/enhanced/test/configCases/layers/9-layers-full/test.config.js create mode 100644 packages/enhanced/test/configCases/layers/9-layers-full/webpack.config.js diff --git a/apps/next-app-router-4000/app/context/context-click-counter.tsx b/apps/next-app-router-4000/app/context/context-click-counter.tsx index 0a36ee3aae..a59be8aeb2 100644 --- a/apps/next-app-router-4000/app/context/context-click-counter.tsx +++ b/apps/next-app-router-4000/app/context/context-click-counter.tsx @@ -3,6 +3,8 @@ import { useCounter } from './counter-context'; import React from 'react'; import { Boundary } from '#/ui/boundary'; +import dynamic from 'next/dynamic'; +const Button = dynamic(() => import('remote_4001/Button'), { ssr: true }); const ContextClickCounter = () => { const [count, setCount] = useCounter(); @@ -14,6 +16,7 @@ const ContextClickCounter = () => { size="small" animateRerendering={false} > + @@ -29,22 +35,29 @@ export default function DemoPage() {
-

Navigation Components

+

Navigation Components

- Tab 1 - Tab 2 - Tab 3 + + Tab 1 + + + Tab 2 + + + Tab 3 +
-

Product Components

+

Product Components

-

Interactive Components

+

Interactive Components

-
-

Count Up Animation

+
+

Count Up Animation

-
-

Rendering Information

+
+

Rendering Information

diff --git a/apps/next-app-router-4001/next.config.js b/apps/next-app-router-4001/next.config.js index dafe9d8ca2..714745c649 100644 --- a/apps/next-app-router-4001/next.config.js +++ b/apps/next-app-router-4001/next.config.js @@ -22,9 +22,11 @@ const nextConfig = { exposes: { // Core UI Components './Button': './ui/button', - // './Header': './ui/header', - // './Footer': './ui/footer', - // './GlobalNav': './ui/global-nav', + // './Header': isServer ? './ui/header?rsc' : './ui/header?shared', + './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', // // // Product Related Components // './ProductCard': './ui/product-card', diff --git a/packages/enhanced/src/lib/sharing/ConsumeSharedPlugin.ts b/packages/enhanced/src/lib/sharing/ConsumeSharedPlugin.ts index e9badacf8a..1ae93165b8 100644 --- a/packages/enhanced/src/lib/sharing/ConsumeSharedPlugin.ts +++ b/packages/enhanced/src/lib/sharing/ConsumeSharedPlugin.ts @@ -334,8 +334,9 @@ class ConsumeSharedPlugin { return createConsumeSharedModule(context, request, match); } for (const [prefix, options] of prefixedConsumes) { - if (request.startsWith(prefix)) { - const remainder = request.slice(prefix.length); + const lookup = options.request || prefix; + if (request.startsWith(lookup)) { + const remainder = request.slice(lookup.length); return createConsumeSharedModule(context, request, { ...options, import: options.import diff --git a/packages/enhanced/src/lib/sharing/ProvideSharedPlugin.ts b/packages/enhanced/src/lib/sharing/ProvideSharedPlugin.ts index 585b6faeb4..a814c6d8cd 100644 --- a/packages/enhanced/src/lib/sharing/ProvideSharedPlugin.ts +++ b/packages/enhanced/src/lib/sharing/ProvideSharedPlugin.ts @@ -229,8 +229,10 @@ class ProvideSharedPlugin { } } for (const [prefix, config] of prefixMatchProvides) { - if (request.startsWith(prefix) && resource) { - const remainder = request.slice(prefix.length); + const lookup = config.request || prefix; + if (request.startsWith(lookup) && resource) { + const remainder = request.slice(lookup.length); + debugger; provideSharedModule( resource, { diff --git a/packages/enhanced/test/configCases/layers/9-layers-full/App.js b/packages/enhanced/test/configCases/layers/9-layers-full/App.js new file mode 100644 index 0000000000..a1ffa9b896 --- /dev/null +++ b/packages/enhanced/test/configCases/layers/9-layers-full/App.js @@ -0,0 +1,12 @@ +import React, { layeredComponentsReact } from 'react'; +import other from 'react/other'; +import ComponentA from 'containerA/ComponentA'; +import RemoteApp from 'containerA/App'; +import LocalComponentA from './ComponentA'; +console.log(other); +export default function App() { + return `App (no layer) rendered with React version: [${React()}] with non-layered React value: [${layeredComponentsReact()}] +Local Component: ${LocalComponentA()} +Remote Component from container7: ${ComponentA()} +Remote App from container7: ${RemoteApp()}`; +} diff --git a/packages/enhanced/test/configCases/layers/9-layers-full/ComponentA.js b/packages/enhanced/test/configCases/layers/9-layers-full/ComponentA.js new file mode 100644 index 0000000000..6e1accf7b1 --- /dev/null +++ b/packages/enhanced/test/configCases/layers/9-layers-full/ComponentA.js @@ -0,0 +1,5 @@ +import React, { layeredComponentsReact } from 'react'; + +export default function ComponentA() { + return `LocalComponentA (in react-layer) rendered with React version: [${React()}], layered React value: [${layeredComponentsReact()}]`; +} diff --git a/packages/enhanced/test/configCases/layers/9-layers-full/index.js b/packages/enhanced/test/configCases/layers/9-layers-full/index.js new file mode 100644 index 0000000000..cb947d335b --- /dev/null +++ b/packages/enhanced/test/configCases/layers/9-layers-full/index.js @@ -0,0 +1,12 @@ +it('should load App with React and both types of remote components', () => { + return import('containerA/noop').then((m) => { + return import('./App').then(({ default: App }) => { + const rendered = App(); + expect(rendered) + .toBe(`App (no layer) rendered with React version: [This is react 0.1.2] with non-layered React value: [No Layer] +Local Component: LocalComponentA (in react-layer) rendered with React version: [This is react 0.1.2], layered React value: [react-layer] +Remote Component from container7: ComponentA (in react-layer) rendered with React version: [This is react 0.1.2] with layered React value: [react-layer] +Remote App from container7: App (no layer) rendered with React version: [This is react 0.1.2] with non-layered React value: [No Layer] and imported: ComponentA (in react-layer) rendered with React version: [This is react 0.1.2] with layered React value: [react-layer]`); + }); + }); +}); diff --git a/packages/enhanced/test/configCases/layers/9-layers-full/layerImport.js b/packages/enhanced/test/configCases/layers/9-layers-full/layerImport.js new file mode 100644 index 0000000000..361f3ac025 --- /dev/null +++ b/packages/enhanced/test/configCases/layers/9-layers-full/layerImport.js @@ -0,0 +1,3 @@ +import LocalComponentA from './ComponentA'; + +export default LocalComponentA; diff --git a/packages/enhanced/test/configCases/layers/9-layers-full/node_modules/react/index.js b/packages/enhanced/test/configCases/layers/9-layers-full/node_modules/react/index.js new file mode 100644 index 0000000000..17f75306c1 --- /dev/null +++ b/packages/enhanced/test/configCases/layers/9-layers-full/node_modules/react/index.js @@ -0,0 +1,4 @@ +let version = "2.1.0"; +export function setVersion(v) { version = v; } +export const layeredComponentsReact = () => "FEDERATION IS BROKEN, THIS VERION SHOULD NOT BE LOADED"; +export default () => `${version}`; diff --git a/packages/enhanced/test/configCases/layers/9-layers-full/node_modules/react/other.js b/packages/enhanced/test/configCases/layers/9-layers-full/node_modules/react/other.js new file mode 100644 index 0000000000..45c70286f8 --- /dev/null +++ b/packages/enhanced/test/configCases/layers/9-layers-full/node_modules/react/other.js @@ -0,0 +1 @@ +export default 'other'; diff --git a/packages/enhanced/test/configCases/layers/9-layers-full/node_modules/react/package.json b/packages/enhanced/test/configCases/layers/9-layers-full/node_modules/react/package.json new file mode 100644 index 0000000000..8a05e35aa0 --- /dev/null +++ b/packages/enhanced/test/configCases/layers/9-layers-full/node_modules/react/package.json @@ -0,0 +1,4 @@ +{ + "version": "2.1.0", + "name": "react" +} diff --git a/packages/enhanced/test/configCases/layers/9-layers-full/package.json b/packages/enhanced/test/configCases/layers/9-layers-full/package.json new file mode 100644 index 0000000000..9db5aa360a --- /dev/null +++ b/packages/enhanced/test/configCases/layers/9-layers-full/package.json @@ -0,0 +1,12 @@ +{ + "private": true, + "engines": { + "node": ">=10.13.0" + }, + "scripts": { + "build": "webpack --config=webpack.config.js" + }, + "dependencies": { + "react": "*" + } +} diff --git a/packages/enhanced/test/configCases/layers/9-layers-full/test.config.js b/packages/enhanced/test/configCases/layers/9-layers-full/test.config.js new file mode 100644 index 0000000000..1ca0b7cf73 --- /dev/null +++ b/packages/enhanced/test/configCases/layers/9-layers-full/test.config.js @@ -0,0 +1,6 @@ +module.exports = { + findBundle: function (i, options) { + return './main.js'; + return i === 0 ? './main.js' : './module/main.mjs'; + }, +}; diff --git a/packages/enhanced/test/configCases/layers/9-layers-full/webpack.config.js b/packages/enhanced/test/configCases/layers/9-layers-full/webpack.config.js new file mode 100644 index 0000000000..4558634414 --- /dev/null +++ b/packages/enhanced/test/configCases/layers/9-layers-full/webpack.config.js @@ -0,0 +1,87 @@ +const { ModuleFederationPlugin } = require('../../../../dist/src'); + +const common = { + name: 'container_8', + filename: 'container.js', + remotes: { + containerA: { + external: '../7-layers-full/container.js', + }, + }, + shared: { + react: { + singleton: true, + requiredVersion: false, + }, + randomvalue: { + request: 'react/', + shareKey: 'react', + singleton: true, + requiredVersion: false, + layer: 'react-layer', + issuerLayer: 'react-layer', + }, + }, +}; + +const commonConfig = { + entry: './index.js', + mode: 'development', + devtool: false, + experiments: { + layers: true, + }, + module: { + rules: [ + { + layer: 'react-layer', + test: /ComponentA\.js$/, + }, + { + test: /react\.js$/, + issuerLayer: 'react-layer', + layer: 'react-layer', + }, + ], + }, +}; + +module.exports = [ + { + ...commonConfig, + output: { + filename: '[name].js', + uniqueName: '8-layers-full', + }, + plugins: [ + new ModuleFederationPlugin({ + ...common, + library: { type: 'commonjs-module' }, + }), + ], + }, + { + ...commonConfig, + experiments: { + ...commonConfig.experiments, + outputModule: true, + }, + output: { + filename: 'module/[name].mjs', + uniqueName: '8-layers-full-mjs', + }, + plugins: [ + new ModuleFederationPlugin({ + ...common, + library: { type: 'module' }, + filename: 'module/container.mjs', + remotes: { + containerA: { + external: '../../7-layers-full/module/container.mjs', + }, + }, + }), + ], + target: 'node14', + }, +]; diff --git a/packages/nextjs-mf/src/internal.ts b/packages/nextjs-mf/src/internal.ts index 52a39ff1a2..84b585c604 100644 --- a/packages/nextjs-mf/src/internal.ts +++ b/packages/nextjs-mf/src/internal.ts @@ -48,66 +48,54 @@ const WEBPACK_LAYERS_NAMES = { appPagesBrowser: 'app-pages-browser', } as const; -const reactShares = [ - WEBPACK_LAYERS_NAMES.reactServerComponents, - WEBPACK_LAYERS_NAMES.serverSideRendering, - undefined, -].reduce( - (acc, layer) => { - const key = layer ? `react-${layer}` : 'react'; - acc[key] = { - singleton: true, - requiredVersion: false, - import: layer ? undefined : false, - shareKey: 'react', - request: 'react', - layer, - issuerLayer: layer, - }; - return acc; - }, - {} as Record, -); +const createSharedConfig = ( + name: string, + layers: (string | undefined)[], + options: { request?: string; import?: false | undefined } = {}, +) => { + return layers.reduce( + (acc, layer) => { + const key = layer ? `${name}-${layer}` : name; + acc[key] = { + singleton: true, + requiredVersion: false, + import: layer ? undefined : (options.import ?? false), + shareKey: options.request ?? name, + request: options.request ?? name, + layer, + issuerLayer: layer, + }; + return acc; + }, + {} as Record, + ); +}; -const reactDomShares = [ +const defaultLayers = [ WEBPACK_LAYERS_NAMES.reactServerComponents, WEBPACK_LAYERS_NAMES.serverSideRendering, undefined, -].reduce( - (acc, layer) => { - const key = layer ? `react-${layer}` : 'react'; - acc[key] = { - singleton: true, - requiredVersion: false, - import: layer ? undefined : false, - shareKey: 'react-dom', - request: 'react-dom', - layer, - issuerLayer: layer, - }; - return acc; - }, - {} as Record, -); +]; -const nextNavigationShares = [ +const navigationLayers = [ WEBPACK_LAYERS_NAMES.reactServerComponents, WEBPACK_LAYERS_NAMES.serverSideRendering, -].reduce( - (acc, layer) => { - const key = layer ? `next-navigation-${layer}` : 'next-navigation'; - acc[key] = { - singleton: true, - requiredVersion: false, - shareKey: 'next/navigation', - request: 'next/navigation', - layer, - issuerLayer: layer, - }; - return acc; - }, - {} as Record, +]; + +const reactShares = createSharedConfig('react', defaultLayers); +const reactDomShares = createSharedConfig('react', defaultLayers, { + request: 'react-dom', +}); +const jsxRuntimeShares = createSharedConfig('react/', navigationLayers, { + request: 'react/', + import: undefined, +}); +const nextNavigationShares = createSharedConfig( + 'next-navigation', + navigationLayers, + { request: 'next/navigation' }, ); + /** * @typedef SharedObject * @type {object} @@ -123,6 +111,7 @@ export const DEFAULT_SHARE_SCOPE: sharePlugin.SharedObject = { ...reactShares, ...reactDomShares, ...nextNavigationShares, + ...jsxRuntimeShares, 'next/dynamic': { requiredVersion: undefined, singleton: true, @@ -173,14 +162,14 @@ export const DEFAULT_SHARE_SCOPE: sharePlugin.SharedObject = { // requiredVersion: false, // import: false, // }, - // 'react/jsx-dev-runtime': { - // singleton: true, - // requiredVersion: false, - // }, - // 'react/jsx-runtime': { - // singleton: true, - // requiredVersion: false, - // }, + 'react/jsx-dev-runtime': { + singleton: true, + requiredVersion: false, + }, + 'react/jsx-runtime': { + singleton: true, + requiredVersion: false, + }, 'styled-jsx': { singleton: true, import: undefined,