@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.
- package/.prettierignore +2 -0
- package/.prettierrc +7 -1
- package/README.md +213 -77
- package/lib/NextFederationPlugin.js +477 -0
- package/lib/include-defaults.js +16 -0
- package/lib/index.js +3 -0
- package/lib/loaders/fixImageLoader.js +25 -0
- package/lib/loaders/fixUrlLoader.js +25 -0
- package/lib/loaders/helpers.js +34 -0
- package/lib/loaders/nextPageMapLoader.js +129 -0
- package/lib/utils.js +97 -0
- package/package.json +16 -6
- package/docs/MFCover.png +0 -0
- package/index.d.ts +0 -2
- package/index.js +0 -5
- package/lib/federation-loader.js +0 -27
- package/lib/noop.js +0 -0
- package/lib/with-federated-sidecar.d.ts +0 -11
- package/lib/with-federated-sidecar.js +0 -120
package/.prettierignore
ADDED
package/.prettierrc
CHANGED
package/README.md
CHANGED
|
@@ -2,123 +2,259 @@
|
|
|
2
2
|
|
|
3
3
|
This plugin enables Module Federation on Next.js
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
### Supports
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- next ^11.x.x || ^12.x.x
|
|
8
|
+
- Client side only, SSR is another package currently in beta
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
I highly recommend referencing this application which takes advantage of the best capabilities:
|
|
11
|
+
https://github.com/module-federation/module-federation-examples
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
## Looking for SSR support?
|
|
14
|
+
|
|
15
|
+
SSR support for federated applications is much harder, as such - it utilizes a different licensing model.
|
|
16
|
+
If you need SSR support, consider this package instead - it does everything that nextjs-mf does, and them some.
|
|
17
|
+
https://app.privjs.com/buy/packageDetail?pkg=@module-federation/nextjs-ssr
|
|
18
|
+
|
|
19
|
+
## Whats shared by default?
|
|
20
|
+
|
|
21
|
+
Under the hood we share some next internals automatically
|
|
22
|
+
You do not need to share these packages, sharing next internals yourself will cause errors.
|
|
23
|
+
|
|
24
|
+
```js
|
|
25
|
+
const DEFAULT_SHARE_SCOPE = {
|
|
26
|
+
react: {
|
|
27
|
+
singleton: true,
|
|
28
|
+
requiredVersion: false,
|
|
29
|
+
},
|
|
30
|
+
'react/': {
|
|
31
|
+
singleton: true,
|
|
32
|
+
requiredVersion: false,
|
|
33
|
+
},
|
|
34
|
+
'react-dom': {
|
|
35
|
+
singleton: true,
|
|
36
|
+
requiredVersion: false,
|
|
37
|
+
},
|
|
38
|
+
'next/dynamic': {
|
|
39
|
+
requiredVersion: false,
|
|
40
|
+
singleton: true,
|
|
41
|
+
},
|
|
42
|
+
'styled-jsx': {
|
|
43
|
+
requiredVersion: false,
|
|
44
|
+
singleton: true,
|
|
45
|
+
},
|
|
46
|
+
'next/link': {
|
|
47
|
+
requiredVersion: false,
|
|
48
|
+
singleton: true,
|
|
49
|
+
},
|
|
50
|
+
'next/router': {
|
|
51
|
+
requiredVersion: false,
|
|
52
|
+
singleton: true,
|
|
53
|
+
},
|
|
54
|
+
'next/script': {
|
|
55
|
+
requiredVersion: false,
|
|
56
|
+
singleton: true,
|
|
57
|
+
},
|
|
58
|
+
'next/head': {
|
|
59
|
+
requiredVersion: false,
|
|
60
|
+
singleton: true,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Usage
|
|
12
66
|
|
|
13
|
-
|
|
14
|
-
|
|
67
|
+
```js
|
|
68
|
+
const SampleComponent = dynamic(() => import('next2/sampleComponent'), {
|
|
69
|
+
ssr: false,
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
If you want support for sync imports. It is possible in next@12 as long as there is an async boundary.
|
|
15
74
|
|
|
16
|
-
|
|
75
|
+
#### See the implementation here: https://github.com/module-federation/module-federation-examples/tree/master/nextjs/home/pages
|
|
17
76
|
|
|
18
|
-
|
|
77
|
+
With async boundary installed at the page level. You can then do the following
|
|
19
78
|
|
|
20
|
-
|
|
21
|
-
|
|
79
|
+
```js
|
|
80
|
+
if (process.browser) {
|
|
81
|
+
const SomeHook = require('next2/someHook');
|
|
82
|
+
}
|
|
83
|
+
// if client only file
|
|
84
|
+
import SomeComponent from 'next2/someComponent';
|
|
85
|
+
```
|
|
22
86
|
|
|
23
|
-
|
|
87
|
+
Make sure you are using `mini-css-extract-plugin@2` - version 2 supports resolving assets through `publicPath:'auto'`
|
|
24
88
|
|
|
25
|
-
|
|
89
|
+
## Demo
|
|
26
90
|
|
|
27
|
-
|
|
91
|
+
You can see it in action here: https://github.com/module-federation/module-federation-examples/tree/master/nextjs
|
|
28
92
|
|
|
29
|
-
|
|
93
|
+
## Usage
|
|
30
94
|
|
|
31
95
|
```js
|
|
32
|
-
|
|
33
|
-
|
|
96
|
+
const SampleComponent = dynamic(() => import('next2/sampleComponent'), {
|
|
97
|
+
ssr: false,
|
|
98
|
+
});
|
|
99
|
+
```
|
|
34
100
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
101
|
+
If you want support for sync imports. It is possible in next@12 as long as there is an async boundary.
|
|
102
|
+
|
|
103
|
+
#### See the implementation here: https://github.com/module-federation/module-federation-examples/tree/master/nextjs/home/pages
|
|
104
|
+
|
|
105
|
+
With async boundary installed at the page level. You can then do the following
|
|
106
|
+
|
|
107
|
+
```js
|
|
108
|
+
if (process.browser) {
|
|
109
|
+
const SomeHook = require('next2/someHook');
|
|
110
|
+
}
|
|
111
|
+
// if client only file
|
|
112
|
+
import SomeComponent from 'next2/someComponent';
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Make sure you are using `mini-css-extract-plugin@2` - version 2 supports resolving assets through `publicPath:'auto'`
|
|
116
|
+
|
|
117
|
+
## Options
|
|
118
|
+
|
|
119
|
+
This plugin works exactly like ModuleFederationPlugin, use it as you'd normally.
|
|
120
|
+
Note that we already share react and next stuff for you automatically.
|
|
121
|
+
|
|
122
|
+
Also NextFederationPlugin has own optional argument `extraOptions` where you can unlock additional features of this plugin:
|
|
123
|
+
|
|
124
|
+
```js
|
|
125
|
+
new NextFederationPlugin({
|
|
126
|
+
name: ...,
|
|
127
|
+
filename: ...,
|
|
128
|
+
remotes: ...,
|
|
129
|
+
exposes: ...,
|
|
130
|
+
shared: ...,
|
|
131
|
+
extraOptions: {
|
|
132
|
+
exposePages: true, // `false` by default
|
|
133
|
+
enableImageLoaderFix: true, // `false` by default
|
|
134
|
+
enableUrlLoaderFix: true, // `false` by default
|
|
47
135
|
},
|
|
48
|
-
})({
|
|
49
|
-
// your original next.config.js export
|
|
50
136
|
});
|
|
51
137
|
```
|
|
52
138
|
|
|
53
|
-
|
|
139
|
+
- `exposePages` – exposes automatically all nextjs pages for you and theirs `./pages-map`.
|
|
140
|
+
- `enableImageLoaderFix` – adds public hostname to all assets bundled by `nextjs-image-loader`. So if you serve remoteEntry from `http://example.com` then all bundled assets will get this hostname in runtime. It's something like Base URL in HTML but for federated modules.
|
|
141
|
+
- `enableUrlLoaderFix` – adds public hostname to all assets bundled by `url-loader`.
|
|
142
|
+
|
|
143
|
+
## Demo
|
|
144
|
+
|
|
145
|
+
You can see it in action here: https://github.com/module-federation/module-federation-examples/pull/2147
|
|
146
|
+
|
|
147
|
+
## Implementing the Plugin
|
|
148
|
+
|
|
149
|
+
1. Use `NextFederationPlugin` in your `next.config.js` of the app that you wish to expose modules from. We'll call this "next2".
|
|
54
150
|
|
|
55
151
|
```js
|
|
152
|
+
// next.config.js
|
|
153
|
+
const NextFederationPlugin = require('@module-federation/nextjs-mf/NextFederationPlugin');
|
|
154
|
+
|
|
56
155
|
module.exports = {
|
|
57
|
-
webpack(config) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
156
|
+
webpack(config, options) {
|
|
157
|
+
if (!options.isServer) {
|
|
158
|
+
config.plugins.push(
|
|
159
|
+
new NextFederationPlugin({
|
|
160
|
+
name: 'next2',
|
|
161
|
+
remotes: {
|
|
162
|
+
next1: `next1@http://localhost:3001/_next/static/chunks/remoteEntry.js`,
|
|
163
|
+
},
|
|
164
|
+
filename: 'static/chunks/remoteEntry.js',
|
|
165
|
+
exposes: {
|
|
166
|
+
'./title': './components/exposedTitle.js',
|
|
167
|
+
'./checkout': './pages/checkout',
|
|
168
|
+
'./pages-map': './pages-map.js',
|
|
169
|
+
},
|
|
170
|
+
shared: {
|
|
171
|
+
// whatever else
|
|
172
|
+
},
|
|
173
|
+
})
|
|
174
|
+
);
|
|
175
|
+
}
|
|
74
176
|
|
|
75
177
|
return config;
|
|
76
178
|
},
|
|
77
179
|
};
|
|
180
|
+
|
|
181
|
+
// _app.js or some other file in as high up in the app (like next's new layouts)
|
|
182
|
+
// this ensures various parts of next.js are imported and "used" somewhere so that they wont be tree shaken out
|
|
183
|
+
import '@module-federation/nextjs-mf/lib/include-defaults';
|
|
78
184
|
```
|
|
79
185
|
|
|
80
|
-
|
|
186
|
+
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`):
|
|
187
|
+
Inside that \_app.js or layout.js file, ensure you import `include-defaults` file
|
|
81
188
|
|
|
82
189
|
```js
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
190
|
+
// next.config.js
|
|
191
|
+
|
|
192
|
+
const NextFederationPlugin = require('@module-federation/nextjs-mf/NextFederationPlugin');
|
|
193
|
+
|
|
194
|
+
module.exports = {
|
|
195
|
+
webpack(config, options) {
|
|
196
|
+
if (!options.isServer) {
|
|
197
|
+
config.plugins.push(
|
|
198
|
+
new NextFederationPlugin({
|
|
199
|
+
name: 'next1',
|
|
200
|
+
remotes: {
|
|
201
|
+
next2: `next2@http://localhost:3000/_next/static/chunks/remoteEntry.js`,
|
|
202
|
+
},
|
|
203
|
+
})
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return config;
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// _app.js or some other file in as high up in the app (like next's new layouts)
|
|
212
|
+
// this ensures various parts of next.js are imported and "used" somewhere so that they wont be tree shaken out
|
|
213
|
+
import '@module-federation/nextjs-mf/lib/include-defaults';
|
|
88
214
|
```
|
|
89
215
|
|
|
90
|
-
4.
|
|
216
|
+
4. Use next/dynamic or low level api to import remotes.
|
|
91
217
|
|
|
92
218
|
```js
|
|
93
|
-
import
|
|
219
|
+
import dynamic from 'next/dynamic';
|
|
94
220
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
221
|
+
const SampleComponent = dynamic(
|
|
222
|
+
() => window.next2.get('./sampleComponent').then((factory) => factory()),
|
|
223
|
+
{
|
|
224
|
+
ssr: false,
|
|
99
225
|
}
|
|
226
|
+
);
|
|
100
227
|
|
|
101
|
-
|
|
102
|
-
return (
|
|
103
|
-
<Html>
|
|
104
|
-
<Head />
|
|
105
|
-
<body>
|
|
106
|
-
<Main />
|
|
107
|
-
<script src="http://next2-domain-here.com/_next/static/chunks/remoteEntry.js" />
|
|
108
|
-
<NextScript />
|
|
109
|
-
</body>
|
|
110
|
-
</Html>
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
228
|
+
// or
|
|
114
229
|
|
|
115
|
-
|
|
230
|
+
const SampleComponent = dynamic(() => import('next2/sampleComponent'), {
|
|
231
|
+
ssr: false,
|
|
232
|
+
});
|
|
116
233
|
```
|
|
117
234
|
|
|
118
|
-
|
|
235
|
+
## Utilities
|
|
236
|
+
|
|
237
|
+
Ive added a util for dynamic chunk loading, in the event you need to load remote containers dynamically.
|
|
119
238
|
|
|
120
239
|
```js
|
|
121
|
-
|
|
122
|
-
|
|
240
|
+
import { injectScript } from '@module-federation/nextjs-mf/lib/utils';
|
|
241
|
+
// if i have remotes in my federation plugin, i can pass the name of the remote
|
|
242
|
+
injectScript('home').then((remoteContainer) => {
|
|
243
|
+
remoteContainer.get('./exposedModule');
|
|
244
|
+
});
|
|
245
|
+
// if i want to load a custom remote not known at build time.
|
|
246
|
+
|
|
247
|
+
injectScript({
|
|
248
|
+
global: 'home',
|
|
249
|
+
url: 'http://somthing.com/remoteEntry.js',
|
|
250
|
+
}).then((remoteContainer) => {
|
|
251
|
+
remoteContainer.get('./exposedModule');
|
|
123
252
|
});
|
|
124
253
|
```
|
|
254
|
+
|
|
255
|
+
## Contact
|
|
256
|
+
|
|
257
|
+
If you have any questions or need to report a bug
|
|
258
|
+
<a href="https://twitter.com/ScriptedAlchemy"> Reach me on Twitter @ScriptedAlchemy</a>
|
|
259
|
+
|
|
260
|
+
Or join this discussion thread: https://github.com/module-federation/module-federation-examples/discussions/978
|