@module-federation/nextjs-mf 5.2.2 → 5.3.1

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.
Files changed (49) hide show
  1. package/lib/ModuleFederationPlugin.js +80 -0
  2. package/lib/NextFederationPlugin.js +524 -1
  3. package/lib/_virtual/_tslib.js +101 -0
  4. package/lib/client/CombinedPages.d.ts +28 -0
  5. package/lib/client/CombinedPages.d.ts.map +1 -0
  6. package/lib/client/CombinedPages.js +60 -0
  7. package/lib/client/MFClient.d.ts +70 -0
  8. package/lib/client/MFClient.d.ts.map +1 -0
  9. package/lib/client/MFClient.js +197 -0
  10. package/lib/client/RemoteContainer.d.ts +58 -0
  11. package/lib/client/RemoteContainer.d.ts.map +1 -0
  12. package/lib/client/RemoteContainer.js +161 -0
  13. package/lib/client/RemotePages.d.ts +48 -0
  14. package/lib/client/RemotePages.d.ts.map +1 -0
  15. package/lib/client/RemotePages.js +168 -0
  16. package/lib/client/UrlNode.d.ts +18 -0
  17. package/lib/client/UrlNode.d.ts.map +1 -0
  18. package/lib/client/UrlNode.js +162 -0
  19. package/lib/client/helpers.d.ts +17 -0
  20. package/lib/client/helpers.d.ts.map +1 -0
  21. package/lib/client/helpers.js +108 -0
  22. package/lib/client/useMFClient.d.ts +25 -0
  23. package/lib/client/useMFClient.d.ts.map +1 -0
  24. package/lib/client/useMFClient.js +79 -0
  25. package/lib/client/useMFRemote.d.ts +17 -0
  26. package/lib/client/useMFRemote.d.ts.map +1 -0
  27. package/lib/client/useMFRemote.js +72 -0
  28. package/lib/include-defaults.js +1 -3
  29. package/lib/internal.js +241 -0
  30. package/lib/loaders/UrlNode.js +209 -0
  31. package/lib/loaders/nextPageMapLoader.js +63 -13
  32. package/lib/loaders/patchNextClientPageLoader.js +53 -0
  33. package/lib/node-plugin/streaming/CommonJsChunkLoadingPlugin.js +86 -0
  34. package/lib/node-plugin/streaming/LoadFileChunkLoadingRuntimeModule.js +410 -0
  35. package/lib/node-plugin/streaming/NodeRuntime.js +147 -0
  36. package/lib/node-plugin/streaming/index.js +44 -0
  37. package/lib/node-plugin/streaming/loadScript.js +55 -0
  38. package/lib/plugins/DevHmrFixInvalidPongPlugin.js +60 -0
  39. package/lib/utils.js +14 -3
  40. package/node-plugin/README.md +27 -0
  41. package/node-plugin/package.json +4 -0
  42. package/node-plugin/streaming/CommonJsChunkLoadingPlugin.js +89 -0
  43. package/node-plugin/streaming/LoadFileChunkLoadingRuntimeModule.js +410 -0
  44. package/node-plugin/streaming/NodeRuntime.js +245 -0
  45. package/node-plugin/streaming/index.js +42 -0
  46. package/node-plugin/streaming/loadScript.js +51 -0
  47. package/package.json +22 -5
  48. package/tsconfig.json +33 -0
  49. package/lib/NextFederationPlugin2.js +0 -0
