I'm currently trying to set up a project that uses Webpack's Module Federation to share components.
To do so, I set up two basic vue projects with the cli and added a vue.config.js file in both projects:
Host project (that will include the shared component) (running on localhost:8000)
const { ModuleFederationPlugin } = require('webpack').container
module.exports = {
configureWebpack: {
plugins: [
new ModuleFederationPlugin({
name: 'shell',
filename: 'remoteEntry.js',
remotes: {
component: 'component@http://localhost:8001/remoteEntry.js'
},
exposes: {},
shared: {}
})
]
}
}
The component project (which shares the component) (running on localhost:8001):
const { ModuleFederationPlugin } = require('webpack').container
module.exports = {
configureWebpack: {
plugins: [
new ModuleFederationPlugin({
name: 'component',
filename: 'remoteEntry.js',
remotes: {},
exposes: {
'./HelloWorld': './src/components/HelloWorld.vue'
},
shared: {}
})
]
}
}
I try to load the component in my App.vue:
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<HelloWorld msg="Welcome to Your Vue.js App" />
<otherComp />
</template>
<script>
import { defineAsyncComponent } from "vue";
import HelloWorld from "./components/HelloWorld.vue";
const otherComp = defineAsyncComponent(() => import("component/HelloWorld"));
export default {
name: "App",
components: {
HelloWorld,
otherComp,
},
};
</script>
Indeed it tries to load the component, but instead of loading it from localhost:8001 (where the component is hosted) it tries to load it from localhost:8000:
The same path at localhost:8001 does exist. Some debugging showed, that the webpack publicPath seems to be set to "/" (causing the hosting application at localhost:8000 to set the url to /js/src_components_HelloWorld_vue.js
)
/******/ /* webpack/runtime/publicPath */
/******/ !function() {
/******/ __webpack_require__.p = "/";
/******/ }();
I believe this is due to how vue-cli interacts with webpack. Is this a known problem and how can this be fixed?
Did you try to change the publicPath
variable (source)? It takes an absolute path /
as default value, but you have to set them explicitly in the vue.config.js
of both your projects. I described a detailed example on how you can configure this. For the sake of simplicity, the host project is the project that exposes components. The consumer project is the project that consumes those remote components.
The host project should expose components. So you explicitly need to set publicPath
variable to the full hostname. Note that devServer.port
is set to 8000, so you don't have to do this manually.
// vue.config.js host project
const ModuleFederationPlugin =
require("webpack").container.ModuleFederationPlugin;
module.exports = {
publicPath: "http://localhost:8000/",
configureWebpack: {
plugins: [
new ModuleFederationPlugin({
name: "host",
filename: "remoteEntry.js",
exposes: {
"./HelloWorld": "./src/components/HelloWorld",
},
}),
],
},
devServer: {
port: 8000,
},
};
On the other side, we have a project that uses those components, which is specified through the remotes
field. For the sake if simplicity, this is the consumer project. Again, we set the publicPath
to the host where it is running on. Note that to use the remote component we have to know the name, hostname and port of the host project: host@http://localhost:8000/remoteEntry.js
.
// vue.config.js consumer project
const ModuleFederationPlugin =
require("webpack").container.ModuleFederationPlugin;
module.exports = {
publicPath: "http://localhost:8001/",
configureWebpack: {
plugins: [
new ModuleFederationPlugin({
name: "consumer",
filename: "remoteEntry.js",
remotes: {
host: "host@http://localhost:8000/remoteEntry.js",
},
}),
],
},
devServer: {
port: 8001,
},
};
You can find a very detailed example from the authors of Webpack Module Federation in this Github repository. This example project uses both Vue and Webpack. There are also some additional features that you may need:
With the remotes
option, you can specify more than one remote source. This is the idea of having many Micro Frontends. For example:
new ModuleFederationPlugin({
// ...
remotes: {
host: "host@http://localhost:8000/remoteEntry.js",
other: "other@http://localhost:9000/remoteEntry.js",
}
})
The shared
option lets you share dependencies between instances. For example, you can share UIkits, so that both instances have the same styling. However, there are still some caveats when sharing packages like vue, react or angular. You will probably run into the following error: Shared module is not available for eager consumption
. This post describes several ways to work around this problem, this is one of them:
const deps = require('./package.json').dependencies
new ModuleFederationPlugin({
// ...
shared: {
...deps,
vue: {
eager: true,
singleton: true,
requiredVersion: deps.vue,
strictVersion: true,
},
},
})
Note: ModuleFederation is only included in webpack 5 (and higher).