@module-federation/modern-js 0.0.0-next-20240623084034 → 0.0.0-next-20240625025206

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1 +1,260 @@
1
1
  # @module-federation/modern-js
2
+
3
+ This plugin provides Module Federation supporting functions for Modern.js
4
+
5
+ ## Supports
6
+
7
+ - modern.js ^2.52.0
8
+ - Server-Side Rendering
9
+
10
+ We highly recommend referencing this application which takes advantage of the best capabilities:
11
+ [module-federation example](https://github.com/module-federation/core/tree/feat/modernjs-ssr/apps/modernjs-ssr)
12
+
13
+ ## Quick Start
14
+
15
+ ### Installation
16
+
17
+ You can install the plugin with the following commands:
18
+
19
+ import { PackageManagerTabs } from '@theme';
20
+
21
+ <PackageManagerTabs
22
+ command={{
23
+ npm: 'npm add @module-federation/modern-js --save',
24
+ yarn: 'yarn add @module-federation/modern-js --save',
25
+ pnpm: 'pnpm add @module-federation/modern-js --save',
26
+ bun: 'bun add @module-federation/modern-js --save',
27
+ }}
28
+ />
29
+
30
+ ### Apply Plugin
31
+
32
+ Apply this plugin in `plugins` of `modern.config.ts`:
33
+
34
+ ```ts title="modern.config.ts"
35
+ import { appTools, defineConfig } from '@modern-js/app-tools';
36
+ import { moduleFederationPlugin } from '@module-federation/modern-js';
37
+
38
+ export default defineConfig({
39
+ dev: {
40
+ port: 3005,
41
+ },
42
+ runtime: {
43
+ router: true,
44
+ },
45
+ // moduleFederationPlugin is a plug-in for modern.js, which can make certain modifications to the build/runtime
46
+ plugins: [appTools(), moduleFederationPlugin()],
47
+ });
48
+ ```
49
+
50
+ Then create the `module-federation.config.ts` file and write the required configuration:
51
+
52
+ ```ts title="module-federation.config.ts"
53
+ import { createModuleFederationConfig } from '@module-federation/modern-js';
54
+ export default createModuleFederationConfig({
55
+ name: 'host',
56
+ remotes: {
57
+ remote: 'remote@http://localhost:3006/mf-manifest.json',
58
+ },
59
+ shared: {
60
+ react: { singleton: true },
61
+ 'react-dom': { singleton: true },
62
+ },
63
+ });
64
+ ```
65
+
66
+ ### Type support
67
+
68
+ add `/// <reference types='@module-federation/modern-js/types' />` in `modern-app-env.d.ts` to get type support.
69
+
70
+ ```diff title='modern-app-env.d.ts'
71
+ + /// <reference types='@module-federation/modern-js/types' />
72
+ ```
73
+
74
+ ## Server-Side Rendering
75
+
76
+ :::info
77
+ For a better performance experience, Module Federation X Modern.js SSR only supports stream SSR.
78
+ :::
79
+
80
+ There is no difference between using Module Federation in SSR scenarios and CSR scenarios. Developers can just keep following the original development behavior.
81
+
82
+ But for a better user experience, we provide supporting functions/components to help developers better use Module Federation.
83
+
84
+ ### createRemoteSSRComponent
85
+
86
+ ```ts
87
+ declare function createRemoteSSRComponent(
88
+ props: CreateRemoteSSRComponentOptions
89
+ ): (props: ComponentType) => React.JSX.Element;
90
+
91
+ type CreateRemoteSSRComponentOptions = {
92
+ loader: () => Promise<T>;
93
+ loading: React.ReactNode;
94
+ fallback: ErrorBoundaryPropsWithComponent['FallbackComponent'];
95
+ injectScript?: boolean;
96
+ injectLink?: boolean;
97
+ export?: E;
98
+ };
99
+
100
+ type ComponentType = T[E] extends (...args: any) => any
101
+ ? Parameters<T[E]>[0] extends undefined
102
+ ? Record<string, never>
103
+ : Parameters<T[E]>[0]
104
+ : Record<string, never>;
105
+ ```
106
+
107
+ This function will also help inject the corresponding style tag/script while loading the component. This behavior can help avoid the CSS flickering problem caused by streaming rendering and accelerate the PID (first screen interactive time).
108
+
109
+ #### Example
110
+
111
+ ```tsx
112
+ import React, { FC, memo, useEffect } from 'react';
113
+ import { registerRemotes, createRemoteSSRComponent } from '@modern-js/runtime/mf';
114
+ // The remote declared in the build plug-in can be imported directly at the top level
115
+ import RemoteComp from 'remote/Image';
116
+
117
+
118
+ const RemoteSSRComponent = createRemoteSSRComponent({
119
+ // The remote declared in the build plug-in can also be loaded using this function: loader: () => import('remote/Image'),
120
+ loader: () => loadRemote('dynamic_remote/Image'),
121
+ loading: <div>loading...</div>,
122
+ fallback: ({ error }) => {
123
+ if (error instanceof Error && error.message.includes('not exist')) {
124
+ return <div>fallback - not existed id</div>;
125
+ }
126
+ return <div>fallback</div>;
127
+ },
128
+ });
129
+
130
+ const Product: FC = () => {
131
+ registerRemotes([
132
+ {
133
+ name: 'dynamic_remote',
134
+ entry: 'http://localhost:3008/mf-manifest.json',
135
+ },
136
+ ]);
137
+
138
+ const fallback = (err: Error) => {
139
+ if (err.message.includes('does not exist in container')) {
140
+ return <div>404</div>;
141
+ }
142
+ throw err;
143
+ };
144
+
145
+ return <>
146
+ <RemoteSSRComponent />
147
+ <RemoteComp />
148
+ </>;
149
+ };
150
+ export default Product;
151
+ ```
152
+
153
+ #### loading
154
+
155
+ - Type:`React.ReactNode`
156
+ - Required: Yes
157
+ - Default value: `undefined`
158
+
159
+ Set module loading status.
160
+
161
+ #### fallback
162
+
163
+ - Type:`((err: Error) => React.ReactElement)`
164
+ - Required: Yes
165
+ - Default value: `undefined`
166
+
167
+ A fault-tolerant component that is rendered when the component fails to **load** or **render**.
168
+
169
+ Note: This component only renders this fault-tolerant component on the client side when **rendering** fails.
170
+
171
+ #### injectLink
172
+
173
+ - Type:`boolean`
174
+ - Required: No
175
+ - Default value: `true`
176
+
177
+ Whether to inject the style of the corresponding component.
178
+
179
+ #### injectScript
180
+
181
+ - Type:`boolean`
182
+ - Required: No
183
+ - Default value: `true`
184
+
185
+ Whether to inject the script of the corresponding component.
186
+
187
+ ### collectSSRAssets
188
+
189
+ ```ts
190
+ declare function createRemoteSSRComponent(
191
+ props: CollectSSRAssetsOptions
192
+ ): React.ReactNode[];
193
+
194
+ type CollectSSRAssetsOptions = {
195
+ id: string;
196
+ injectScript?: boolean;
197
+ injectLink?: boolean;
198
+ };
199
+ ```
200
+
201
+ This function collects producer-related scripts and styles rendered on the server side and returns the script/link tag.
202
+
203
+ #### Example
204
+
205
+ ```tsx
206
+ import React from 'react';
207
+ import { collectSSRAssets } from '@modern-js/runtime/mf';
208
+ import Comp from 'remote/Image';
209
+
210
+ export default (): JSX.Element => (
211
+ <div>
212
+ {collectSSRAssets({ id: 'remote/Image' })}
213
+ <Comp />
214
+ </div>
215
+ );
216
+ ```
217
+
218
+ #### injectLink
219
+
220
+ - Type:`boolean`
221
+ - Required: No
222
+ - Default value: `true`
223
+
224
+ Whether to inject the style of the corresponding component.
225
+
226
+ #### injectScript
227
+
228
+ - Type:`boolean`
229
+ - Required: No
230
+ - Default value: `true`
231
+
232
+ Whether to inject the script of the corresponding component.
233
+
234
+ ### SSRLiveReload
235
+
236
+ :::info
237
+ This component will not take effect in the production environment!
238
+ :::
239
+
240
+ ```ts
241
+ declare function SSRLiveReload(): React.JSX.Element | null;
242
+ ```
243
+
244
+ When remote components are updated, page reloads occur automatically.
245
+
246
+ #### Example
247
+
248
+ ```tsx
249
+ import { Outlet } from '@modern-js/runtime/router';
250
+ import { SSRLiveReload } from '@modern-js/runtime/mf';
251
+
252
+ export default function Layout() {
253
+ return (
254
+ <div>
255
+ <SSRLiveReload />
256
+ <Outlet />
257
+ </div>
258
+ );
259
+ }
260
+ ```
@@ -89,10 +89,13 @@ const moduleFederationPlugin = (userConfig = {}) => ({
89
89
  mfConfig: envConfig
90
90
  });
91
91
  };