@@ -0,0 +1,209 @@
1
+ /**
2
+ * 🛑🛑🛑 Attention! 🛑🛑🛑
3
+ * Do not add type definitions to this file!!
4
+ * It already exists in src/client folder.
5
+ * So remove this file and import ts version from src/client/UrlNode.
6
+ *
7
+ * This file was add just for proper compilation of JS files without any rollup warnings:
8
+ * "(!) Unresolved dependencies"
9
+ * "nextjs-mf/src/client/UrlNode (imported by nextjs-mf/src/client/UrlNode?commonjs-external)"
10
+ */
11
+
12
+ /**
13
+ * This class provides a logic of sorting dynamic routes in NextJS.
14
+ *
15
+ * It was copied from
16
+ * @see https://github.com/vercel/next.js/blob/canary/packages/next/shared/lib/router/utils/sorted-routes.ts
17
+ */
18
+ export class UrlNode {
19
+ placeholder = true;
20
+ children = new Map();
21
+ slugName = null;
22
+ restSlugName = null;
23
+ optionalRestSlugName = null;
24
+
25
+ insert(urlPath) {
26
+ this._insert(urlPath.split('/').filter(Boolean), [], false);
27
+ }
28
+
29
+ smoosh() {
30
+ return this._smoosh();
31
+ }
32
+
33
+ _smoosh(prefix = '/') {
34
+ const childrenPaths = [...this.children.keys()].sort();
35
+ if (this.slugName !== null) {
36
+ childrenPaths.splice(childrenPaths.indexOf('[]'), 1);
37
+ }
38
+ if (this.restSlugName !== null) {
39
+ childrenPaths.splice(childrenPaths.indexOf('[...]'), 1);
40
+ }
41
+ if (this.optionalRestSlugName !== null) {
42
+ childrenPaths.splice(childrenPaths.indexOf('[[...]]'), 1);
43
+ }
44
+
45
+ const routes = childrenPaths
46
+ .map((c) => this.children.get(c)._smoosh(`${prefix}${c}/`))
47
+ .reduce((prev, curr) => [...prev, ...curr], []);
48
+
49
+ if (this.slugName !== null) {
50
+ routes.push(
51
+ ...this.children.get('[]')._smoosh(`${prefix}[${this.slugName}]/`)
52
+ );
53
+ }
54
+
55
+ if (!this.placeholder) {
56
+ const r = prefix === '/' ? '/' : prefix.slice(0, -1);
57
+ if (this.optionalRestSlugName != null) {
58
+ throw new Error(
59
+ `You cannot define a route with the same specificity as a optional catch-all route ("${r}" and "${r}[[...${this.optionalRestSlugName}]]").`
60
+ );
61
+ }
62
+
63
+ routes.unshift(r);
64
+ }
65
+
66
+ if (this.restSlugName !== null) {
67
+ routes.push(
68
+ ...this.children
69
+ .get('[...]')
70
+ ._smoosh(`${prefix}[...${this.restSlugName}]/`)
71
+ );
72
+ }
73
+
74
+ if (this.optionalRestSlugName !== null) {
75
+ routes.push(
76
+ ...this.children
77
+ .get('[[...]]')
78
+ ._smoosh(`${prefix}[[...${this.optionalRestSlugName}]]/`)
79
+ );
80
+ }
81
+
82
+ return routes;
83
+ }
84
+
85
+ _insert(urlPaths, slugNames, isCatchAll) {
86
+ if (urlPaths.length === 0) {
87
+ this.placeholder = false;
88
+ return;
89
+ }
90
+
91
+ if (isCatchAll) {
92
+ throw new Error(`Catch-all must be the last part of the URL.`);
93
+ }
94
+
95
+ // The next segment in the urlPaths list
96
+ let nextSegment = urlPaths[0];
97
+
98
+ // Check if the segment matches `[something]`
99
+ if (nextSegment.startsWith('[') && nextSegment.endsWith(']')) {
100
+ // Strip `[` and `]`, leaving only `something`
101
+ let segmentName = nextSegment.slice(1, -1);
102
+
103
+ let isOptional = false;
104
+ if (segmentName.startsWith('[') && segmentName.endsWith(']')) {
105
+ // Strip optional `[` and `]`, leaving only `something`
106
+ segmentName = segmentName.slice(1, -1);
107
+ isOptional = true;
108
+ }
109
+
110
+ if (segmentName.startsWith('...')) {
111
+ // Strip `...`, leaving only `something`
112
+ segmentName = segmentName.substring(3);
113
+ isCatchAll = true;
114
+ }
115
+
116
+ if (segmentName.startsWith('[') || segmentName.endsWith(']')) {
117
+ throw new Error(
118
+ `Segment names may not start or end with extra brackets ('${segmentName}').`
119
+ );
120
+ }
121
+
122
+ if (segmentName.startsWith('.')) {
123
+ throw new Error(
124
+ `Segment names may not start with erroneous periods ('${segmentName}').`
125
+ );
126
+ }
127
+
128
+ const handleSlug = function handleSlug(previousSlug, nextSlug) {
129
+ if (previousSlug !== null) {
130
+ // If the specific segment already has a slug but the slug is not `something`
131
+ // This prevents collisions like:
132
+ // pages/[post]/index.js
133
+ // pages/[id]/index.js
134
+ // Because currently multiple dynamic params on the same segment level are not supported
135
+ if (previousSlug !== nextSlug) {
136
+ // TODO: This error seems to be confusing for users, needs an error link, the description can be based on above comment.
137
+ throw new Error(
138
+ `You cannot use different slug names for the same dynamic path ('${previousSlug}' !== '${nextSlug}').`
139
+ );
140
+ }
141
+ }
142
+
143
+ slugNames.forEach((slug) => {
144
+ if (slug === nextSlug) {
145
+ throw new Error(
146
+ `You cannot have the same slug name "${nextSlug}" repeat within a single dynamic path`
147
+ );
148
+ }
149
+
150
+ if (slug.replace(/\W/g, '') === nextSegment.replace(/\W/g, '')) {
151
+ throw new Error(
152
+ `You cannot have the slug names "${slug}" and "${nextSlug}" differ only by non-word symbols within a single dynamic path`
153
+ );
154
+ }
155
+ });
156
+
157
+ slugNames.push(nextSlug);
158
+ };
159
+
160
+ if (isCatchAll) {
161
+ if (isOptional) {
162
+ if (this.restSlugName != null) {
163
+ throw new Error(
164
+ `You cannot use both an required and optional catch-all route at the same level ("[...${this.restSlugName}]" and "${urlPaths[0]}" ).`
165
+ );
166
+ }
167
+
168
+ handleSlug(this.optionalRestSlugName, segmentName);
169
+ // slugName is kept as it can only be one particular slugName
170
+ this.optionalRestSlugName = segmentName;
171
+ // nextSegment is overwritten to [[...]] so that it can later be sorted specifically
172
+ nextSegment = '[[...]]';
173
+ } else {
174
+ if (this.optionalRestSlugName != null) {
175
+ throw new Error(
176
+ `You cannot use both an optional and required catch-all route at the same level ("[[...${this.optionalRestSlugName}]]" and "${urlPaths[0]}").`
177
+ );
178
+ }
179
+
180
+ handleSlug(this.restSlugName, segmentName);
181
+ // slugName is kept as it can only be one particular slugName
182
+ this.restSlugName = segmentName;
183
+ // nextSegment is overwritten to [...] so that it can later be sorted specifically
184
+ nextSegment = '[...]';
185
+ }
186
+ } else {
187
+ if (isOptional) {
188
+ throw new Error(
189
+ `Optional route parameters are not yet supported ("${urlPaths[0]}").`
190
+ );
191
+ }
192
+ handleSlug(this.slugName, segmentName);
193
+ // slugName is kept as it can only be one particular slugName
194
+ this.slugName = segmentName;
195
+ // nextSegment is overwritten to [] so that it can later be sorted specifically
196
+ nextSegment = '[]';
197
+ }
198
+ }
199
+
200
+ // If this UrlNode doesn't have the nextSegment yet we create a new child UrlNode
201
+ if (!this.children.has(nextSegment)) {
202
+ this.children.set(nextSegment, new UrlNode());
203
+ }
204
+
205
+ this.children
206
+ .get(nextSegment)
207
+ ._insert(urlPaths.slice(1), slugNames, isCatchAll);
208
+ }
209
+ }
@@ -1,23 +1,32 @@
1
1
  const fg = require('fast-glob');
