Migrate an Nx workspace from Webpack to Rspack

If you have an Nx workspace setup for Micro-Frontend application in place with Webpack and curious about how to migrate to Rspack and use Zephyr - this documentation is for you.

Prerequisites
  • You have a pre-existing Nx workspace with React based on Webpack
  • We expect you to have finish our Get Started guide.
  • Have our browser extension installed in your browser.
  • A registered account on Zephyr Cloud.
  • npm >=10
  • node >=20

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

nvm use 20

Video Walk-through

Example in Code

An example contains line-by-line changes in our example repository.

Installation

Within your project directory, run below commands to install required packages for rspack and Zephyr's npm package:

Terminal
npm i @nx/rspack @rspack/dev-server [email protected] --force

Change import path

  1. module-federation.config.ts

Every module-federation.config.ts's import needs to be replaced by

module-federation.config.ts
// import { ModuleFederationConfig } from '@nx/webpack'; 
import { ModuleFederationConfig } from '@nx/rspack/module-federation';
  1. webpack.config.ts and webpack.config.prod.ts

Rename webpack.config.ts to rspack.config.ts and change all import path to below. While changing the import path, we will also add Zephyr's plugin to build configuration.

host and remotes / rspack.config.ts
1// import { composePlugins, withNx, ModuleFederationConfig } from '@nx/webpack';
2// import { withReact } from '@nx/react';
3// import { withModuleFederation } from '@nx/react/module-federation';
4import { composePlugins, withNx, withReact } from '@nx/rspack';
5import { withModuleFederation, ModuleFederationConfig } from '@nx/rspack/module-federation';
6import { withZephyr } from 'zephyr-webpack-plugin'; // import Zephyr
7
8import baseConfig from './module-federation.config';
9
10const config: ModuleFederationConfig = {
11  ...baseConfig,
12};
13// Nx plugins for webpack to build config object from Nx options and context.
14/**
15 * DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support for Module Federation
16 * The DTS Plugin can be enabled by setting dts: true
17 * Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html
18 */
19export default composePlugins(
20  withNx(),
21  withReact(),
22  withModuleFederation(config, { dts: false }),
23  withZephyr() // add zephyr in plugin
24);

Rename all webpack.config.prod.ts to rspack.config.prod.ts and within the file (of each remotes), change the import path:

remote*/rspack.config.prod.ts
export default require('./rspack.config');

For Host application's rspack.config.prod.ts, you will need to do the same changes as rspack.config.ts:

host/rspack.config.prod.ts
1// import { composePlugins, withNx } from '@nx/webpack';
2// import { withReact } from '@nx/react';
3// import { withModuleFederation } from '@nx/react/module-federation';
4// import { ModuleFederationConfig } from '@nx/webpack';
5import { composePlugins, withNx, withReact } from '@nx/rspack';
6import { withModuleFederation, ModuleFederationConfig } from '@nx/rspack/module-federation';
7import { withZephyr } from 'zephyr-webpack-plugin';
8
9import baseConfig from './module-federation.config';
10
11const prodConfig: ModuleFederationConfig = {
12  ...baseConfig,
13  /*
14   * Remote overrides for production.
15   * Each entry is a pair of a unique name and the URL where it is deployed.
16   *
17   * e.g.
18   * remotes: [
19   *   ['app1', 'http://app1.example.com'],
20   *   ['app2', 'http://app2.example.com'],
21   * ]
22   *
23   * You can also use a full path to the remoteEntry.js file if desired.
24   *
25   * remotes: [
26   *   ['app1', 'http://example.com/path/to/app1/remoteEntry.js'],
27   *   ['app2', 'http://example.com/path/to/app2/remoteEntry.js'],
28   * ]
29   */
30  remotes: [
31    ['remote1', 'http://localhost:4201/'],
32    ['remote2', 'http://localhost:4202/'],
33  ],
34};
35// Nx plugins for webpack to build config object from Nx options and context.
36/**
37 * DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support for Module Federation
38 * The DTS Plugin can be enabled by setting dts: true
39 * Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html
40 */
41export default composePlugins(
42  withNx(),
43  withReact(),
44  withModuleFederation(prodConfig, { dts: false }),
45  withZephyr()
46);

Edit project.json

