Vite + Webpack + Rspack with Module Federation

Vite is the first build tool Zephyr is able to handle Module Federation configuration directly. This guide aims to walk you through how you can deploy a Micro-Frontend application using the Official Vite Plugin from Module Federation. After this guide, you will have a React application consuming remote applications bundled by Vite, Rspack and Webpack deployed through Zephyr Cloud.

Prerequisites

If you don't have nvm to manage your node version yet, head to the official nvm guide to install it.

1nvm use 20

Install Zephyr Plugins

For applications built with Vite:

Terminal
1npm i vite-plugin-zephyr@latest

For applications built with Webpack and Rspack:

Terminal
1npm i zephyr-webpack-plugin@latest

Example configuration

Four example build configuration for Vite, Rspack and Webpack.

Vite Host

vite.config.ts
1import { defineConfig } from 'vite';
2import react from '@vitejs/plugin-react';
3import { withZephyr } from 'vite-plugin-zephyr';
4
5const mfConfig = {
6  name: 'vite-host',
7  filename: 'remoteEntry.js',
8  remotes: {
9    'vite-remote': {
10      entry: 'http://localhost:5174/remoteEntry.js',
11      type: 'module',
12    },
13    vite_webpack: {
14      entry: 'http://localhost:8080/remoteEntry.js',
15      type: 'var',
16    },
17    vite_rspack: {
18      entry: 'http://localhost:8081/remoteEntry.js',
19      type: 'var',
20    },
21  },
22  shared: {
23    react: {
24      singleton: true,
25    },
26    'react-dom': {
27      singleton: true,
28    },
29  },
30};
31
32export default defineConfig({
33  plugins: [react(), withZephyr(mfConfig)],
34  build: {
35    target: 'chrome89',
36  },
37});

Vite Remote

vite.config.ts
1import { defineConfig } from 'vite';
2import react from '@vitejs/plugin-react';
3import { withZephyr } from 'vite-plugin-zephyr';
4
5const mfConfig = {
6  name: 'vite-remote',
7  filename: 'remoteEntry.js',
8  exposes: {
9    './Button': './src/Button',
10  },
11  shared: ['react', 'react-dom'],
12};
13
14// https://vitejs.dev/config/
15export default defineConfig({
16  plugins: [react(), withZephyr({ ...mfConfig })],
17  experimental: {
18    renderBuiltUrl() {
19      return { relative: true };
20    },
21  },
22  build: {
23    target: 'chrome89',
24  },
25});

Rspack Remote

Example project created via npx create-mf-app