2
2
  const fs = require('fs');
3
3
 
4
+ // TODO: import UrlNode from ./client folder when whole project migrates on TypeScript (but right now using JS copy of this class)
5
+ // const UrlNode = require('../client/UrlNode').UrlNode;
6
+ const UrlNode = require('./UrlNode').UrlNode;
7
+
4
8
  /**
5
9
  * Webpack loader which prepares MF map for NextJS pages
6
10
  *
7
11
  * @type {(this: import("webpack").LoaderContext<{}>, content: string) => string>}
8
12
  */
9
13
  function nextPageMapLoader() {
10
- const pages = getNextPages(this.rootContext);
11
- const pageMap = preparePageMap(pages);
12
-
13
14
  // const [pagesRoot] = getNextPagesRoot(this.rootContext);
14
15
  // this.addContextDependency(pagesRoot);
16
+ const opts = this.getOptions();
17
+ const pages = getNextPages(this.rootContext);
15
18
 
16
- const result = `module.exports = {
17
- default: ${JSON.stringify(pageMap)},
18
- };`;
19
+ let result = '';
20
+ if (Object.hasOwnProperty.call(opts, 'v2')) {
21
+ result = preparePageMapV2(pages);
22
+ } else {
23
+ result = preparePageMap(pages);
24
+ }
19
25
 
20
- this.callback(null, result);
26
+ this.callback(
27
+ null,
28
+ `module.exports = { default: ${JSON.stringify(result)} };`
29
+ );
21
30
  }