92
- const ipv4 = await (0, import_utils2.lookupIpv4)();
92
+ const ipv4 = (0, import_utils2.getIPV4)();
93
93
  return {
94
94
  tools: {
95
95
  rspack(config) {
96
+ if (enableSSR) {
97
+ throw new Error("@module-federation/modern-js not support ssr for rspack bundler yet!");
98
+ }
96
99
  modifyBundlerConfig(config, false);
97
100
  },
98
101
  webpack(config, { isServer }) {
@@ -28,17 +28,17 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
29
  var utils_exports = {};
30
30
  __export(utils_exports, {
31
+ getIPV4: () => getIPV4,
31
32
  getMFConfig: () => getMFConfig,
32
33
  getTargetEnvConfig: () => getTargetEnvConfig,
33
- lookupIpv4: () => lookupIpv4,
34
34
  patchMFConfig: () => patchMFConfig,
35
35
  patchWebpackConfig: () => patchWebpackConfig
36
36
  });
37
37
  module.exports = __toCommonJS(utils_exports);
38
38
  var import_sdk = require("@module-federation/sdk");
39
39
  var import_path = __toESM(require("path"));
40
+ var import_os = __toESM(require("os"));
40
41
  var import_node_bundle_require = require("@modern-js/node-bundle-require");
41
- var import_dns = __toESM(require("dns"));
42
42
  var import_constant = require("../constant");
43
43
  const defaultPath = import_path.default.resolve(process.cwd(), "module-federation.config.ts");
44
44
  const getMFConfig = async (userConfig) => {
@@ -64,7 +64,7 @@ const replaceRemoteUrl = async (mfConfig) => {
64
64
  if (!mfConfig.remotes) {
65
65
  return;
66
66
  }
67
- const ipv4 = await lookupIpv4();
67
+ const ipv4 = getIPV4();
68
68
  const handleRemoteObject = (remoteObject) => {
69
69
  Object.keys(remoteObject).forEach((remoteKey) => {
70
70
  const remote = remoteObject[remoteKey];
@@ -95,6 +95,33 @@ const patchMFConfig = (mfConfig, isServer) => {
95
95
  const runtimePlugins = [
96
96
  ...mfConfig.runtimePlugins || []
97
97
  ];
98
+ const ModernJSRuntime = "@modern-js/runtime/mf";
99
+ if (mfConfig.dts !== false) {
100
+ var _mfConfig_dts, _mfConfig_dts1;
101
+ if (typeof mfConfig.dts === "boolean" || mfConfig.dts === void 0) {
102
+ mfConfig.dts = {
103
+ consumeTypes: {
104
+ runtimePkgs: [
105
+ ModernJSRuntime
106
+ ]
107
+ }
108
+ };
109
+ } else if (((_mfConfig_dts = mfConfig.dts) === null || _mfConfig_dts === void 0 ? void 0 : _mfConfig_dts.consumeTypes) || ((_mfConfig_dts1 = mfConfig.dts) === null || _mfConfig_dts1 === void 0 ? void 0 : _mfConfig_dts1.consumeTypes) === void 0) {
110
+ var _mfConfig_dts2;
111
+ if (typeof mfConfig.dts.consumeTypes === "boolean" || ((_mfConfig_dts2 = mfConfig.dts) === null || _mfConfig_dts2 === void 0 ? void 0 : _mfConfig_dts2.consumeTypes) === void 0) {
112
+ mfConfig.dts.consumeTypes = {
113
+ runtimePkgs: [
114
+ ModernJSRuntime
115
+ ]
116
+ };
117
+ } else {
118
+ mfConfig.dts.consumeTypes.runtimePkgs = mfConfig.dts.consumeTypes.runtimePkgs || [];
119
+ if (!mfConfig.dts.consumeTypes.runtimePkgs.includes(ModernJSRuntime)) {
120
+ mfConfig.dts.consumeTypes.runtimePkgs.push(ModernJSRuntime);
121
+ }
122
+ }
123
+ }
124
+ }
98
125
  injectRuntimePlugins(import_path.default.resolve(__dirname, "./mfRuntimePlugins/shared-strategy.js"), runtimePlugins);
99
126
  if (isDev) {
100
127
  injectRuntimePlugins(import_path.default.resolve(__dirname, "./mfRuntimePlugins/resolve-entry-ipv4.js"), runtimePlugins);
@@ -178,16 +205,31 @@ function patchWebpackConfig(options) {
178
205
  };
179
206
  }
180
207
  }
181
- const lookupIpv4 = async () => {
208
+ const localIpv4 = "127.0.0.1";
209
+ const getIpv4Interfaces = () => {
182
210
  try {
183
- const res = await import_dns.default.promises.lookup(import_constant.LOCALHOST, {
184
- family: 4
211
+ const interfaces = import_os.default.networkInterfaces();
212
+ const ipv4Interfaces = [];
213
+ Object.values(interfaces).forEach((detail) => {
214
+ detail === null || detail === void 0 ? void 0 : detail.forEach((detail2) => {
215
+ const familyV4Value = typeof detail2.family === "string" ? "IPv4" : 4;
216
+ if (detail2.family === familyV4Value && detail2.address !== localIpv4) {
217
+ ipv4Interfaces.push(detail2);
218
+ }
219
+ });
185
220
  });
186
- return res.address;
187
- } catch (err) {
188
- return "127.0.0.1";
221
+ return ipv4Interfaces;
222
+ } catch (_err) {
223
+ return [];
189
224
  }
190
225
  };
226
+ const getIPV4 = () => {
227
+ const ipv4Interfaces = getIpv4Interfaces();
228
+ const ipv4Interface = ipv4Interfaces[0] || {
229
+ address: localIpv4
230
+ };
231
+ return ipv4Interface.address;
232
+ };
191
233
  const SPLIT_CHUNK_MAP = {
192
234
  REACT: "lib-react",
193
235
  ROUTER: "lib-router",
@@ -231,9 +273,9 @@ function autoDeleteSplitChunkCacheGroups(mfConfig, bundlerConfig) {
231
273
  }
232
274
  // Annotate the CommonJS export names for ESM import in node:
233
275
  0 && (module.exports = {
276
+ getIPV4,
234
277
  getMFConfig,
235
278
  getTargetEnvConfig,
236
- lookupIpv4,
237
279
  patchMFConfig,
238
280
  patchWebpackConfig
239
281
  });
@@ -92,6 +92,13 @@ const mfConfig = {
92
92
  eager: true,
93
93
  singleton: true
94
94
  }
95
+ },
96
+ dts: {
97
+ consumeTypes: {
98
+ runtimePkgs: [
99
+ "@modern-js/runtime/mf"
100
+ ]
101
+ }
95
102
  }
96
103
  });
97
104
  });
@@ -16,13 +16,13 @@ var __copyProps = (to, from, except, desc) => {
16
16
  return to;
17
17
  };
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
- var LiveReload_exports = {};
20
- __export(LiveReload_exports, {
21
- LiveReload: () => LiveReload
19
+ var SSRLiveReload_exports = {};
20
+ __export(SSRLiveReload_exports, {
21
+ SSRLiveReload: () => SSRLiveReload
22
22
  });
23
- module.exports = __toCommonJS(LiveReload_exports);
23
+ module.exports = __toCommonJS(SSRLiveReload_exports);
24
24
  var import_jsx_runtime = require("react/jsx-runtime");
25
- function LiveReload() {
25
+ function SSRLiveReload() {
26
26
  if (process.env.NODE_ENV !== "development") {
27
27
  return null;
28
28
  }
@@ -39,5 +39,5 @@ function LiveReload() {
39
39
  }
40
40
  // Annotate the CommonJS export names for ESM import in node:
41
41
  0 && (module.exports = {
42
- LiveReload
42
+ SSRLiveReload
43
43
  });
@@ -26,12 +26,12 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  mod
27
27
  ));
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
- var MFReactComponent_exports = {};
30
- __export(MFReactComponent_exports, {
31
- MFReactComponent: () => MFReactComponent,
32
- collectAssets: () => collectAssets
29
+ var createRemoteSSRComponent_exports = {};
30
+ __export(createRemoteSSRComponent_exports, {
31
+ collectSSRAssets: () => collectSSRAssets,
32
+ createRemoteSSRComponent: () => createRemoteSSRComponent
33
33
  });
34
- module.exports = __toCommonJS(MFReactComponent_exports);
34
+ module.exports = __toCommonJS(createRemoteSSRComponent_exports);
35
35
  var import_jsx_runtime = require("react/jsx-runtime");
36
36
  var import_react = __toESM(require("react"));
37
37
  var import_runtime = require("@module-federation/enhanced/runtime");
@@ -84,7 +84,7 @@ function getTargetModuleInfo(id) {
84
84
  remoteEntry
85
85
  };
86
86
  }
87
- function collectAssets(options) {
87
+ function collectSSRAssets(options) {
88
88
  const { id, injectLink = true, injectScript = true } = typeof options === "string" ? {
89
89
  id: options
90
90
  } : options;
@@ -138,49 +138,63 @@ function collectAssets(options) {
138
138
  ...links
139
139
  ];
140
140
  }
141
- function MFReactComponent(props) {
142
- const { loading = "loading...", id, remoteProps = {}, fallback } = props;
143
- const Component = /* @__PURE__ */ import_react.default.lazy(() => (0, import_runtime.loadRemote)(id).then((mod) => {
144
- const assets = collectAssets(props);
145
- if (!mod) {
146
- throw new Error("load remote failed");
147
- }
148
- const Com = typeof mod === "object" ? "default" in mod ? mod.default : mod : mod;
149
- return {
150
- default: () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, {
151
- children: [
152
- assets,
153
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Com, {
154
- ...remoteProps
155
- })
156
- ]
157
- })
158
- };
159
- }).catch((err) => {
160
- if (!fallback) {
161
- throw err;
162
- }
163
- const FallbackFunctionComponent = fallback;
164
- const FallbackNode = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FallbackFunctionComponent, {
165
- error: err,
166
- resetErrorBoundary: () => {
167
- console.log('SSR mode not support "resetErrorBoundary" !');
141
+ function createRemoteSSRComponent(info) {
142
+ return (props) => {
143
+ const exportName = (info === null || info === void 0 ? void 0 : info.export) || "default";
144
+ const LazyComponent = /* @__PURE__ */ import_react.default.lazy(async () => {
145
+ try {
146
+ const m = await info.loader();
147
+ if (!m) {
148
+ throw new Error("load remote failed");
149
+ }
150
+ const moduleId = m && m[Symbol.for("mf_module_id")];
151
+ const assets = collectSSRAssets({
152
+ id: moduleId,
153
+ injectLink: info.injectLink,
154
+ injectScript: info.injectScript
155
+ });
156
+ const Com = m[exportName];
157
+ if (exportName in m && typeof Com === "function") {
158
+ return {
159
+ default: () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, {
160
+ children: [
161
+ assets,
162
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Com, {
163
+ ...props
164
+ })
165
+ ]
166
+ })
167
+ };
168
+ } else {
169
+ throw Error(`Make sure that ${moduleId} has the correct export when export is ${String(exportName)}`);
170
+ }
171
+ } catch (err) {
172
+ if (!info.fallback) {
173
+ throw err;
174
+ }
175
+ const FallbackFunctionComponent = info.fallback;
176
+ const FallbackNode = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FallbackFunctionComponent, {
177
+ error: err,
178
+ resetErrorBoundary: () => {
179
+ console.log('SSR mode not support "resetErrorBoundary" !');
180
+ }
181
+ });
182
+ return {
183
+ default: () => FallbackNode
184
+ };
168
185
  }
169
186
  });
170
- return {
171
- default: () => FallbackNode
172
- };
173
- }));
174
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_error_boundary.ErrorBoundary, {
175
- FallbackComponent: fallback,
176
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.default.Suspense, {
177
- fallback: loading,
178
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Component, {})
179
- })
180
- });
187
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_error_boundary.ErrorBoundary, {
188
+ FallbackComponent: info.fallback,
189
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.default.Suspense, {
190
+ fallback: info.loading,
191
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LazyComponent, {})
192
+ })
193
+ });
194
+ };
181
195
  }
182
196
  // Annotate the CommonJS export names for ESM import in node:
183
197
  0 && (module.exports = {
184
- MFReactComponent,
185
- collectAssets
198
+ collectSSRAssets,
199
+ createRemoteSSRComponent
186
200
  });
@@ -19,18 +19,18 @@ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "defau
19
19
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
20
  var runtime_exports = {};
21
21
  __export(runtime_exports, {
22
- LiveReload: () => import_LiveReload.LiveReload,
23
- MFReactComponent: () => import_MFReactComponent.MFReactComponent,
24
- collectAssets: () => import_MFReactComponent.collectAssets
22
+ SSRLiveReload: () => import_SSRLiveReload.SSRLiveReload,
23
+ collectSSRAssets: () => import_createRemoteSSRComponent.collectSSRAssets,
24
+ createRemoteSSRComponent: () => import_createRemoteSSRComponent.createRemoteSSRComponent
25
25
  });
26
26
  module.exports = __toCommonJS(runtime_exports);
27
27
  __reExport(runtime_exports, require("@module-federation/enhanced/runtime"), module.exports);
28
- var import_MFReactComponent = require("./MFReactComponent");
29
- var import_LiveReload = require("./LiveReload");
28
+ var import_createRemoteSSRComponent = require("./createRemoteSSRComponent");
29
+ var import_SSRLiveReload = require("./SSRLiveReload");
30
30
  // Annotate the CommonJS export names for ESM import in node:
31
31
  0 && (module.exports = {
32
- LiveReload,
33
- MFReactComponent,
34
- collectAssets,
32
+ SSRLiveReload,
33
+ collectSSRAssets,
34
+ createRemoteSSRComponent,
35
35
  ...require("@module-federation/enhanced/runtime")
36
36
  });