Frontend Development 10 min read

Migrating Excalidraw from Webpack to Rspack: A Step‑by‑Step Guide

This article demonstrates how to migrate the open‑source Excalidraw drawing application from Webpack to the Rust‑based Rspack bundler, covering repository cloning, dependency installation, Rspack initialization, Sass, HTML plugin, environment variable handling, static asset copying, performance comparison, and the use of react‑scripts‑rspack for automated migration.

ByteDance Web Infra
ByteDance Web Infra
ByteDance Web Infra
Migrating Excalidraw from Webpack to Rspack: A Step‑by‑Step Guide

Last month ByteDance open‑sourced Rspack, a new Rust‑based build engine that maintains good compatibility with the Webpack API while delivering a 5‑10× performance boost.

In this tutorial we use the popular open‑source drawing tool Excalidraw (written in TypeScript and built with create‑react‑app ) as a real‑world example to test Rspack.

Clone the Excalidraw repository

git clone [email protected]:excalidraw/excalidraw.git

Install dependencies and start the project:

yarn
yarn start

The application launches successfully, confirming the original setup works.

Initialize Rspack

Install the Rspack CLI:

yarn add @rspack/cli

Configure the entry point in package.json and create rspack.config.js :

{
  "scripts": {
    "build:rspack": "rspack build",
    "start:rspack": "rspack serve"
  },
  "dependencies": {
    "@rspack/cli": "0.1.4"
  }
}
module.exports = {
  context: __dirname,
  entry: { main: './src/index.tsx' }
};

Sass compilation

Add sass-loader to package.json and configure it in rspack.config.js :

{
  "dependencies": { "sass-loader": "13.2.2" }
}
module.exports = {
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: [{ loader: "sass-loader" }],
        type: 'css'
      }
    ]
  }
};

HTML plugin

Install @rspack/plugin-html and configure it to process the HTML template:

{
  "dependencies": { "@rspack/plugin-html": "0.1.4" }
}
module.exports = {
  plugins: [
    new html({
      template: "./public/index.html",
      templateParameters: false
    })
  ]
};

Environment variables

Install dotenv and expand it to load .env.development or .env.production files, then define the variables for Rspack:

{
  "dependencies": { "dotenv": "16.0.1" }
}
const env = process.env.NODE_ENV || "development";
const dotEnvFiles = env === "development" ? [".env.development"] : [".env.production"];

dotEnvFiles.forEach(doteEnvFile => {
  require("dotenv-expand")(require("dotenv").config({ path: doteEnvFile }));
});

const REACT_APP = /^REACT_APP_/i;
const filterEnv = {};
const define = Object.keys(process.env)
  .filter(key => REACT_APP.test(key))
  .reduce((env, key) => {
    filterEnv[key] = process.env[key];
    env[`process.env.${key}`] = JSON.stringify(process.env[key]);
    return env;
  }, {});

module.exports = {
  builtins: {
    define: {
      ...define,
      "import.meta.env && import.meta.env.MODE": JSON.stringify(env),
      "process.env": JSON.stringify(filterEnv)
    }
  }
};

Static assets (favicon)

Use Rspack’s built‑in copy feature (equivalent to copy‑webpack‑plugin ) to copy the public folder except index.html :

module.exports = {
  builtins: {
    copy: {
      patterns: [{
        from: "public",
        globOptions: { ignore: ["**/index.html"] }
      }]
    }
  }
};

Performance comparison

After migration the build time dropped from 51 s to 3 s , a more than ten‑fold improvement.

Full Rspack configuration

const html = require("@rspack/plugin-html").default;
const env = process.env.NODE_ENV || "development";
const dotEnvFiles = env === "development" ? [".env.development"] : [".env.production"];

dotEnvFiles.forEach(doteEnvFile => {
  require("dotenv-expand")(require("dotenv").config({ path: doteEnvFile }));
});

const REACT_APP = /^REACT_APP_/i;
const filterEnv = {};
const define = Object.keys(process.env)
  .filter(key => REACT_APP.test(key))
  .reduce((env, key) => {
    filterEnv[key] = process.env[key];
    env[`process.env.${key}`] = JSON.stringify(process.env[key]);
    return env;
  }, {});

module.exports = {
  entry: { main: "./src/index.tsx" },
  module: {
    rules: [{
      test: /\.scss$/,
      use: [{ loader: "sass-loader" }],
      type: "css"
    }]
  },
  builtins: {
    define: {
      ...define,
      "import.meta.env && import.meta.env.MODE": JSON.stringify(process.env.NODE_ENV || "production"),
      "process.env": JSON.stringify(filterEnv)
    },
    copy: {
      patterns: [{
        from: "public",
        globOptions: { ignore: ["**/index.html"] }
      }]
    }
  },
  plugins: [
    new html({
      template: "./public/index.html",
      templateParameters: false
    })
  ]
};

One‑click migration with react-scripts-rspack

Install the helper package and run the familiar commands:

// Development
react-scripts-rspack start
// Production build
react-scripts-rspack build

See the real migration PR for reference: https://github.com/excalidraw/excalidraw/pull/6425

Conclusion

The migration of a relatively complex Webpack project like Excalidraw to Rspack is straightforward thanks to Rspack’s API compatibility and its support for common loaders and plugins such as sass-loader , HTML and copy plugins. The effort yields a ten‑fold build‑time reduction, making Rspack an attractive option for future projects.

RspackWebpack migrationexcalidrawSASSbuild performanceEnvironment VariablesHTML plugin
ByteDance Web Infra
Written by

ByteDance Web Infra

ByteDance Web Infra team, focused on delivering excellent technical solutions, building an open tech ecosystem, and advancing front-end technology within the company and the industry | The best way to predict the future is to create it

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.