forked from v-anton/nextjs-mf
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b9e9995
commit 6ab8e3f
Showing
16 changed files
with
351 additions
and
144 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,37 +1,141 @@ | ||
# Module Federation Runtime Chunk Merging | ||
# Module Federation For Next.js | ||
|
||
This plugin makes module federation work as expected when using | ||
`runtimeChunk:"single"` as an optimization tactic. | ||
This plugin enables Module Federation on Next.js | ||
|
||
## Usage | ||
This is a workaround to hard limitations caused by Next.js being synchronous. | ||
|
||
I am working on an update to Webpack Core which will circumvent projects with older architecture (like Next.js). | ||
|
||
This is a stable and viable workaround to leverage Module Federation [until this issue is resolved](https://github.com/webpack/webpack/issues/11811). | ||
|
||
### Supports | ||
|
||
- next ^9.5.6 | ||
- SSG | ||
- SSR | ||
|
||
**Once I PR webpack, this workaround will no longer be required.** | ||
|
||
# Check out our book | ||
|
||
| <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> | | ||
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | ||
|
||
#### Demo | ||
|
||
You can see it in action here: https://github.com/module-federation/module-federation-examples/tree/master/nextjs | ||
|
||
## How to use on a fresh nextjs app | ||
|
||
```sh | ||
yarn global add @module-federation/nextjs-mf | ||
``` | ||
|
||
Run this inside of a fresh nextjs install. | ||
|
||
```sh | ||
nextjs-mf upgrade -p 3001 | ||
``` | ||
|
||
## How to use on an existing app | ||
|
||
1. Use `withModuleFederation` in your `next.config.js` | ||
|
||
```js | ||
plugins: [ | ||
new ModuleFedSingleRuntimePlugin(), | ||
new ModuleFederationPlugin({ | ||
name: "app2", | ||
filename: "remoteEntry.js", | ||
// next.config.js | ||
const { withModuleFederation } = require("@module-federation/nextjs-mf"); | ||
const path = require("path"); | ||
|
||
module.exports = { | ||
webpack: (config, options) => { | ||
const { buildId, dev, isServer, defaultLoaders, webpack } = options; | ||
const mfConf = { | ||
mergeRuntime: true, //this is experimental, read below | ||
name: "next2", | ||
library: { type: config.output.libraryTarget, name: "next2" }, | ||
filename: "static/runtime/remoteEntry.js", | ||
remotes: { | ||
// For SSR, resolve to disk path (or you can use code streaming if you have access) | ||
next1: isServer | ||
? path.resolve( | ||
__dirname, | ||
"../next1/.next/server/static/runtime/remoteEntry.js" | ||
) | ||
: "next1", // for client, treat it as a global | ||
}, | ||
exposes: { | ||
"./Button": "./src/Button", | ||
"./nav": "./components/nav", | ||
}, | ||
shared: { react: { singleton: true }, "react-dom": { singleton: true } }, | ||
}), | ||
new HtmlWebpackPlugin({ | ||
template: "./public/index.html", | ||
}), | ||
] | ||
shared: ["lodash"], | ||
}; | ||
// Configures ModuleFederation and other Webpack properties | ||
withModuleFederation(config, options, mfConf); | ||
|
||
return config; | ||
}, | ||
}; | ||
``` | ||
|
||
# Example | ||
2. Add the `patchSharing` to `_document.js`. This will solve the react sharing issue. | ||
|
||
Can be found in the `/example` directory | ||
```jsx | ||
import Document, { Html, Head, Main, NextScript } from "next/document"; | ||
import { patchSharing } from "@module-federation/nextjs-mf"; | ||
|
||
class MyDocument extends Document { | ||
static async getInitialProps(ctx) { | ||
const initialProps = await Document.getInitialProps(ctx); | ||
return { ...initialProps }; | ||
} | ||
|
||
render() { | ||
return ( | ||
<Html> | ||
{patchSharing()} | ||
<script src="http://localhost:3000/_next/static/chunks/webpack.js" /> | ||
<script src="http://localhost:3000/_next/static/runtime/remoteEntry.js" /> | ||
<Head /> | ||
<body> | ||
<Main /> | ||
<NextScript /> | ||
</body> | ||
</Html> | ||
); | ||
} | ||
} | ||
``` | ||
|
||
### Supports | ||
Webpack 5 with Federated Modules | ||
3. Use top-level-await | ||
|
||
# Check out our book | ||
```js | ||
// some-component.js | ||
const Nav = (await import("../components/nav")).default; | ||
const _ = await import("lodash"); | ||
``` | ||
|
||
| <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> | | ||
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | ||
## Experimental | ||
|
||
Use at your own risk. | ||
|
||
Next.js uses `runtimeChunk:'single'` | ||
Which forces us to also add the webpack script itself. Till this is fixed in webpack, heres a plugin that will merge the runtimes back together for MF | ||
|
||
This can be enabled via `mergeRuntime` flag. This is not part of Module Federation, its part of this plugin. | ||
|
||
`withModuleFederation(config, options, {mergeRuntime:true,...mfConf})` | ||
|
||
You can manually add it as follows | ||
|
||
```js | ||
const { MergeRuntime } = require("@module-federation/nextjs-mf"); | ||
// in your next config. | ||
config.plugins.push(new MergeRuntime({ filename: "remoteEntry" })); | ||
``` | ||
|
||
This allows the following to be done | ||
|
||
```diff | ||
- <script src="http://localhost:3000/_next/static/chunks/webpack.js" /> | ||
- <script src="http://localhost:3000/_next/static/runtime/remoteEntry.js" /> | ||
+ <script src="http://localhost:3000/_next/static/remoteEntryMerged.js" /> | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
#!node | ||
const { Command } = require("commander"); | ||
const program = new Command(); | ||
const path = require("path"); | ||
const fs = require("fs"); | ||
|
||
program.version(require(path.resolve(__dirname, "../package.json")).version); | ||
|
||
const nextConfigJS = ({ name, port }) => `const { | ||
withModuleFederation, | ||
MergeRuntime, | ||
} = require("@module-federation/nextjs-mf"); | ||
const path = require("path"); | ||
module.exports = { | ||
webpack: (config, options) => { | ||
const { buildId, dev, isServer, defaultLoaders, webpack } = options; | ||
const mfConf = { | ||
name: "${name}", | ||
library: { type: config.output.libraryTarget, name: "${name}" }, | ||
filename: "static/runtime/remoteEntry.js", | ||
remotes: { | ||
// test1: isServer | ||
// ? path.resolve( | ||
// __dirname, | ||
// "../test1/.next/server/static/runtime/remoteEntry.js" | ||
// ) | ||
// : "test1", // for client, treat it as a global | ||
}, | ||
exposes: {}, | ||
shared: [], | ||
}; | ||
// Configures ModuleFederation and other Webpack properties | ||
withModuleFederation(config, options, mfConf); | ||
config.plugins.push(new MergeRuntime()); | ||
if (!isServer) { | ||
config.output.publicPath = "http://localhost:${port}/_next/"; | ||
} | ||
return config; | ||
}, | ||
};`; | ||
|
||
const documentJS = () => `import Document, { Html, Head, Main, NextScript } from "next/document"; | ||
import { patchSharing } from "@module-federation/nextjs-mf"; | ||
class MyDocument extends Document { | ||
static async getInitialProps(ctx) { | ||
const initialProps = await Document.getInitialProps(ctx); | ||
return { ...initialProps }; | ||
} | ||
render() { | ||
return ( | ||
<Html> | ||
{patchSharing()} | ||
{/* <script src="http://localhost:3000/_next/static/remoteEntryMerged.js" /> */} | ||
<Head /> | ||
<body> | ||
<Main /> | ||
<NextScript /> | ||
</body> | ||
</Html> | ||
); | ||
} | ||
} | ||
export default MyDocument;`; | ||
|
||
const upgrade = ({ port: p }) => { | ||
const port = p || 3000; | ||
|
||
const nextJSMFpkgJSON = JSON.parse( | ||
fs.readFileSync(path.resolve(__dirname, "../package.json")).toString() | ||
); | ||
|
||
// Upgrade package.json | ||
const pkgJSON = JSON.parse(fs.readFileSync("package.json").toString()); | ||
const name = pkgJSON.name; | ||
pkgJSON.resolutions = { | ||
webpack: "5.1.3", | ||
}; | ||
pkgJSON.scripts.dev = `next dev -p ${port}`; | ||
pkgJSON.dependencies[ | ||
"@module-federation/nextjs-mf" | ||
] = `^${nextJSMFpkgJSON.version}`; | ||
fs.writeFileSync("package.json", JSON.stringify(pkgJSON, null, 2)); | ||
|
||
fs.writeFileSync("next.config.js", nextConfigJS({ name, port })); | ||
fs.writeFileSync("pages/_document.js", documentJS({ name, port })); | ||
}; | ||
|
||
program | ||
.command("upgrade") | ||
.description("upgrade the NextJS instance in the current directory") | ||
.option("-p, --port <port>", "port") | ||
.action(upgrade); | ||
|
||
program.parse(process.argv); |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,8 @@ | ||
const withModuleFederation = require("./withModuleFederation"); | ||
const patchSharing = require("./patchSharing"); | ||
const MergeRuntime = require("./merge-runtime"); | ||
module.exports = MergeRuntime | ||
module.exports = { | ||
withModuleFederation, | ||
patchSharing, | ||
MergeRuntime, | ||
}; |
Oops, something went wrong.