@module-federation/nextjs-mf 2.3.1 → 5.1.2

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,129 @@
1
+ const fg = require('fast-glob');
2
+ const fs = require('fs');
3
+
4
+ /**
5
+ * Webpack loader which prepares MF map for NextJS pages
6
+ *
7
+ * @type {(this: import("webpack").LoaderContext<{}>, content: string) => string>}
8
+ */
9
+ function nextPageMapLoader() {
10
+ const pages = getNextPages(this.rootContext);
11
+ const pageMap = preparePageMap(pages);
12
+
13
+ // const [pagesRoot] = getNextPagesRoot(this.rootContext);
14
+ // this.addContextDependency(pagesRoot);
15
+
16
+ const result = `module.exports = {
17
+ default: ${JSON.stringify(pageMap)},
18
+ };`;
19
+
20
+ this.callback(null, result);
21
+ }
22
+
23
+ /**
24
+ * Webpack config generator for `exposes` option.
25
+ * - automatically create `./pages-map` module
26
+ * - automatically add all page modules
27
+ */
28
+ function exposeNextjsPages(cwd) {
29
+ const pages = getNextPages(cwd);
30
+
31
+ const pageModulesMap = {};
32
+ pages.forEach((page) => {
33
+ // Creating a map of pages to modules
34
+ // './pages/storage/index': './pages/storage/index.tsx',
35
+ // './pages/storage/[...slug]': './pages/storage/[...slug].tsx',
36
+ pageModulesMap['./' + sanitizePagePath(page)] = `./${page}`;
37
+ });
38
+
39
+ const exposesWithPageMap = {
40
+ './pages-map': `${__filename}!${__filename}`,
41
+ ...pageModulesMap,
42
+ };
43
+
44
+ return exposesWithPageMap;
45
+ }
46
+
47
+ function getNextPagesRoot(appRoot) {
48
+ let pagesDir = 'src/pages/';
49
+ let absPageDir = `${appRoot}/${pagesDir}`;
50
+ if (!fs.existsSync(absPageDir)) {
51
+ pagesDir = 'pages/';
52
+ absPageDir = `${appRoot}/${pagesDir}`;
53
+ }
54
+
55
+ return [absPageDir, pagesDir];
56
+ }
57
+
58
+ /**
59
+ * From provided ROOT_DIR `scan` pages directory
60
+ * and return list of user defined pages
61
+ * (except special ones, like _app, _document, _error)
62
+ *
63
+ * @type {(rootDir: string) => string[]}
64
+ */
65
+ function getNextPages(rootDir) {
66
+ const [cwd, pagesDir] = getNextPagesRoot(rootDir);
67
+
68
+ // scan all files in pages folder except pages/api
69
+ let pageList = fg.sync('**/*.{ts,tsx,js,jsx}', {
70
+ cwd,
71
+ onlyFiles: true,
72
+ ignore: ['api/**'],
73
+ });
74
+
75
+ // remove specific nextjs pages
76
+ const exclude = [
77
+ /^_app\..*/, // _app.tsx
78
+ /^_document\..*/, // _document.tsx
79
+ /^_error\..*/, // _error.tsx
80
+ /^404\..*/, // 404.tsx
81
+ /^500\..*/, // 500.tsx
82
+ /^\[\.\.\..*\]\..*/, // /[...federationPage].tsx
83
+ ];
84
+ pageList = pageList.filter((page) => {
85
+ return !exclude.some((r) => r.test(page));
86
+ });
87
+
88
+ pageList = pageList.map((page) => `${pagesDir}${page}`);
89
+
90
+ return pageList;
91
+ }
92
+
93
+ function sanitizePagePath(item) {
94
+ return item
95
+ .replace(/^src\/pages\//i, 'pages/')
96
+ .replace(/\.(ts|tsx|js|jsx)$/, '');
97
+ }
98
+
99
+ /**
100
+ * Create MF map from list of NextJS pages
101
+ *
102
+ * From
103
+ * ['pages/index.tsx', 'pages/storage/[...slug].tsx', 'pages/storage/index.tsx']
104
+ * Getting the following map
105
+ * {
106
+ * '/': './pages/index',
107
+ * '/storage/*': './pages/storage/[...slug]',
108
+ * '/storage': './pages/storage/index'
109
+ * }
110
+ *
111
+ * @type {(pages: string[]) => {[key: string]: string}}
112
+ */
113
+ function preparePageMap(pages) {
114
+ const result = {};
115
+
116
+ pages.forEach((pagePath) => {
117
+ const page = sanitizePagePath(pagePath);
118
+ let key =
119
+ '/' +
120
+ page.replace(/\[\.\.\.[^\]]+\]/gi, '*').replace(/\[([^\]]+)\]/gi, ':$1');
121
+ key = key.replace(/^\/pages\//, '/').replace(/\/index$/, '') || '/';
122
+ result[key] = `./${page}`;
123
+ });
124
+
125
+ return result;
126
+ }
127
+
128
+ module.exports = nextPageMapLoader;
129
+ module.exports.exposeNextjsPages = exposeNextjsPages;
package/lib/utils.js ADDED
@@ -0,0 +1,97 @@
1
+ const remoteVars = process.env.REMOTES || {};
2
+
3
+ const runtimeRemotes = Object.entries(remoteVars).reduce(function (acc, item) {
4
+ const [key, value] = item;
5
+ if (typeof value === 'object' && typeof value.then === 'function') {
6
+ acc[key] = { asyncContainer: value };
7
+ } else if (typeof value === 'string') {
8
+ const [global, url] = value.split('@');
9
+ acc[key] = { global, url };
10
+ } else {
11
+ throw new Error(`[mf] Invalid value received for runtime_remote "${key}"`);
12
+ }
13
+ return acc;
14
+ }, {});
15
+
16
+ module.exports.remotes = runtimeRemotes;
17
+
18
+ /**
19
+ * Return initialized remote container by remote's key or its runtime remote item data.
20
+ *
21
+ * `runtimeRemoteItem` might be
22
+ * { global, url } - values obtained from webpack remotes option `global@url`
23
+ * or
24
+ * { asyncContainer } - async container is a promise that resolves to the remote container
25
+ */
26
+ function injectScript(keyOrRuntimeRemoteItem) {
27
+ let reference = keyOrRuntimeRemoteItem;
28
+ if (typeof keyOrRuntimeRemoteItem === 'string') {
29
+ reference = runtimeRemotes[keyOrRuntimeRemoteItem];
30
+ }
31
+
32
+ // 1) Load remote container if needed
33
+ let asyncContainer;
34
+ if (reference.asyncContainer) {
35
+ asyncContainer = reference.asyncContainer;
36
+ } else {
37
+ const remoteGlobal = reference.global;
38
+ const __webpack_error__ = new Error();
39
+ asyncContainer = new Promise(function (resolve, reject) {
40
+ if (typeof window[remoteGlobal] !== 'undefined') {
41
+ return resolve(window[remoteGlobal]);
42
+ }
43
+
44
+ __webpack_require__.l(
45
+ reference.url,
46
+ function (event) {
47
+ if (typeof window[remoteGlobal] !== 'undefined') {
48
+ return resolve(window[remoteGlobal]);
49
+ }
50
+
51
+ var errorType =
52
+ event && (event.type === 'load' ? 'missing' : event.type);
53
+ var realSrc = event && event.target && event.target.src;
54
+ __webpack_error__.message =
55
+ 'Loading script failed.\n(' + errorType + ': ' + realSrc + ')';
56
+ __webpack_error__.name = 'ScriptExternalLoadError';
57
+ __webpack_error__.type = errorType;
58
+ __webpack_error__.request = realSrc;
59
+ reject(__webpack_error__);
60
+ },
61
+ remoteGlobal
62
+ );
63
+ });
64
+ }
65
+
66
+ // 2) Initialize remote container
67
+ return asyncContainer
68
+ .then(function (container) {
69
+ if (!__webpack_share_scopes__.default) {
70
+ // not always a promise, so we wrap it in a resolve
71
+ return Promise.resolve(__webpack_init_sharing__('default')).then(
72
+ function () {
73
+ return container;
74
+ }
75
+ );
76
+ } else {
77
+ return container;
78
+ }
79
+ })
80
+ .then(function (container) {
81
+ try {
82
+ // WARNING: here might be a potential BUG.
83
+ // `container.init` does not return a Promise, and here we do not call `then` on it.
84
+ // But according to [docs](https://webpack.js.org/concepts/module-federation/#dynamic-remote-containers)
85
+ // it must be async.
86
+ // The problem may be in Proxy in NextFederationPlugin.js.
87
+ // or maybe a bug in the webpack itself - instead of returning rejected promise it just throws an error.
88
+ // But now everything works properly and we keep this code as is.
89
+ container.init(__webpack_share_scopes__.default);
90
+ } catch (e) {
91
+ // maybe container already initialized so nothing to throw
92
+ }
93
+ return container;
94
+ });
95
+ }
96
+
97
+ module.exports.injectScript = injectScript;
package/package.json CHANGED
@@ -1,22 +1,32 @@
1
1
  {
2
2
  "public": true,
3
3
  "name": "@module-federation/nextjs-mf",
4
- "version": "2.3.1",
4
+ "version": "5.1.2",
5
5
  "description": "Module Federation helper for NextJS",
6
- "main": "index.js",
7
- "types": "index.d.ts",
6
+ "main": "lib/index.js",
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
10
  "license": "MIT",
11
11
  "scripts": {
12
- "prettier": "prettier --write \"**/*.{js,json,md,ts,tsx}\""
12
+ "demo": "yarn build && cd demo && yarn install && yarn dev",
13
+ "prettier": "prettier --write \"**/*.{js,json,md,ts,tsx}\"",
14
+ "build": "rm -rf lib && cp -r ./src/ lib/ && rollup -c"
13
15
  },
14
16
  "dependencies": {
15
- "webpack-federated-stats-plugin": "2.0.5"
17
+ "chalk": "^4.0.0",
18
+ "fast-glob": "^3.2.11"
16
19
  },
17
20
  "devDependencies": {
21
+ "@rollup/plugin-commonjs": "^22.0.2",
22
+ "@rollup/plugin-multi-entry": "^4.1.0",
23
+ "@rollup/plugin-node-resolve": "^13.3.0",
18
24
  "next": "11.0.1",
19
25
  "prettier": "2.3.2",
20
- "webpack": "5.40.0"
26
+ "rollup": "^2.78.1",
27
+ "rollup-obfuscator": "^3.0.1",
28
+ "rollup-plugin-node-builtins": "^2.1.2",
29
+ "rollup-plugin-node-globals": "^1.4.0",
30
+ "webpack": "5.45.1"
21
31
  }
22
32
  }