22
31
 
23
32
  /**
@@ -38,6 +47,7 @@ function exposeNextjsPages(cwd) {
38
47
 
39
48
  const exposesWithPageMap = {
40
49
  './pages-map': `${__filename}!${__filename}`,
50
+ './pages-map-v2': `${__filename}?v2!${__filename}`,
41
51
  ...pageModulesMap,
42
52
  };
43
53
 
@@ -113,13 +123,53 @@ function sanitizePagePath(item) {
113
123
  function preparePageMap(pages) {
114
124
  const result = {};
115
125
 
116
- pages.forEach((pagePath) => {
117
- const page = sanitizePagePath(pagePath);
118
- let key =
119
- '/' +
120
- page.replace(/\[\.\.\.[^\]]+\]/gi, '*').replace(/\[([^\]]+)\]/gi, ':$1');
126
+ const clearedPages = pages.map((p) => `/${sanitizePagePath(p)}`);
127
+
128
+ // getSortedRoutes @see https://github.com/vercel/next.js/blob/canary/packages/next/shared/lib/router/utils/sorted-routes.ts
129
+ const root = new UrlNode();
130
+ clearedPages.forEach((pagePath) => root.insert(pagePath));
131
+ // Smoosh will then sort those sublevels up to the point where you get the correct route definition priority
132
+ const sortedPages = root.smoosh();
133
+
134
+ sortedPages.forEach((page) => {
135
+ let key = page
136
+ .replace(/\[\.\.\.[^\]]+\]/gi, '*')
137
+ .replace(/\[([^\]]+)\]/gi, ':$1');
121
138
  key = key.replace(/^\/pages\//, '/').replace(/\/index$/, '') || '/';
122
- result[key] = `./${page}`;
139
+ result[key] = `.${page}`;
140
+ });
141
+
142
+ return result;
143
+ }
144
+
145
+ /**
146
+ * Create MF list of NextJS pages
147
+ *
148
+ * From
149
+ * ['pages/index.tsx', 'pages/storage/[...slug].tsx', 'pages/storage/index.tsx']
150
+ * Getting the following map
151
+ * {
152
+ * '/': './pages/index',
153
+ * '/storage': './pages/storage/index'
154
+ * '/storage/[...slug]': './pages/storage/[...slug]',
155
+ * }
156
+ *
157
+ * @type {(pages: string[]) => {[key: string]: string}}
158
+ */
159
+ function preparePageMapV2(pages) {
160
+ const result = {};
161
+
162
+ const clearedPages = pages.map((p) => `/${sanitizePagePath(p)}`);
163
+
164
+ // getSortedRoutes @see https://github.com/vercel/next.js/blob/canary/packages/next/shared/lib/router/utils/sorted-routes.ts
165
+ const root = new UrlNode();
166
+ clearedPages.forEach((pagePath) => root.insert(pagePath));
167
+ // Smoosh will then sort those sublevels up to the point where you get the correct route definition priority
168
+ const sortedPages = root.smoosh();
169
+
170
+ sortedPages.forEach((page) => {
171
+ let key = page.replace(/^\/pages\//, '/').replace(/\/index$/, '') || '/';
172
+ result[key] = `.${page}`;
123
173
  });
124
174
 
125
175
  return result;
@@ -0,0 +1,53 @@
1
+ const path = require('path');
2
+
3
+ /**
4
+ * This webpack loader patches next/dist/client/page-loader.js file.
5
+ * Also it requires `include-defaults.js` with required shared libs
6
+ *
7
+ * @type {(this: import("webpack").LoaderContext<{}>, content: string) => string>}
8
+ */
9
+ function patchNextClientPageLoader(content) {
10
+ if (content.includes('MFClient')) {
11
+ // If MFClient already applied then skip patch
12
+ return content;
13
+ }
14
+
15
+ // avoid absolute paths as they break hashing when the root for the project is moved
16
+ // @see https://webpack.js.org/contribute/writing-a-loader/#absolute-paths
17
+ const pathIncludeDefaults = path.relative(
18
+ this.context,
19
+ path.resolve(__dirname, '../include-defaults.js')
20
+ );
21
+ const pathMFClient = path.relative(
22
+ this.context,
23
+ path.resolve(__dirname, '../client/MFClient.js')
24
+ );
25
+
26
+ patchedContent = content.replace(
27
+ 'exports.default = PageLoader;',
28
+ `
29
+ require(${JSON.stringify(pathIncludeDefaults)});
30
+ const MFClient = require(${JSON.stringify(pathMFClient)}).MFClient;
31
+
32
+ class PageLoaderExtended extends PageLoader {
33
+ constructor(buildId, assetPrefix) {
34
+ super(buildId, assetPrefix);
35
+ global.mf_client = new MFClient(this);
36
+ }
37
+
38
+ _getPageListOriginal() {
39
+ return super.getPageList();
40
+ }
41
+
42
+ getPageList() {
43
+ return global.mf_client.getPageList();
44
+ }
45
+ }
46
+ exports.default = PageLoaderExtended;
47
+ `
48
+ );
49
+
50
+ return patchedContent;
51
+ }
52
+
53
+ module.exports = patchNextClientPageLoader;
@@ -0,0 +1,86 @@
1
+ 'use strict';
2
+
3
+ var LoadFileChunkLoadingRuntimeModule = require('./LoadFileChunkLoadingRuntimeModule.js');
4
+
5
+ const RuntimeGlobals = require('webpack/lib/RuntimeGlobals');
6
+ const StartupChunkDependenciesPlugin = require('webpack/lib/runtime/StartupChunkDependenciesPlugin');
7
+ // const ChunkLoadingRuntimeModule = require('webpack/lib/node/ReadFileChunkLoadingRuntimeModule')
8
+ class CommonJsChunkLoadingPlugin {
9
+ constructor(options) {
10
+ this.options = options || {};
11
+ this._asyncChunkLoading = this.options.asyncChunkLoading;
12
+ }
13
+
14
+ /**
15
+ * Apply the plugin
16
+ * @param {Compiler} compiler the compiler instance
17
+ * @returns {void}
18
+ */
19
+ apply(compiler) {
20
+ const chunkLoadingValue = this._asyncChunkLoading
21
+ ? 'async-node'
22
+ : 'require';
23
+ new StartupChunkDependenciesPlugin({
24
+ chunkLoading: chunkLoadingValue,
25
+ asyncChunkLoading: this._asyncChunkLoading,
26
+ }).apply(compiler);
27
+ compiler.hooks.thisCompilation.tap(
28
+ 'CommonJsChunkLoadingPlugin',
29
+ (compilation) => {
30
+ const onceForChunkSet = new WeakSet();
31
+ const handler = (chunk, set) => {
32
+ if (onceForChunkSet.has(chunk)) return;
33
+ onceForChunkSet.add(chunk);
34
+ set.add(RuntimeGlobals.moduleFactoriesAddOnly);
35
+ set.add(RuntimeGlobals.hasOwnProperty);
36
+ compilation.addRuntimeModule(
37
+ chunk,
38
+ new LoadFileChunkLoadingRuntimeModule(set, this.options, {
39
+ webpack: compiler.webpack,
40
+ })
41
+ );
42
+ };
43
+
44
+ compilation.hooks.runtimeRequirementInTree
45
+ .for(RuntimeGlobals.ensureChunkHandlers)
46
+ .tap('CommonJsChunkLoadingPlugin', handler);
47
+ compilation.hooks.runtimeRequirementInTree
48
+ .for(RuntimeGlobals.hmrDownloadUpdateHandlers)
49
+ .tap('CommonJsChunkLoadingPlugin', handler);
50
+ compilation.hooks.runtimeRequirementInTree
51
+ .for(RuntimeGlobals.hmrDownloadManifest)
52
+ .tap('CommonJsChunkLoadingPlugin', handler);
53
+ compilation.hooks.runtimeRequirementInTree
54
+ .for(RuntimeGlobals.baseURI)
55
+ .tap('CommonJsChunkLoadingPlugin', handler);
56
+ compilation.hooks.runtimeRequirementInTree
57
+ .for(RuntimeGlobals.externalInstallChunk)
58
+ .tap('CommonJsChunkLoadingPlugin', handler);
59
+ compilation.hooks.runtimeRequirementInTree
60
+ .for(RuntimeGlobals.onChunksLoaded)
61
+ .tap('CommonJsChunkLoadingPlugin', handler);
62
+
63
+ compilation.hooks.runtimeRequirementInTree
64
+ .for(RuntimeGlobals.ensureChunkHandlers)
65
+ .tap('CommonJsChunkLoadingPlugin', (chunk, set) => {
66
+ set.add(RuntimeGlobals.getChunkScriptFilename);
67
+ });
68
+ compilation.hooks.runtimeRequirementInTree
69
+ .for(RuntimeGlobals.hmrDownloadUpdateHandlers)
70
+ .tap('CommonJsChunkLoadingPlugin', (chunk, set) => {
71
+ set.add(RuntimeGlobals.getChunkUpdateScriptFilename);
72
+ set.add(RuntimeGlobals.moduleCache);
73
+ set.add(RuntimeGlobals.hmrModuleData);
74
+ set.add(RuntimeGlobals.moduleFactoriesAddOnly);
75
+ });
76
+ compilation.hooks.runtimeRequirementInTree
77
+ .for(RuntimeGlobals.hmrDownloadManifest)
78
+ .tap('CommonJsChunkLoadingPlugin', (chunk, set) => {
79
+ set.add(RuntimeGlobals.getUpdateManifestFilename);
80
+ });
81
+ }
82
+ );
83
+ }
84
+ }
85
+
86
+ module.exports = CommonJsChunkLoadingPlugin;