rspack.config.js
1const rspack = require('@rspack/core');
2const refreshPlugin = require('@rspack/plugin-react-refresh');
3const isDev = process.env.NODE_ENV === 'development';
4const path = require('path');
5const { withZephyr } = require('zephyr-webpack-plugin');
6
7const printCompilationMessage = require('./compilation.config.js');
8
9/**
10 * @type {import('@rspack/cli').Configuration}
11 */
12module.exports = withZephyr()({
13  context: __dirname,
14  entry: {
15    main: './src/index.tsx',
16  },
17
18  devServer: {
19    port: 8081,
20    historyApiFallback: true,
21    watchFiles: [path.resolve(__dirname, 'src')],
22    onListening: function (devServer) {
23      const port = devServer.server.address().port;
24
25      printCompilationMessage('compiling', port);
26
27      devServer.compiler.hooks.done.tap('OutputMessagePlugin', (stats) => {
28        setImmediate(() => {
29          if (stats.hasErrors()) {
30            printCompilationMessage('failure', port);
31          } else {
32            printCompilationMessage('success', port);
33          }
34        });
35      });
36    },
37  },
38  experiments: {
39    css: true,
40  },
41  resolve: {
42    extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
43  },
44  module: {
45    rules: [
46      {
47        test: /\.(svg|png)$/,
48        type: 'asset',
49      },
50      {
51        test: /\.css$/,
52        use: [
53          {
54            loader: 'postcss-loader',
55            options: {
56              postcssOptions: {
57                plugins: {
58                  tailwindcss: {},
59                  autoprefixer: {},
60                },
61              },
62            },
63          },
64        ],
65        type: 'css',
66      },
67      {
68        test: /\.(jsx?|tsx?)$/,
69        use: [
70          {
71            loader: 'builtin:swc-loader',
72            options: {
73              sourceMap: true,
74              jsc: {
75                parser: {
76                  syntax: 'typescript',
77                  tsx: true,
78                },
79                transform: {
80                  react: {
81                    runtime: 'automatic',
82                    development: isDev,
83                    refresh: isDev,
84                  },
85                },
86                target: 'es2020',
87              },
88            },
89          },
90        ],
91      },
92    ],
93  },
94  plugins: [
95    new rspack.container.ModuleFederationPlugin({
96      name: 'vite_rspack',
97      filename: 'remoteEntry.js',
98      exposes: {
99        './Image': './src/Image',
100      },
101      shared: ['react', 'react-dom'],
102    }),
103    new rspack.DefinePlugin({
104      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
105    }),
106    new rspack.ProgressPlugin({}),
107    new rspack.HtmlRspackPlugin({
108      template: './src/index.html',
109    }),
110    isDev ? new refreshPlugin() : null,
111  ].filter(Boolean),
112});

Webpack Remote

Example project created via npx create-mf-app

webpack.config.js
1const HtmlWebPackPlugin = require('html-webpack-plugin');
2//const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
3const path = require('path');
4const Dotenv = require('dotenv-webpack');
5const { withZephyr } = require('zephyr-webpack-plugin');
6const { ModuleFederationPlugin } = require('@module-federation/enhanced/webpack');
7
8const deps = require('./package.json').dependencies;
9
10const printCompilationMessage = require('./compilation.config.js');
11
12module.exports = (_, argv) =>
13  withZephyr()({
14    output: {
15      publicPath: 'auto',
16    },
17
18    resolve: {
19      extensions: ['.tsx', '.ts', '.jsx', '.js', '.json'],
20    },
21
22    devServer: {
23      port: 8080,
24      historyApiFallback: true,
25      watchFiles: [path.resolve(__dirname, 'src')],
26      onListening: function (devServer) {
27        const port = devServer.server.address().port;
28
29        printCompilationMessage('compiling', port);
30
31        devServer.compiler.hooks.done.tap('OutputMessagePlugin', (stats) => {
32          setImmediate(() => {
33            if (stats.hasErrors()) {
34              printCompilationMessage('failure', port);
35            } else {
36              printCompilationMessage('success', port);
37            }
38          });
39        });
40      },
41    },
42
43    module: {
44      rules: [
45        {
46          test: /\.(svg|png)$/,
47          type: 'asset',
48        },
49        {
50          test: /\.m?js/,
51          type: 'javascript/auto',
52          resolve: {
53            fullySpecified: false,
54          },
55        },
56        {
57          test: /\.(css|s[ac]ss)$/i,
58          use: ['style-loader', 'css-loader', 'postcss-loader'],
59        },
60        {
61          test: /\.(ts|tsx|js|jsx)$/,
62          exclude: /node_modules/,
63          use: {
64            loader: 'babel-loader',
65          },
66        },
67      ],
68    },
69
70    plugins: [
71      new ModuleFederationPlugin({
72        name: 'vite_webpack',
73        filename: 'remoteEntry.js',
74        exposes: {
75          './Image': './src/Image',
76        },
77        shared: {
78          react: {
79            singleton: true,
80          },
81          'react-dom': {
82            singleton: true,
83          },
84        },
85      }),
86      new HtmlWebPackPlugin({
87        template: './src/index.html',
88      }),
89      new Dotenv(),
90    ],
91  });

Start from scratch