package/docs/MFCover.png DELETED
Binary file
package/index.d.ts DELETED
@@ -1,2 +0,0 @@
1
- export type { WithFederatedSidecarOptions } from "./lib/with-federated-sidecar";
2
- export { withFederatedSidecar } from "./lib/with-federated-sidecar";
package/index.js DELETED
@@ -1,5 +0,0 @@
1
- const withFederatedSidecar = require("./lib/with-federated-sidecar");
2
-
3
- module.exports = {
4
- withFederatedSidecar
5
- };
@@ -1,27 +0,0 @@
1
- const shareNextInternals = `if (process.browser) {
2
- Object.assign(__webpack_share_scopes__.default, {
3
- "next/link": {
4
- [next.version]: {
5
- loaded: true,
6
- get: () => Promise.resolve(() => require("next/link")),
7
- },
8
- },
9
- "next/head": {
10
- [next.version]: {
11
- loaded: true,
12
- get: () => Promise.resolve(() => require("next/head")),
13
- },
14
- },
15
- "next/dynamic": {
16
- [next.version]: {
17
- loaded: true,
18
- get: () => Promise.resolve(() => require("next/dynamic")),
19
- },
20
- },
21
- });
22
- }
23
- `
24
-
25
- module.exports = function (source) {
26
- return shareNextInternals + source
27
- };
package/lib/noop.js DELETED
File without changes
@@ -1,11 +0,0 @@
1
- import type { ModuleFederationPluginOptions } from "webpack/lib/container/ModuleFederationPlugin";
2
-
3
- export type WithFederatedSidecarOptions = {
4
- removePlugins?: string[];
5
- stats?: string;
6
- };
7
-
8
- export function withFederatedSidecar(
9
- federationPluginOptions: ModuleFederationPluginOptions,
10
- withModuleFederationOptions?: WithFederatedSidecarOptions
11
- ): (nextConfig?: any) => any;
@@ -1,120 +0,0 @@
1
- const path = require("path");
2
-
3
- const FederatedStatsPlugin = require("webpack-federated-stats-plugin");
4
-
5
- const withModuleFederation =
6
- (
7
- federationPluginOptions,
8
- {
9
- removePlugins = [
10
- "BuildManifestPlugin",
11
- "ReactLoadablePlugin",
12
- "DropClientPage",
13
- "WellKnownErrorsPlugin",
14
- ],
15
- stats,
16
- } = {}
17
- ) =>
18
- (nextConfig = {}) =>
19
- Object.assign({}, nextConfig, {
20
- /**
21
- * @param {import("webpack").Configuration} config
22
- * @param {*} options
23
- * @returns {import("webpack").Configuration}
24
- */
25
- webpack(config, options) {
26
- const { webpack } = options;
27
-
28
- if (!options.isServer) {
29
- /**
30
- * @type {{ webpack: import("webpack") }}
31
- */
32
- config.plugins.push({
33
- __NextFederation__: true,
34
- /**
35
- * @param {import("webpack").Compiler} compiler
36
- */
37
- apply(compiler) {
38
- compiler.hooks.afterCompile.tapAsync(
39
- "NextFederation",
40
- (compilation, done) => {
41
- const toDrop = new Set(removePlugins || []);
42
-
43
- const filteredPlugins = compilation.options.plugins.filter(
44
- (plugin) => {
45
- if (
46
- (plugin.constructor &&
47
- toDrop.has(plugin.constructor.name)) ||
48
- plugin.__NextFederation__
49
- ) {
50
- return false;
51
- }
52
-
53
- return true;
54
- }
55
- );
56
- // attach defaults that always need to be shared
57
- Object.assign(federationPluginOptions.shared, {
58
- "next/dynamic": {
59
- requiredVersion: false,
60
- singleton: true,
61
- },
62
- "next/link": {
63
- requiredVersion: false,
64
- singleton: true,
65
- },
66
- "next/head": {
67
- requiredVersion: false,
68
- singleton: true,
69
- },
70
- });
71
- /** @type {import("webpack").WebpackOptionsNormalized} */
72
- const webpackOptions = {
73
- cache: false,
74
- ...compilation.options,
75
- output: {
76
- ...compilation.options.output,
77
- chunkLoadingGlobal: undefined,
78
- devtoolNamespace: undefined,
79
- uniqueName: federationPluginOptions.name,
80
- },
81
- entry: {
82
- noop: { import: [path.resolve(__dirname, "noop.js")] },
83
- },
84
- plugins: [
85
- ...filteredPlugins,
86
- new webpack.container.ModuleFederationPlugin(
87
- federationPluginOptions
88
- ),
89
- ],
90
- optimization: {
91
- ...compilation.options.optimization,
92
- runtimeChunk: false,
93
- splitChunks: undefined,
94
- },
95
- };
96
-
97
- if (stats) {
98
- webpackOptions.plugins.push(
99
- new FederatedStatsPlugin({ filename: stats })
100
- );
101
- }
102
-
103
- webpack(webpackOptions, (err) => {
104
- done(err);
105
- });
106
- }
107
- );
108
- },
109
- });
110
- }
111
-
112
- if (typeof nextConfig.webpack === "function") {
113
- return nextConfig.webpack(config, options);
114
- }
115
-
116
- return config;
117
- },
118
- });
119
-
120
- module.exports = withModuleFederation;