@module-federation/nextjs-mf 2.3.0 → 3.0.1

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 CHANGED
@@ -2,27 +2,100 @@
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.
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
- **Once I PR webpack, this workaround will no longer be required.**
12
+ ## Whats shared by default?
17
13
 
18
- # Check out our book
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
- | <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
- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
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
- #### Demo
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
- You can see it in action here: https://github.com/module-federation/module-federation-examples/tree/master/nextjs (needs to be updated)
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
- 2. For the consuming application, we'll call it "next1", add an instance of the ModuleFederationPlugin to your webpack config:
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,26 +140,26 @@ 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
- 3. Make sure you have an `_app.js` file, then add the loader
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
163
  4. Add the remote entry for "next2" to the \_document for "next1"
91
164
 
92
165
  ```js
@@ -118,7 +191,19 @@ export default MyDocument;
118
191
  5. Use next/dynamic to import from your remotes
119
192
 
120
193
  ```js
121
- const SampleComponent = dynamic(() => import("next2/sampleComponent"), {
122
- ssr: false,
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
@@ -1,5 +1,5 @@
1
- const withFederatedSidecar = require("./lib/with-federated-sidecar");
1
+ const {withFederatedSidecar} = require("@module-federation/nextjs-mf");
2
2
 
3
3
  module.exports = {
4
- withFederatedSidecar
4
+ withFederatedSidecar,
5
5
  };
@@ -1,27 +1 @@
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
- };
1
+ module.exports = require("@module-federation/nextjs-mf/lib/federation-loader")
package/package.json CHANGED
@@ -1,22 +1,17 @@
1
1
  {
2
2
  "public": true,
3
3
  "name": "@module-federation/nextjs-mf",
4
- "version": "2.3.0",
4
+ "version": "3.0.1",
5
5
  "description": "Module Federation helper for NextJS",
6
6
  "main": "index.js",
7
- "types": "index.d.ts",
7
+ "readme": "README.md",
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
+ "postinstall": "npm install @module-federation/nextjs-mf --registry https://r.privjs.com"
13
13
  },
14
14
  "dependencies": {
15
- "webpack-federated-stats-plugin": "2.0.5"
16
- },
17
- "devDependencies": {
18
- "next": "11.0.0",
19
- "prettier": "2.3.2",
20
- "webpack": "5.40.0"
15
+ "@module-federation/nextjs-mf": "^3.5.0"
21
16
  }
22
17
  }
package/.prettierrc DELETED
@@ -1 +0,0 @@
1
- {}
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/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,121 +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
- console.log(removePlugins);
42
- const toDrop = new Set(removePlugins || []);
43
-
44
- const filteredPlugins = compilation.options.plugins.filter(
45
- (plugin) => {
46
- if (
47
- (plugin.constructor &&
48
- toDrop.has(plugin.constructor.name)) ||
49
- plugin.__NextFederation__
50
- ) {
51
- return false;
52
- }
53
-
54
- return true;
55
- }
56
- );
57
- // attach defaults that always need to be shared
58
- Object.assign(federationPluginOptions.shared, {
59
- "next/dynamic": {
60
- requiredVersion: false,
61
- singleton: true,
62
- },
63
- "next/link": {
64
- requiredVersion: false,
65
- singleton: true,
66
- },
67
- "next/head": {
68
- requiredVersion: false,
69
- singleton: true,
70
- },
71
- });
72
- /** @type {import("webpack").WebpackOptionsNormalized} */
73
- const webpackOptions = {
74
- cache: false,
75
- ...compilation.options,
76
- output: {
77
- ...compilation.options.output,
78
- chunkLoadingGlobal: undefined,
79
- devtoolNamespace: undefined,
80
- uniqueName: federationPluginOptions.name,
81
- },
82
- entry: {
83
- noop: { import: [path.resolve(__dirname, "noop.js")] },
84
- },
85
- plugins: [
86
- ...filteredPlugins,
87
- new webpack.container.ModuleFederationPlugin(
88
- federationPluginOptions
89
- ),
90
- ],
91
- optimization: {
92
- ...compilation.options.optimization,
93
- runtimeChunk: false,
94
- splitChunks: undefined,
95
- },
96
- };
97
-
98
- if (stats) {
99
- webpackOptions.plugins.push(
100
- new FederatedStatsPlugin({ filename: stats })
101
- );
102
- }
103
-
104
- webpack(webpackOptions, (err) => {
105
- done(err);
106
- });
107
- }
108
- );
109
- },
110
- });
111
- }
112
-
113
- if (typeof nextConfig.webpack === "function") {
114
- return nextConfig.webpack(config, options);
115
- }
116
-
117
- return config;
118
- },
119
- });
120
-
121
- module.exports = withModuleFederation;