@module-federation/nextjs-mf 5.3.2 → 5.4.0

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