@telenko/node-mf
Version:
Module Federation plugin support for NodeJS
181 lines (155 loc) • 7.22 kB
Markdown
# About
Library is a set of webpack plugins which gives support of Module Federation into NodeJS.
Usage example with NextJS https://github.com/telenko/node-mf-example
Here is also my [article](https://mangolik931.medium.com/how-i-implemented-module-federation-in-nodejs-and-did-something-wrong-724642c26da5) about this library.
# Purpose of creating
Release of [Webpack Module Federation](https://webpack.js.org/concepts/module-federation/) really have made a shift in modern architecture of web applications. But what about NodeJS and such frameworks like [Next](https://nextjs.org/)? What about SSR? I see for now a gap in webpack, so pure ModuleFederation plugin can not be used inside NodeJS environment. Muliple examples of NextJS+MF here https://github.com/module-federation/module-federation-examples with NextJS using only client-side rendering or not using remote deployed build at all.
# What can I propose
I have implemented 2 plugins for webpack:
1) **NodeAsyncHttpRuntime** - Plugin which should be used on a remote side to build remote scripts for NodeJS. But the main key of plugin is resolution of modules - it is done via http requests, so NodeJS can dynamically resolve child modules, load them and perform.
2) **NodeModuleFederation** - Plugin is wrapper around origin WebpackModuleFederation plugin and adds NodeJS specific resolution of remote modules (same thing: resolution is done via http requests)
# Getting started
### 1) On remote library:
1.1) Install package (remote can be either pure JS application or NodeJS application - doesn't matter)
```
npm i --save-dev @telenko/node-mf
```
1.2. Customize webpack configuration, so it should now build 2 targets: for web (if you want legacy browser build) and for node (webpack for now doesn't support universal targets)
1.3) For node build add **NodeAsyncHttpRuntime** plugin and set webpack's 'target' flag to false
```js
//pseudocode
module.exports = [
{
target: "web",
...webOptionsWebpack
},
{
target: false,
plugins: [
new NodeAsyncHttpRuntime() //this is instead of target to make it work
],
...nodeOptionsWebpack
}
];
```
1.4) Serve both builds
Full example is here https://github.com/telenko/node-mf-example/blob/master/remoteLib/webpack.config.js
### 2) On NodeJS application:
2.1) Install library
```
npm i --save-dev @telenko/node-mf
```
2.2) Add **NodeModuleFederation** plugin to the webpack config (api parameters schema is same)
**!Note, remote script url should point to 'node' build (which should be built with NodeAsyncHttpRuntime)**
```js
module.exports = {
plugins: [
new NodeModuleFederation({
remotes: {
someLib: "someLib@http://some-url/remoteEntry.node.js"
},
shared: {
lodash: {
eager: true,
singleton: true,
requiredVersion: "1.1.2"
}
}
})
],
...otherWebpackOptions
};
```
### Usage with Next.js
As Next.js use an internal version of webpack, it's mandatory to use their version. To do so, use the second argument of the 2 plugins to send the "context" of the [webpack options](https://nextjs.org/docs/api-reference/next.config.js/custom-webpack-config).
```js
// next.config.js
module.exports = {
webpack: (config, options) => {
config.plugins.push(
new NodeAsyncHttpRuntime({}, options)
);
return config
},
}
```
```js
// next.config.js
module.exports = {
webpack: (config, options) => {
config.plugins.push(
new NodeModuleFederation({
remotes: {
someLib: "someLib@http://some-url/remoteEntry.node.js"
},
shared: {
lodash: {
eager: true,
singleton: true,
requiredVersion: "1.1.2"
}
}
}, options)
);
return config
},
}
```
Full example of setuping NextJS with SSR here https://github.com/telenko/node-mf-example/blob/master/host/next.config.js
### Usage with runtime uris
If you do not know the real uri during the build step, you can use a promise which will be executed at runtime.
Here is an example based on [telenko/node-mf-example](https://github.com/telenko/node-mf-example) to have an url according to the dev or CDN host. On the CDN, the assets are stored in a path according to the remote app name. The environment variable `CDN_URL` is only known on the production server so we set an alternative for the development env.
```js
// host
module.exports = {
webpack: (config, options) => {
// [...]
if (isServer) {
// Set the public path of the remote as auto
mfConf.remotes.remoteLib = "remoteLib@auto";
// Parse the remote uri and return a promise template string with the runtime data
mfConf.getRemoteUri = function (remoteUri) {
const remoteName = remoteUri.split("@")[0];
return `new Promise(r => {
const uri = "${process.env.CDN_URL ? `${process.env.CDN_URL}/${remoteName}` : 'http://localhost:3002'}";
r("${remoteName}@${uri}/node/remoteEntry.js");
})`
}
}
// [...]
}
};
```
```js
// remoteLib
const getConfig = (target) => ({
plugins: [
...(target === "web"
? [
new HtmlWebpackPlugin({
template: "./public/index.html"
})
]
: [
new NodeAsyncHttpRuntime({
getBaseUri = function () {
// Return a promise template string with the runtime data
return `new Promise(r => {
const uri = "${process.env.CDN_URL ? `${process.env.CDN_URL}/remoteLib` : 'http://localhost:3002'}";
r("${uri}/${target}/");
})`
}
})
]
)
]
});
```
# Risks
## Aren't 2 build makes build-time 2 times longer?
Yes, if we speak about NextJS and SSR - we need both builds: for web and for node, and we have to start entire build separately without sharing built chunks. That will increase build time 2 times.
## Security
Since NodeJS out of the box doesn't support remote scripts execution (like in browser we can add **script** tag) it looks like a hack calling http request then 'eval'-ing it. I'm not sure if it is safe to add remote script execution on server side, but it is the only one possible way to make it on server.
# Roadmap
1) https support
2) Next.js support for NodeAsyncHttpRuntime plugin