4

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?

looper
  • 1,929
  • 23
  • 42
  • I think your problem is very clear, however, I think a host project is the one that hosts components that others can use. And on the other hand you have a consuming project that will use components of the host. I specified it like this in my answer to make the clear distinction between the two. – Cloudkollektiv Jul 23 '21 at 16:01

1 Answers1

2

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.

  1. 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,
       },
     };
    
  2. 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:

  1. 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",
       }
     })
    
  2. 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).

Cloudkollektiv
  • 11,852
  • 3
  • 44
  • 71
  • 1
    @RoarS. he is proposing this to OP, he will not try to replicate his setup locally, when OP can do this in like 30s. And, it's not because it's 500rep, that you need to spend days on it neither. – kissu Jul 21 '21 at 13:56
  • 1
    as of 2022, set `publicPath: 'auto'` works perfectly – Duc Trung Mai Aug 08 '22 at 18:10