@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,2 @@
1
+ .next
2
+ lib
package/.prettierrc CHANGED
@@ -1 +1,7 @@
1
- {}
1
+ {
2
+ "singleQuote": true,
3
+ "arrowParens": "always",
4
+ "tabWidth": 2,
5
+ "useTabs": false,
6
+ "trailingComma": "es5"
7
+ }
package/README.md CHANGED
@@ -2,123 +2,259 @@
2
2
 
3
3
  This plugin enables Module Federation on Next.js
4
4
 
5
- This is a workaround to hard limitations caused by Next.js being synchronous.
5
+ ### Supports
6
6
 
7
- I am working on an update to Webpack Core which will circumvent projects with older architecture (like Next.js).
7
+ - next ^11.x.x || ^12.x.x
8
+ - Client side only, SSR is another package currently in beta
8
9
 
9
- This is a stable and viable workaround to leverage Module Federation [until this issue is resolved](https://github.com/webpack/webpack/issues/11811).
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
- ### Supports
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
- - next ^10.2.x
14
- - Client side only
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
- **Once I PR webpack, this workaround will no longer be required.**
75
+ #### See the implementation here: https://github.com/module-federation/module-federation-examples/tree/master/nextjs/home/pages
17
76
 
18
- # Check out our book
77
+ With async boundary installed at the page level. You can then do the following
19
78
 
20
- | <a href="https://module-federation.myshopify.com/products/practical-module-federation" target="_blank"><img src="./docs/MFCover.png" alt='Practical Module Federation Book' width="95%"/></a> | <a href="https://module-federation.myshopify.com/products/practical-module-federation" target="_blank">We will be actively updating this book over the next year as we learn more about best practices and what issues people are running into with Module Federation, as well as with every release of Webpack as it moves towards a release candidate and release. So with your one purchase you are buying a whole year of updates.</a> |
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
- #### Demo
87
+ Make sure you are using `mini-css-extract-plugin@2` - version 2 supports resolving assets through `publicPath:'auto'`
24
88
 
25
- You can see it in action here: https://github.com/module-federation/module-federation-examples/tree/master/nextjs (needs to be updated)
89
+ ## Demo
26
90
 
27
- ## How to add a sidecar for exposes to your nextjs app
91
+ You can see it in action here: https://github.com/module-federation/module-federation-examples/tree/master/nextjs
28
92
 
29
- 1. Use `withFederatedSidecar` in your `next.config.js` of the app that you wish to expose modules from. We'll call this "next2".
93
+ ## Usage
30
94
 
31
95
  ```js
32
- // next.config.js
33
- const { withFederatedSidecar } = require("@module-federation/nextjs-mf");
96
+ const SampleComponent = dynamic(() => import('next2/sampleComponent'), {
97
+ ssr: false,
98
+ });
99
+ ```
34
100
 
35
- module.exports = withFederatedSidecar({
36
- name: "next2",
37
- filename: "static/chunks/remoteEntry.js",
38
- exposes: {
39
- "./sampleComponent": "./components/sampleComponent.js",
40
- },
41
- shared: {
42
- react: {
43
- // Notice shared are NOT eager here.
44
- requiredVersion: false,
45
- singleton: true,
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
- 2. For the consuming application, we'll call it "next1", add an instance of the ModuleFederationPlugin to your webpack config:
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
- config.plugins.push(
59
- new options.webpack.container.ModuleFederationPlugin({
60
- remoteType: "var",
61
- remotes: {
62
- next2: "next2",
63
- },
64
- shared: {
65
- react: {
66
- // Notice shared ARE eager here.
67
- eager: true,
68
- singleton: true,
69
- requiredVersion: false,
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
- 3. Make sure you have an `_app.js` file, then add the loader
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
- // 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
- });
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. Add the remote entry for "next2" to the \_document for "next1"
216
+ 4. Use next/dynamic or low level api to import remotes.
91
217
 
92
218
  ```js
93
- import Document, { Html, Head, Main, NextScript } from "next/document";
219
+ import dynamic from 'next/dynamic';
94
220
 
95
- class MyDocument extends Document {
96
- static async getInitialProps(ctx) {
97
- const initialProps = await Document.getInitialProps(ctx);
98
- return { ...initialProps };
221
+ const SampleComponent = dynamic(
222
+ () => window.next2.get('./sampleComponent').then((factory) => factory()),
223
+ {
224
+ ssr: false,
99
225
  }
226
+ );
100
227
 
101
- render() {
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
- export default MyDocument;
230
+ const SampleComponent = dynamic(() => import('next2/sampleComponent'), {
231
+ ssr: false,
232
+ });
116
233
  ```
117
234
 
118
- 5. Use next/dynamic to import from your remotes
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
- const SampleComponent = dynamic(() => import("next2/sampleComponent"), {
122
- ssr: false,
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