In every project.json, you will need to edit below changes:

  1. Change build executor to @nx/rspack:rspack 2. Add "target":"web" to build options (this inferred a )
  2. Under build's options -- change all "webpackConfig":"apps/{appName}/webpack.config.ts" to "rspackConfig": "apps/{appName}/rspack.config.ts"
  3. Under configurations's production config -- change all "webpackConfig":"apps/{appName}/webpack.config.prod.ts" to "rspackConfig": "apps/{appName}/rspack.config.prod.ts"
  4. Edit executor in serve -- change @nx/react:module-federation-dev-server to "@nx/rspack:module-federation-dev-server"
host or remotes project.json
1{
2  "name": "remote1",
3  "$schema": "../../node_modules/nx/schemas/project-schema.json",
4  "sourceRoot": "apps/remote1/src",
5  "projectType": "application",
6  "tags": [],
7  "targets": {
8    "build": {
9      "executor": "@nx/rspack:rspack",
10      "outputs": [
11        "{options.outputPath}"
12      ],
13      "defaultConfiguration": "production",
14      "options": {
15        "target": "web",
16        "compiler": "babel",
17        "outputPath": "dist/apps/remote1",
18        "index": "apps/remote1/src/index.html",
19        "baseHref": "/",
20        "main": "apps/remote1/src/main.ts",
21        "tsConfig": "apps/remote1/tsconfig.app.json",
22        "assets": [
23          "apps/remote1/src/favicon.ico",
24          "apps/remote1/src/assets"
25        ],
26        "styles": [
27          "apps/remote1/src/styles.css"
28        ],
29        "scripts": [],
30        "rspackConfig": "apps/remote1/rspack.config.ts"
31      },
32      "configurations": {
33        "development": {
34          "extractLicenses": false,
35          "optimization": false,
36          "sourceMap": true,
37          "vendorChunk": true
38        },
39        "production": {
40          "fileReplacements": [
41            {
42              "replace": "apps/remote1/src/environments/environment.ts",
43              "with": "apps/remote1/src/environments/environment.prod.ts"
44            }
45          ],
46          "optimization": true,
47          "outputHashing": "all",
48          "sourceMap": false,
49          "namedChunks": false,
50          "extractLicenses": true,
51          "vendorChunk": false,
52          "rspackConfig": "apps/remote1/rspack.config.prod.ts"
53        }
54      }
55    },
56    "serve": {
57      "executor": "@nx/rspack:module-federation-dev-server",
58      "defaultConfiguration": "development",
59      "options": {
60        "buildTarget": "remote1:build",
61        "hmr": true,
62        "port": 4201
63      },
64      "configurations": {
65        "development": {
66          "buildTarget": "remote1:build:development"
67        },
68        "production": {
69          "buildTarget": "remote1:build:production",
70          "hmr": false
71        }
72      }
73    },
74    "lint": {
75      "executor": "@nx/eslint:lint"
76    },
77    "serve-static": {
78      "executor": "@nx/web:file-server",
79      "defaultConfiguration": "production",
80      "options": {
81        "buildTarget": "remote1:build",
82        "watch": false,
83        "port": 4201
84      },
85      "configurations": {
86        "development": {
87          "buildTarget": "remote1:build:development"
88        },
89        "production": {
90          "buildTarget": "remote1:build:production"
91        }
92      }
93    },
94    "test": {
95      "executor": "@nx/jest:jest",
96      "outputs": [
97        "{workspaceRoot}/coverage/{projectRoot}"
98      ],
99      "options": {
100        "jestConfig": "apps/remote1/jest.config.ts"
101      }
102    }
103  }
104}

Add package.json

In each of the applications, hosts or remotes, it will need a package.json file with name and version field in order for Zephyr to map the remote's name.

If you are naming host application in Module Federation configuration like:

module-federation.config.ts
const config: ModuleFederationConfig = {
  name: 'host',

  remotes: ['remote1', 'remote2'],
};

export default config;

You will need a package.json like so within the host's folder:

package.json
{
    "name": "host", 
    "version": "0.0.0" // Versioning depending on you, it's a required field for Zephyr 
}

Deploy through Zephyr

Before we deploy to Zephyr, make sure:

  • this repository is a git repository
  • Have pre-existing commit hash
  • Have a remote.origin.url

Since this is a Micro-Frontend application, you will need to build the remote first by running each of these command in sequence. Remotes must be built first for Zephyr to map them in host applications. Read more.

First:

Terminal
npx nx run remote1:build

Second:

Terminal
npx nx run remote2:build

Third:

Terminal
npx nx run host:build