@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,245 @@
1
+ 'use strict';
2
+ // possible remote evaluators
3
+ // this depends on the chunk format selected.
4
+ // commonjs2 - it think, is lazily evaluated - beware
5
+ // const remote = eval(scriptContent + '\n try{' + moduleName + '}catch(e) { null; };');
6
+ // commonjs - fine to use but exports marker doesnt exist
7
+ // const remote = eval('let exports = {};' + scriptContent + 'exports');
8
+ // commonjs-module, ideal since it returns a commonjs module format
9
+ // const remote = eval(scriptContent + 'module.exports')
10
+
11
+ // Note on ESM.
12
+ // Its possible to use ESM import, but its impossible to invalidate the module cache
13
+ // So once something is imported, its stuck. This is problematic with at-runtime since we want to hot reload node
14
+ // if ESM were used, youd be forced to restart the process to re-import modules or use a worker pool
15
+ // Workaround is possible with query string on end of request, but this leaks memory badly
16
+ // with commonjs, we can at least reset the require cache to "reboot" webpack runtime
17
+ // It *can* leak memory, but ive not been able to replicate this to an extent that would be concerning.
18
+ // ESM WILL leak memory, big difference.
19
+ // Im talking with TC39 about a proposal around "virtual module trees" which would solve many problems.
20
+ // VMT is like Realms but better - easiest analogy would be like forking the main thread, without going off main thread
21
+ // VMT allows for scope isolation, but still allows reflection and non-primitive memory pointers to be shared - perfect for MFP
22
+
23
+ //TODO: should use extractUrlAndGlobal from internal.js
24
+ //TODO: should use Template system like LoadFileChunk runtime does.
25
+ //TODO: should use vm.runInThisContext instead of eval
26
+ //TODO: global.webpackChunkLoad could use a better convention? I have to use a special http client to get out of my infra firewall
27
+ const executeLoadTemplate = `
28
+ function executeLoad(remoteUrl) {
29
+ console.log('remoteUrl',remoteUrl)
30
+ const scriptUrl = remoteUrl.split("@")[1];
31
+ const moduleName = remoteUrl.split("@")[0];
32
+ console.log("executing remote load", scriptUrl);
33
+ return new Promise(function (resolve, reject) {
34
+
35
+ (global.webpackChunkLoad || fetch)(scriptUrl).then(function(res){
36
+ return res.text();
37
+ }).then(function(scriptContent){
38
+ try {
39
+ const remote = eval(scriptContent + 'module.exports');
40
+ /* TODO: need something like a chunk loading queue, this can lead to async issues
41
+ if two containers load the same remote, they can overwrite global scope
42
+ should check someone is already loading remote and await that */
43
+ global.__remote_scope__[moduleName] = remote[moduleName] || remote
44
+ resolve(global.__remote_scope__[moduleName])
45
+ } catch(e) {
46
+ console.error('problem executing remote module', moduleName);
47
+ reject(e);
48
+ }
49
+ }).catch((e)=>{
50
+ console.error('failed to fetch remote', moduleName, scriptUrl);
51
+ console.error(e);
52
+ reject(null)
53
+ })
54
+ }).catch((e)=>{
55
+ console.error('error',e);
56
+ console.warn(moduleName,'is offline, returning fake remote')
57
+ return {
58
+ fake: true,
59
+ get:(arg)=>{
60
+ console.log('faking', arg,'module on', moduleName);
61
+
62
+ return ()=> Promise.resolve();
63
+ },
64
+ init:()=>{}
65
+ }
66
+ })
67
+ }
68
+ `;
69
+
70
+ function buildRemotes(mfConf, webpack) {
71
+ return Object.entries(mfConf.remotes || {}).reduce(
72
+ (acc, [name, config]) => {
73
+ // if its already been converted into promise, dont do it again
74
+ if(config.startsWith('promise ') || config.startsWith('external ')){
75
+ acc.buildTime[name] = config;
76
+ return acc;
77
+ }
78
+ /*
79
+ TODO: global remote scope object should go into webpack runtime as a runtime requirement
80
+ this can be done by referencing my LoadFile, CommonJs plugins in this directory.
81
+ */
82
+ const [global, url] = config.split('@');
83
+ const loadTemplate = `promise new Promise((resolve)=>{
84
+ if(!global.__remote_scope__) {
85
+ // create a global scope for container, similar to how remotes are set on window in the browser
86
+ global.__remote_scope__ = {
87
+ _config: {},
88
+ }
89
+ }
90
+
91
+ global.__remote_scope__._config[${JSON.stringify(global)}] = ${JSON.stringify(url)};
92
+
93
+ ${executeLoadTemplate}
94
+ resolve(executeLoad(${JSON.stringify(config)}))
95
+ }).then(remote=>{
96
+ console.log(remote);
97
+
98
+ return {
99
+ get: remote.get,
100
+ init: (args)=> {
101
+ console.log(args)
102
+ return remote.init(args)
103
+ }
104
+ }
105
+ });
106
+ `;
107
+ acc.buildTime[name] = loadTemplate;
108
+ return acc;
109
+ },
110
+ { runtime: {}, buildTime: {}, hot: {} }
111
+ );
112
+ //old design
113
+ return Object.entries(mfConf.remotes || {}).reduce(
114
+ (acc, [name, config]) => {
115
+ const hasMiddleware = config.startsWith('middleware ');
116
+ let middleware;
117
+ if (hasMiddleware) {
118
+ middleware = config.split('middleware ')[1];
119
+ } else {
120
+ middleware = `Promise.resolve(${JSON.stringify(config)})`;
121
+ }
122
+
123
+ const templateStart = `
124
+ var ${webpack.RuntimeGlobals.require} = ${
125
+ webpack.RuntimeGlobals.require
126
+ } ? ${
127
+ webpack.RuntimeGlobals.require
128
+ } : typeof arguments !== 'undefined' ? arguments[2] : false;
129
+ ${executeLoadTemplate}
130
+ global.loadedRemotes = global.loadedRemotes || {};
131
+ if (global.loadedRemotes[${JSON.stringify(name)}]) {
132
+ return global.loadedRemotes[${JSON.stringify(name)}]
133
+ }
134
+ // if using modern output, then there are no arguments on the parent function scope, thus we need to get it via a window global.
135
+
136
+ var shareScope = (${webpack.RuntimeGlobals.require} && ${
137
+ webpack.RuntimeGlobals.shareScopeMap
138
+ }) ? ${
139
+ webpack.RuntimeGlobals.shareScopeMap
140
+ } : global.__webpack_share_scopes__
141
+ var name = ${JSON.stringify(name)}
142
+ `;
143
+ const template = `(remotesConfig) => new Promise((res) => {
144
+ console.log('in template promise',JSON.stringify(remotesConfig))
145
+ executeLoad(remotesConfig).then((remote) => {
146
+
147
+ return Promise.resolve(remote.init(shareScope.default)).then(() => {
148
+ return remote
149
+ })
150
+ })
151
+ .then(function (remote) {
152
+ const proxy = {
153
+ get: remote.get,
154
+ chunkMap: remote.chunkMap,
155
+ path: remotesConfig.toString(),
156
+ init: (arg) => {
157
+ try {
158
+ return remote.init(shareScope.default)
159
+ } catch (e) {
160
+ console.log('remote container already initialized')
161
+ }
162
+ }
163
+ }
164
+ if (remote.fake) {
165
+ res(proxy);
166
+ return null
167
+ }
168
+
169
+ Object.assign(global.loadedRemotes, {
170
+ [name]: proxy
171
+ });
172
+
173
+ res(global.loadedRemotes[name])
174
+ })
175
+
176
+
177
+ })`;
178
+
179
+ acc.runtime[name] = `()=> ${middleware}.then((remoteConfig)=>{
180
+ console.log('remoteConfig runtime',remoteConfig);
181
+ if(!global.REMOTE_CONFIG) {
182
+ global.REMOTE_CONFIG = {};
183
+ }
184
+ global.REMOTE_CONFIG[${JSON.stringify(name)}] = remoteConfig;
185
+ ${templateStart}
186
+ const loadTemplate = ${template};
187
+ return loadTemplate(remoteConfig)
188
+ })`;
189
+
190
+ acc.buildTime[name] = `promise ${middleware}.then((remoteConfig)=>{
191
+ if(!global.REMOTE_CONFIG) {
192
+ global.REMOTE_CONFIG = {};
193
+ }
194
+ console.log('remoteConfig buildtime',remoteConfig);
195
+ global.REMOTE_CONFIG[${JSON.stringify(name)}] = remoteConfig;
196
+ ${templateStart};
197
+ const loadTemplate = ${template};
198
+ return loadTemplate(remoteConfig)
199
+ })`;
200
+
201
+ acc.hot[name] = `()=> ${middleware}`;
202
+
203
+ return acc;
204
+ },
205
+ { runtime: {}, buildTime: {}, hot: {} }
206
+ );
207
+ }
208
+
209
+ class StreamingFederation {
210
+ constructor({ experiments, ...options }, context) {
211
+ this.options = options || {};
212
+ this.context = context || {};
213
+ this.experiments = experiments || {};
214
+ }
215
+
216
+ apply(compiler) {
217
+ // When used with Next.js, context is needed to use Next.js webpack
218
+ const { webpack } = compiler;
219
+
220
+ const { buildTime, runtime, hot } = buildRemotes(
221
+ this.options,
222
+ webpack || require('webpack')
223
+ );
224
+ const defs = {
225
+ 'process.env.REMOTES': runtime,
226
+ 'process.env.REMOTE_CONFIG': hot,
227
+ };
228
+
229
+ // new ((webpack && webpack.DefinePlugin) || require("webpack").DefinePlugin)(
230
+ // defs
231
+ // ).apply(compiler);
232
+
233
+ const pluginOptions = {
234
+ ...this.options,
235
+ remotes: buildTime,
236
+ };
237
+
238
+ new (this.context.ModuleFederationPlugin || (webpack && webpack.container.ModuleFederationPlugin) ||
239
+ require('webpack/lib/container/ModuleFederationPlugin'))(
240
+ pluginOptions
241
+ ).apply(compiler);
242
+ }
243
+ }
244
+
245
+ export default StreamingFederation;
@@ -0,0 +1,42 @@
1
+ import CommonJsChunkLoadingPlugin from './CommonJsChunkLoadingPlugin';
2
+
3
+ class NodeSoftwareStreamRuntime {
4
+ constructor(options, context) {
5
+ this.options = options || {};
6
+ this.context = context || {};
7
+ }
8
+
9
+ apply(compiler) {
10
+ if (compiler.options.target) {
11
+ console.warn(
12
+ `target should be set to false while using NodeSoftwareStreamRuntime plugin, actual target: ${compiler.options.target}`
13
+ );
14
+ }
15
+
16
+ // When used with Next.js, context is needed to use Next.js webpack
17
+ const { webpack } = compiler;
18
+
19
+ // This will enable CommonJsChunkFormatPlugin
20
+ compiler.options.output.chunkFormat = 'commonjs';
21
+ // This will force async chunk loading
22
+ compiler.options.output.chunkLoading = 'async-node';
23
+ // Disable default config
24
+ compiler.options.output.enabledChunkLoadingTypes = false;
25
+
26
+ new ((webpack && webpack.node && webpack.node.NodeEnvironmentPlugin) ||
27
+ require('webpack/lib/node/NodeEnvironmentPlugin'))({
28
+ infrastructureLogging: compiler.options.infrastructureLogging,
29
+ }).apply(compiler);
30
+ new ((webpack && webpack.node && webpack.node.NodeTargetPlugin) ||
31
+ require('webpack/lib/node/NodeTargetPlugin'))().apply(compiler);
32
+ new CommonJsChunkLoadingPlugin({
33
+ asyncChunkLoading: true,
34
+ name: this.options.name,
35
+ remotes: this.options.remotes,
36
+ baseURI: compiler.options.output.publicPath,
37
+ promiseBaseURI: this.options.promiseBaseURI,
38
+ }).apply(compiler);
39
+ }
40
+ }
41
+
42
+ export default NodeSoftwareStreamRuntime;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * loadScript(baseURI, fileName, cb)
3
+ * loadScript(scriptUrl, cb)
4
+ */
5
+
6
+ module.exports = `
7
+ function loadScript(url,cb,chunkID) {
8
+ var url;
9
+ var cb = arguments[arguments.length - 1];
10
+ if (typeof cb !== "function") {
11
+ throw new Error("last argument should be a function");
12
+ }
13
+ if (arguments.length === 2) {
14
+ url = arguments[0];
15
+ } else if (arguments.length === 3) {
16
+ url = new URL(arguments[1], arguments[0]).toString();
17
+ } else {
18
+ throw new Error("invalid number of arguments");
19
+ }
20
+ if(global.webpackChunkLoad){
21
+ global.webpackChunkLoad(url).then(function(resp){
22
+ return resp.text();
23
+ }).then(function(rawData){
24
+ cb(null, rawData);
25
+ }).catch(function(err){
26
+ console.error('Federated Chunk load failed', error);
27
+ return cb(error)
28
+ });
29
+ } else {
30
+ //TODO https support
31
+ let request = (url.startsWith('https') ? require('https') : require('http')).get(url, function (resp) {
32
+ if (resp.statusCode === 200) {
33
+ let rawData = '';
34
+ resp.setEncoding('utf8');
35
+ resp.on('data', chunk => {
36
+ rawData += chunk;
37
+ });
38
+ resp.on('end', () => {
39
+ cb(null, rawData);
40
+ });
41
+ } else {
42
+ cb(resp);
43
+ }
44
+ });
45
+ request.on('error', error => {
46
+ console.error('Federated Chunk load failed', error);
47
+ return cb(error)
48
+ });
49
+ }
50
+ }
51
+ `;
package/package.json CHANGED
@@ -1,33 +1,50 @@
1
1
  {
2
2
  "public": true,
3
3
  "name": "@module-federation/nextjs-mf",
4
- "version": "5.2.2",
4
+ "version": "5.3.1",
5
5
  "description": "Module Federation helper for NextJS",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/index.d.ts",
8
8
  "repository": "https://github.com/module-federation/nextjs-mf",
9
9
  "author": "Zack Jackson <zackary.l.jackson@gmail.com>",
10
+ "contributors": [
11
+ "Pavel Chertorogov, nodkz <pavel.chertorogov@gmail.com> (www.ps.kz)"
12
+ ],
10
13
  "license": "MIT",
11
14
  "scripts": {
12
- "demo": "yarn build && cd demo && yarn install && yarn dev",
15
+ "demo": "sleep 3 && cd demo && yarn install && yarn dev",
16
+ "dev": "rm -rf lib && concurrently \"yarn sync-files --watch\" \"yarn compile --watch\" \"yarn demo\"",
13
17
  "prettier": "prettier --write \"**/*.{js,json,md,ts,tsx}\"",
14
- "build": "rm -rf lib && cp -r ./src/ lib/ && rollup -c",
18
+ "sync-files": "cpx \"src/**/*.js\" lib/",
19
+ "compile": "rollup -c",
20
+ "build": "rm -rf lib && yarn sync-files && yarn compile",
15
21
  "prepublishOnly": "yarn build"
16
22
  },
17
23
  "dependencies": {
18
24
  "chalk": "^4.0.0",
25
+ "eventemitter3": "^4.0.7",
19
26
  "fast-glob": "^3.2.11"
20
27
  },
21
28
  "devDependencies": {
22
29
  "@rollup/plugin-commonjs": "^22.0.2",
23
30
  "@rollup/plugin-multi-entry": "^4.1.0",
24
31
  "@rollup/plugin-node-resolve": "^13.3.0",
25
- "next": "11.0.1",
32
+ "@types/react": "^18.0.19",
33
+ "concurrently": "^7.3.0",
34
+ "cpx": "^1.5.0",
35
+ "next": "12.3.2",
26
36
  "prettier": "2.3.2",
37
+ "react": "^18.2.0",
27
38
  "rollup": "^2.78.1",
28
39
  "rollup-obfuscator": "^3.0.1",
29
40
  "rollup-plugin-node-builtins": "^2.1.2",
30
41
  "rollup-plugin-node-globals": "^1.4.0",
31
- "webpack": "5.45.1"
42
+ "rollup-plugin-typescript2": "^0.33.0",
43
+ "tslib": "^2.4.0",
44
+ "typescript": "^4.8.2",
45
+ "webpack": "5.64.4"
46
+ },
47
+ "peerDependencies": {
48
+ "react": "^17.0.0 || ^18.0.0"
32
49
  }
33
50
  }
package/tsconfig.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es5",
4
+ "declaration": true,
5
+ "declarationMap": true,
6
+ "module": "ES2020",
7
+ "esModuleInterop": true,
8
+ "moduleResolution": "node",
9
+ "sourceMap": true,
10
+ "downlevelIteration": true,
11
+ "resolveJsonModule": true,
12
+ "strict": false,
13
+ "allowJs": true,
14
+ "strictBindCallApply": true,
15
+ "strictNullChecks": true,
16
+ "strictPropertyInitialization": false,
17
+ "suppressImplicitAnyIndexErrors": true,
18
+ "importHelpers": false,
19
+ "noImplicitAny": true,
20
+ "noImplicitReturns": true,
21
+ "noImplicitThis": true,
22
+ "noFallthroughCasesInSwitch": true,
23
+ "noUnusedParameters": false,
24
+ "noUnusedLocals": false,
25
+ "noEmitOnError": false,
26
+ "forceConsistentCasingInFileNames": true,
27
+ "skipLibCheck": true,
28
+ "lib": ["dom", "ESNext"],
29
+ "jsx": "react",
30
+ },
31
+ "exclude": ["node_modules"],
32
+ "include": ["src/client/**/*"]
33
+ }
Binary file