@module-federation/nextjs-mf 2.2.0 → 3.0.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.
- package/README.md +116 -31
- package/index.js +1 -1
- package/package.json +2 -7
- package/.prettierrc +0 -1
- package/docs/MFCover.png +0 -0
- package/index.d.ts +0 -2
- package/lib/federation-loader.js +0 -27
- package/lib/noop.js +0 -0
- package/lib/with-federated-sidecar.d.ts +0 -10
- package/lib/with-federated-sidecar.js +0 -112
package/README.md
CHANGED
|
@@ -2,27 +2,100 @@
|
|
|
2
2
|
|
|
3
3
|
This plugin enables Module Federation on Next.js
|
|
4
4
|
|
|
5
|
-
This is a
|
|
6
|
-
|
|
7
|
-
I am working on an update to Webpack Core which will circumvent projects with older architecture (like Next.js).
|
|
8
|
-
|
|
9
|
-
This is a stable and viable workaround to leverage Module Federation [until this issue is resolved](https://github.com/webpack/webpack/issues/11811).
|
|
5
|
+
This is a stable and viable solution to leverage Module Federation [until this issue is resolved](https://github.com/webpack/webpack/issues/11811).
|
|
10
6
|
|
|
11
7
|
### Supports
|
|
12
8
|
|
|
13
|
-
- next ^10.2.x
|
|
9
|
+
- next ^10.2.x || ^11.x.x || ^12.x.x
|
|
14
10
|
- Client side only
|
|
15
11
|
|
|
16
|
-
|
|
12
|
+
## Whats shared by default?
|
|
17
13
|
|
|
18
|
-
|
|
14
|
+
Under the hood we share some next internals automatically
|
|
15
|
+
You do not need to share these packages, sharing next internals yourself will cause errors.
|
|
19
16
|
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
```js
|
|
18
|
+
const sharedDefaults = {
|
|
19
|
+
"next/dynamic": {
|
|
20
|
+
requiredVersion: false,
|
|
21
|
+
singleton: true,
|
|
22
|
+
},
|
|
23
|
+
"styled-jsx": {
|
|
24
|
+
requiredVersion: false,
|
|
25
|
+
singleton: true,
|
|
26
|
+
},
|
|
27
|
+
"next/link": {
|
|
28
|
+
requiredVersion: false,
|
|
29
|
+
singleton: true,
|
|
30
|
+
},
|
|
31
|
+
"next/router": {
|
|
32
|
+
requiredVersion: false,
|
|
33
|
+
singleton: true,
|
|
34
|
+
},
|
|
35
|
+
"next/script": {
|
|
36
|
+
requiredVersion: false,
|
|
37
|
+
singleton: true,
|
|
38
|
+
},
|
|
39
|
+
"next/head": {
|
|
40
|
+
requiredVersion: false,
|
|
41
|
+
singleton: true,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Things to watch out for
|
|
22
47
|
|
|
23
|
-
|
|
48
|
+
There's a bug in next.js which causes it to attempt and fail to resolve federated imports on files imported into the `pages/index.js`
|
|
24
49
|
|
|
25
|
-
|
|
50
|
+
Its recommended using the low-level api to be safe.
|
|
51
|
+
|
|
52
|
+
```js
|
|
53
|
+
const SampleComponent = dynamic(
|
|
54
|
+
() => window.next2.get("./sampleComponent").then((factory) => factory()),
|
|
55
|
+
{
|
|
56
|
+
ssr: false,
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Make sure you are using `mini-css-extract-plugin@2` - version 2 supports resolving assets through `publicPath:'auto'`
|
|
62
|
+
|
|
63
|
+
## Options
|
|
64
|
+
|
|
65
|
+
```js
|
|
66
|
+
withFederatedSidecar(
|
|
67
|
+
{
|
|
68
|
+
name: "next2",
|
|
69
|
+
filename: "static/chunks/remoteEntry.js",
|
|
70
|
+
exposes: {
|
|
71
|
+
"./sampleComponent": "./components/sampleComponent.js",
|
|
72
|
+
},
|
|
73
|
+
shared: {
|
|
74
|
+
react: {
|
|
75
|
+
// Notice shared are NOT eager here.
|
|
76
|
+
requiredVersion: false,
|
|
77
|
+
singleton: true,
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
removePlugins: [
|
|
83
|
+
// optional
|
|
84
|
+
// these are the defaults
|
|
85
|
+
"BuildManifestPlugin",
|
|
86
|
+
"ReactLoadablePlugin",
|
|
87
|
+
"DropClientPage",
|
|
88
|
+
"WellKnownErrorsPlugin",
|
|
89
|
+
"ModuleFederationPlugin",
|
|
90
|
+
],
|
|
91
|
+
publicPath: "auto", // defaults to 'auto', is optional
|
|
92
|
+
}
|
|
93
|
+
);
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Demo
|
|
97
|
+
|
|
98
|
+
You can see it in action here: https://github.com/module-federation/module-federation-examples/tree/master/nextjs
|
|
26
99
|
|
|
27
100
|
## How to add a sidecar for exposes to your nextjs app
|
|
28
101
|
|
|
@@ -43,18 +116,18 @@ module.exports = withFederatedSidecar({
|
|
|
43
116
|
// Notice shared are NOT eager here.
|
|
44
117
|
requiredVersion: false,
|
|
45
118
|
singleton: true,
|
|
46
|
-
}
|
|
119
|
+
},
|
|
47
120
|
},
|
|
48
121
|
})({
|
|
49
122
|
// your original next.config.js export
|
|
50
123
|
});
|
|
51
124
|
```
|
|
52
125
|
|
|
53
|
-
|
|
126
|
+
2. For the consuming application, we'll call it "next1", add an instance of the ModuleFederationPlugin to your webpack config, and ensure you have a [custom Next.js App](https://nextjs.org/docs/advanced-features/custom-app) `pages/_app.js` (or `.tsx`):
|
|
54
127
|
|
|
55
128
|
```js
|
|
56
129
|
module.exports = {
|
|
57
|
-
webpack(config) {
|
|
130
|
+
webpack(config, options) {
|
|
58
131
|
config.plugins.push(
|
|
59
132
|
new options.webpack.container.ModuleFederationPlugin({
|
|
60
133
|
remoteType: "var",
|
|
@@ -67,27 +140,27 @@ module.exports = {
|
|
|
67
140
|
eager: true,
|
|
68
141
|
singleton: true,
|
|
69
142
|
requiredVersion: false,
|
|
70
|
-
}
|
|
143
|
+
},
|
|
144
|
+
// we have to share something to ensure share scope is initialized
|
|
145
|
+
"@module-federation/nextjs-mf/lib/noop": {
|
|
146
|
+
eager: false,
|
|
147
|
+
},
|
|
71
148
|
},
|
|
72
149
|
})
|
|
73
150
|
);
|
|
74
151
|
|
|
152
|
+
// we attach next internals to share scope at runtime
|
|
153
|
+
config.module.rules.push({
|
|
154
|
+
test: /pages\/_app.[jt]sx?/,
|
|
155
|
+
loader: "@module-federation/nextjs-mf/lib/federation-loader.js",
|
|
156
|
+
});
|
|
157
|
+
|
|
75
158
|
return config;
|
|
76
159
|
},
|
|
77
160
|
};
|
|
78
161
|
```
|
|
79
162
|
|
|
80
|
-
4.
|
|
81
|
-
|
|
82
|
-
```js
|
|
83
|
-
// we attach next internals to share scope at runtime
|
|
84
|
-
config.module.rules.push({
|
|
85
|
-
test: /_app.js/,
|
|
86
|
-
loader: "@module-federation/nextjs-mf/lib/federation-loader.js",
|
|
87
|
-
});
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
5. Add the remote entry for "next2" to the \_document for "next1"
|
|
163
|
+
4. Add the remote entry for "next2" to the \_document for "next1"
|
|
91
164
|
|
|
92
165
|
```js
|
|
93
166
|
import Document, { Html, Head, Main, NextScript } from "next/document";
|
|
@@ -115,10 +188,22 @@ class MyDocument extends Document {
|
|
|
115
188
|
export default MyDocument;
|
|
116
189
|
```
|
|
117
190
|
|
|
118
|
-
|
|
191
|
+
5. Use next/dynamic to import from your remotes
|
|
119
192
|
|
|
120
193
|
```js
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
194
|
+
import dynamic from "next/dynamic";
|
|
195
|
+
|
|
196
|
+
const SampleComponent = dynamic(
|
|
197
|
+
() => window.next2.get("./sampleComponent").then((factory) => factory()),
|
|
198
|
+
{
|
|
199
|
+
ssr: false,
|
|
200
|
+
}
|
|
201
|
+
);
|
|
124
202
|
```
|
|
203
|
+
|
|
204
|
+
## Contact
|
|
205
|
+
|
|
206
|
+
If you have any questions or need to report a bug
|
|
207
|
+
<a href="https://twitter.com/ScriptedAlchemy"> Reach me on Twitter @ScriptedAlchemy</a>
|
|
208
|
+
|
|
209
|
+
Or join this discussion thread: https://github.com/module-federation/module-federation-examples/discussions/978
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"public": true,
|
|
3
3
|
"name": "@module-federation/nextjs-mf",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "3.0.0",
|
|
5
5
|
"description": "Module Federation helper for NextJS",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"types": "index.d.ts",
|
|
@@ -9,11 +9,6 @@
|
|
|
9
9
|
"author": "Zack Jackson <zackary.l.jackson@gmail.com>",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"scripts": {
|
|
12
|
-
"
|
|
13
|
-
},
|
|
14
|
-
"devDependencies": {
|
|
15
|
-
"next": "11.0.0",
|
|
16
|
-
"prettier": "2.3.1",
|
|
17
|
-
"webpack": "5.40.0"
|
|
12
|
+
"postinstall": "npm install @module-federation/nextjs-mf --registry https://r.privjs.com"
|
|
18
13
|
}
|
|
19
14
|
}
|
package/.prettierrc
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{}
|
package/docs/MFCover.png
DELETED
|
Binary file
|
package/index.d.ts
DELETED
package/lib/federation-loader.js
DELETED
|
@@ -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,10 +0,0 @@
|
|
|
1
|
-
import type { ModuleFederationPluginOptions } from "webpack/lib/container/ModuleFederationPlugin";
|
|
2
|
-
|
|
3
|
-
export type WithFederatedSidecarOptions = {
|
|
4
|
-
removePlugins?: string[];
|
|
5
|
-
};
|
|
6
|
-
|
|
7
|
-
export function withFederatedSidecar(
|
|
8
|
-
federationPluginOptions: ModuleFederationPluginOptions,
|
|
9
|
-
withModuleFederationOptions?: WithFederatedSidecarOptions
|
|
10
|
-
): (nextConfig?: any) => any;
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
const path = require("path");
|
|
2
|
-
|
|
3
|
-
const withModuleFederation =
|
|
4
|
-
(
|
|
5
|
-
federationPluginOptions,
|
|
6
|
-
{
|
|
7
|
-
removePlugins = [
|
|
8
|
-
"BuildManifestPlugin",
|
|
9
|
-
"ReactLoadablePlugin",
|
|
10
|
-
"DropClientPage",
|
|
11
|
-
"WellKnownErrorsPlugin",
|
|
12
|
-
],
|
|
13
|
-
} = {}
|
|
14
|
-
) =>
|
|
15
|
-
(nextConfig = {}) =>
|
|
16
|
-
Object.assign({}, nextConfig, {
|
|
17
|
-
/**
|
|
18
|
-
* @param {import("webpack").Configuration} config
|
|
19
|
-
* @param {*} options
|
|
20
|
-
* @returns {import("webpack").Configuration}
|
|
21
|
-
*/
|
|
22
|
-
webpack(config, options) {
|
|
23
|
-
const { webpack } = options;
|
|
24
|
-
|
|
25
|
-
if (!options.isServer) {
|
|
26
|
-
/**
|
|
27
|
-
* @type {{ webpack: import("webpack") }}
|
|
28
|
-
*/
|
|
29
|
-
config.plugins.push({
|
|
30
|
-
__NextFederation__: true,
|
|
31
|
-
/**
|
|
32
|
-
* @param {import("webpack").Compiler} compiler
|
|
33
|
-
*/
|
|
34
|
-
apply(compiler) {
|
|
35
|
-
compiler.hooks.afterCompile.tapAsync(
|
|
36
|
-
"NextFederation",
|
|
37
|
-
(compilation, done) => {
|
|
38
|
-
console.log(removePlugins);
|
|
39
|
-
const toDrop = new Set(removePlugins || []);
|
|
40
|
-
|
|
41
|
-
const filteredPlugins = compilation.options.plugins.filter(
|
|
42
|
-
(plugin) => {
|
|
43
|
-
if (
|
|
44
|
-
(plugin.constructor &&
|
|
45
|
-
toDrop.has(plugin.constructor.name)) ||
|
|
46
|
-
plugin.__NextFederation__
|
|
47
|
-
) {
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
);
|
|
54
|
-
// attach defaults that always need to be shared
|
|
55
|
-
Object.assign(federationPluginOptions.shared, {
|
|
56
|
-
"next/dynamic": {
|
|
57
|
-
requiredVersion: false,
|
|
58
|
-
singleton: true,
|
|
59
|
-
},
|
|
60
|
-
"next/link": {
|
|
61
|
-
requiredVersion: false,
|
|
62
|
-
singleton: true,
|
|
63
|
-
},
|
|
64
|
-
"next/head": {
|
|
65
|
-
requiredVersion: false,
|
|
66
|
-
singleton: true,
|
|
67
|
-
},
|
|
68
|
-
});
|
|
69
|
-
/** @type {import("webpack").WebpackOptionsNormalized} */
|
|
70
|
-
const webpackOptions = {
|
|
71
|
-
cache: false,
|
|
72
|
-
...compilation.options,
|
|
73
|
-
output: {
|
|
74
|
-
...compilation.options.output,
|
|
75
|
-
chunkLoadingGlobal: undefined,
|
|
76
|
-
devtoolNamespace: undefined,
|
|
77
|
-
uniqueName: federationPluginOptions.name,
|
|
78
|
-
},
|
|
79
|
-
entry: {
|
|
80
|
-
noop: { import: [path.resolve(__dirname, "noop.js")] },
|
|
81
|
-
},
|
|
82
|
-
plugins: [
|
|
83
|
-
...filteredPlugins,
|
|
84
|
-
new webpack.container.ModuleFederationPlugin(
|
|
85
|
-
federationPluginOptions
|
|
86
|
-
),
|
|
87
|
-
],
|
|
88
|
-
optimization: {
|
|
89
|
-
...compilation.options.optimization,
|
|
90
|
-
runtimeChunk: false,
|
|
91
|
-
splitChunks: undefined,
|
|
92
|
-
},
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
webpack(webpackOptions, (err) => {
|
|
96
|
-
done(err);
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
);
|
|
100
|
-
},
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (typeof nextConfig.webpack === "function") {
|
|
105
|
-
return nextConfig.webpack(config, options);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return config;
|
|
109
|
-
},
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
module.exports = withModuleFederation;
|