Setting Up Module Federation in a Vue 2 Application

Overview

This documentation provides a step-by-step guide on how to set up Module Federation in a Vue 2 application. This guide assumes that you are familiar with the basics of Module Federation and Vue 2.

Prerequisites

Two pre-configured Vue 2 projects. For assistance with Vue project setup, consult the Vue documentation.

Installing Development Dependencies

To properly configure Webpack for Module Federation, you'll need to add some development dependencies to both of your Vue 2 projects.

Run the following command to add the required dev dependencies:

yarn add webpack webpack-cli webpack-dev-server vue-loader url-loader sass-loader mini-css-extract-plugin html-webpack-plugin dart-sass css-loader -D

After installing the dependencies, add a start script to your package.json file. This script will use Webpack-CLI to start the server. The purpose of this script is to leverage Webpack-CLI for server initialization, rather than using vue-cli.

In your package.json, update the scripts section as follows:

"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"start": "webpack-cli serve",
"lint": "vue-cli-service lint"
},

Webpack Configuration

Create a webpack.config.js file in the root directory of both projects.

Basic Configuration

The webpack.config.js file in each application must include the following code, with distinct port variables assigned to the two projects.

const path = require("path");
const { VueLoaderPlugin } = require("vue-loader");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const port = 8080;

module.exports = {
  mode: "development",
  cache: false,
  devtool: "source-map",
  target: "web",
  optimization: {
    minimize: false
  },
  entry: path.resolve(__dirname, "./src/main.ts"), // <1>
  output: { // <2>
    path: path.resolve(__dirname, "./dist"),
    publicPath: `http://localhost:${port}/`
  },
  resolve: { // <3>
    extensions: [".ts", ".js", ".vue", ".json"],
    alias: {
      vue: "vue/dist/vue.runtime.esm.js",
      "@": path.resolve(__dirname, "./src"),
      "@assets": path.resolve(__dirname, 'src/assets')
    }
  },
  module: {
    rules: [
      { test: /\.vue$/, loader: "vue-loader" },
      {
        test: /\.ts$/,
        loader: "ts-loader",
        options: { appendTsSuffixTo: [/\.vue$/] }
      },
      {
        test: /\.css|.sass|.scss$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              esModule: false
            }
          },
          "css-loader",
          "sass-loader"
        ]
      },
      {
        test: /\.png$/,
        use: {
          loader: "url-loader",
          options: {
            esModule: false,
            name: "[name].[ext]",
            limit: 8192
          }
        }
      }
    ]
  },
  plugins: [
    new VueLoaderPlugin(),
    new MiniCssExtractPlugin({
      filename: "[name].css"
    }),
  ],
  devServer: {
    static: {
      directory: path.join(__dirname, "public")
    },
    compress: true,
    port,
    hot: true,
    headers: {
      "Access-Control-Allow-Origin": "*",
      "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
      "Access-Control-Allow-Headers":
        "X-Requested-With, content-type, Authorization",
    },
  },
};
  1. Entry Point: The entry point is the file that Webpack uses as the starting point for bundling. In Vue applications, this is usually src/main.ts.
  2. Output: Specify the directory where the bundled application should be placed. By default, Vue applications are built into the dist directory.
  3. Resolve: This section is for setting module resolution and aliases. For example, you can add a shortcut to the assets folder by adding the @assets property in resolve.alias.
  4. Rules: Webpack by default understands only JavaScript and JSON files. Loaders enable Webpack to understand other file types like TypeScript, Vue, CSS, etc. We have added loaders for these types in our configuration.
  5. Plugins: Plugins serve to tailor the Webpack build process through various customization options.
  6. devServer: This section contains configurations for the development server, including the port number and other options.

Upon executing the yarn start command, the server should initialize successfully.

Debugging

After completing the webpack.config.js configuration, navigate to http://localhost:${port}. You may notice an empty page without the application's logo, indicating server issues. To resolve this, update the dynamic variables in the public/index.html file as shown below:

<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="./favicon.ico">
    <title>micro-main</title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but micro-main doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
  </body>
</html>

If the page remains blank, inspect the console for potential errors related to environment variables in the router.

To address this, add a new plugin to your webpack.config.js to enable the use of process.env. Update the plugins array as follows:

const webpack = require('webpack');

// Existing webpack.config.js content
module.exports = {
  // ...
  plugins: [
    new webpack.ProvidePlugin({
      process: 'process/browser',
    }),
    // Other existing plugins
  ],
  // ...
};

After implementing these changes, reload your application. The server and application should now function as expected.

Implementing Module Federation

Upon successfully setting up Webpack and initializing your development servers, the next step is to implement Module Federation.

Configuration for web_common Project

The ModuleFederationPlugin is central to setting up Module Federation. Below is an example configuration for the web_common project:

// Existing imports and configurations
const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  // ... existing configurations
  plugins: [
    new ModuleFederationPlugin({
      name: "web_common",
      remotes: {},
      filename: "remoteEntry.js",
      exposes: {
        "./HelloWorld": "./src/components/HelloWorld.vue"
      },
      // ... other configurations
    }),
    // ... other existing plugins
  ],
  // ... existing configurations
};

Key Points:

  • Import ModuleFederationPlugin from require("webpack").container, not directly from require("webpack").
  • The exposes object is responsible for sharing code from the project. The keys should be in the format ./[KEY_NAME].
  • The filename can be any name, though remoteEntry or web_commonRemoteEntry are recommended.
  • The name can also be any name, but it's advisable to use the name of the project.

Configuration for micro_main Project

Here is an example configuration for the micro_main project:

// Existing imports and configurations
const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  // ... existing configurations
  plugins: [
    new ModuleFederationPlugin({
      name: "micro_main",
      remotes: {
        web_common: "web_common@http://localhost:8082/remoteEntry.js"
      },
      // ... other configurations
    }),
    // ... other existing plugins
  ],
  // ... existing configurations
};

Key Points:

  • The remotes object is where you specify projects from which you are receiving shared code.
  • The web_common is the name of the project from which code is being shared.
  • The remote format should be [PROJECT_NAME]@[PROJECT_URL]/[PROJECT_filename]. In this case, the filename is remoteEntry.js.

By following these configurations, you should be able to successfully implement Module Federation in your Vue 2 projects.

Testing the Module Federation Implementation

Importing the Component Globally

To validate the Module Federation setup, import the HelloWorld component globally in your main project.

import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";

Vue.component("HelloWorld", () => import("web_common/HelloWorld"));
Vue.config.productionTip = false;

new Vue({
  router,
  store,
  render: (h) => h(App),
}).$mount("#app");

TypeScript Configuration

If your project uses TypeScript, you'll need to declare web_common as a module. Add the following declarations:

declare module "*.vue" {
  import Vue from "vue";
  export default Vue;
}

declare module "web_common/*";

Upon completing these steps, your server should operate without issues.

Using the HelloWorld Component

You can now utilize the HelloWorld component in your main project as shown below:

<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png" />
    <HelloWorld msg="Welcome to Your Vue.js + TypeScript App" />
  </div>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
@Component({
})
export default class Home extends Vue {}
</script>

Handling 404 Errors

If you encounter 404 errors when navigating to different routes and reloading the page, you can resolve this by enabling historyApiFallback in your webpack.config.js:

// webpack.config.js
module.exports = {
  // ...
  devServer: {
    historyApiFallback: true,
  },
};