I tried replacing Webpack with Rspack and here’s what I’ve found
While working as a frontend web developer, we usually use building tools like Webpack to bundle our code so that browsers can understand it. Webpack is powerful and provides all the necessary features. However, what if there’s an alternative that can perform a similar job but with faster build times?
Recently, I spent some time reading Rspack’s official documentation. Its low setup cost, user-friendly interface for Webpack users, and faster build times are all highly appealing to me.
Taking advantage of a relatively idle work period, I applied Rspack to our company’s project, and the results were quite impressive. In this article, I would like to share our project’s frontend architecture and compare the build speeds between Rspack and Webpack, hoping that this article will be helpful to someone.
TL;DR: if you’re using Webpack with custom configs, you should try replacing it with Rspack, it would save tons of your waiting time.
The Webpack config of my work project
Before we compare the performance between Rspack and Webpack, let me show you how we configure our Webpack to build CSS and JavaScript files.
Here’s the webpack.config.js
of our project:
const path = require('path');
const glob = require('glob');
const toObject = require('./scripts/toObject');
const { VueLoaderPlugin } = require("vue-loader");
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const FixStyleOnlyEntriesPlugin = require("webpack-fix-style-only-entries");
module.exports = (env, options) => {
const jsSetting = {
mode: 'production',
entry: {
'index': path.resolve(__dirname, 'src/js/entry/index.js'),
},
output: {
path: path.resolve(__dirname, 'apps/statics/dist/'),
filename: 'js/[name].js',
},
target: ['web', 'es5'],
module: {
rules: [
{
test: /\.m?js$/,
include: path.resolve(__dirname, 'src'),
exclude: /(node_modules|bower_components)/,
use: [
{
loader: 'babel-loader',
options: {
plugins: ['@babel/plugin-transform-runtime']
}
}
],
},
{
test: /\.vue$/,
use: [
'vue-loader'
]
},
{
test: /\.s?css$/i,
use: [
'vue-style-loader',
'css-loader',
'sass-loader'
],
},
{
test: /\.(png|jpe?g|gif|svg)$/i,
use: [
{
loader: 'url-loader',
options: {
name: '[path][name].[ext]',
context: 'src',
fallback: require.resolve('file-loader'),
limit: 8192,
},
},
],
},
{
test: /\.(woff|woff2|eot|ttf|otf|)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[path][name].[ext]',
context: 'src'
},
},
],
},
],
},
plugins: [
new VueLoaderPlugin(),
new MiniCssExtractPlugin({
filename: 'style/[name].css',
}),
new CleanWebpackPlugin({ verbose: true }),
],
resolve: {
alias: {
src: path.resolve(__dirname, 'src/'),
apps: path.resolve(__dirname, 'apps/'),
}
},
externals: {
vue: 'Vue',
axios: 'axios',
jquery: 'jQuery',
}
};
const cssSetting = {
mode: 'production',
entry: {
...toObject(glob.sync('apps/statics/scss/**/*.scss')),
},
output: {
path: path.resolve(__dirname, ''),
},
module: {
rules: [
{
test: /\.s?css$/i,
use: [
MiniCssExtractPlugin.loader,
{ loader: 'css-loader', options: { url: false }},
'postcss-loader',
{
loader: 'sass-loader',
options: {
implementation: require("sass"),
sassOptions: {
outputStyle: 'expanded'
}
}
}
],
},
],
},
plugins: [
new FixStyleOnlyEntriesPlugin(),
new MiniCssExtractPlugin({}),
],
resolve: {
alias: {
src: path.resolve(__dirname, 'src/'),
apps: path.resolve(__dirname, 'apps/'),
}
},
};
if (options.mode == 'development') {
jsSetting.devtool = 'eval-source-map'
}
return [
cssSetting,
jsSetting,
];
};
To simplify the content, I have removed most of the files in the entry and retained only the necessary parts for illustration purposes.
In our project, we firstly build CSS, then JavaScript. For css files, we use a library called glob
to detect all of the .scss
files. toObject
is a custom function for generating key-value pairs like this:
{
"apps/statics/scss/index": "apps/statics/scss/index.scss",
// ...
}
We use MiniCssExtractPlugin
for generating the output .js
files css-loader
made into .css
, and delete the same .js
files by FixStyleOnlyEntriesPlugin
so that we can built .scss
files into .css
files right at the same folder.
for JavaScript files, we use vue-loader
for building Vue SFC, vue-style-loader
, css-loader
and sass-loader
enableus to write scss
in .vue
files, and we use babel-loader
for building JavaScript files.
So basically those are the things we need for our project:
- Building Vue SFC files
- Building CSS files from SCSS files
- Building JavaScript files
Let’s see how we configure our Rspack settings.
My Rspack setting
Here’s my rspack.config.js
:
const path = require("path");
const glob = require("glob");
const toObject = require("./scripts/toObject");
const { VueLoaderPlugin } = require("vue-loader");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const PreventOutputJSPlugin = require("./scripts/PreventOutputJSPlugin");
module.exports = (env, options) => {
const isProd = options.mode === "production";
const cssSetting = {
mode: "development",
context: __dirname,
devtool: false,
entry: {
...toObject(glob.sync("apps/statics/scss/**/*.scss")),
},
output: {
path: path.resolve(__dirname, ""),
},
module: {
rules: [
{
test: /\.scss$/,
use: ["postcss-loader", "sass-loader"],
type: "css",
},
],
},
plugins: [new PreventOutputJSPlugin()],
resolve: {
alias: {
src: path.resolve(__dirname, "src/"),
apps: path.resolve(__dirname, "apps/"),
},
},
optimization: {
minimize: false,
},
};
const jsSetting = {
context: __dirname,
devtool: false,
entry: {
index: path.resolve(__dirname, "src/js/entry/index.js"),
},
output: {
path: path.resolve(__dirname, "apps/statics/dist/"),
filename: "js/[name].js",
},
target: ["web", "es5"],
module: {
rules: [
{
test: /\.vue$/,
loader: "vue-loader",
options: {
experimentalInlineMatchResource: true,
},
},
{
test: /\.scss$/,
use: [
{
loader: "style-loader",
options: {
esModule: false,
},
},
"css-loader",
"postcss-loader",
"sass-loader",
],
type: "javascript/auto",
},
{
test: /\.css$/,
use: [
{
loader: "style-loader",
options: {
esModule: false,
},
},
"css-loader",
],
type: "javascript/auto",
},
{
test: /\.(png|jpe?g|gif|svg)$/i,
type: "asset/inline",
},
{
test: /\.(woff|woff2|eot|ttf|otf|)$/,
type: "asset/resource",
},
],
},
plugins: [new VueLoaderPlugin()],
resolve: {
alias: {
src: path.resolve(__dirname, "src/"),
apps: path.resolve(__dirname, "apps/"),
node_modules: path.resolve(__dirname, "node_modules/"),
},
},
externals: {
vue: "Vue",
axios: "axios",
jquery: "jQuery",
},
optimization: {
minimize: isProd,
},
};
if (isProd) {
jsSetting.plugins.push(new CleanWebpackPlugin({ verbose: true }));
}
return [cssSetting, jsSetting];
};
For CSS files, we don’t need css-loader
nor MiniCssExtractPlugin
to build .scss
.
For JavaScript files, most of the configs where same as before.
There’re about 40 JavaScript files and 90 CSS files need to be built in our project, and let’s see the building performance:
Webpack
- Starts dev server: 35s
- Production build: 42s
- Dev server hot build time: ~300ms
Rspack
- Starts dev server: 15s
- Production build: 15s
- Dev server hot build time: ~170ms
As you can see, RSPack outperforms Webpack in various scenarios.
Did I replace Webpack with Rspack already?
I really want to retire Webpack to save my precious time. However, in our project, we would configure our css-loader
not to resolve the urls in our .scss
files:
{ loader: 'css-loader', options: { url: false }}
And the current version of Rspack (v0.2.9) does not support it yet. Though it still builds far faster than Webpack, it’ll also leave tons of error messages on our terminal. After discussing with my team members, we decide not to replace it for now. I’ve reported it as a feature request to Rspack’s Github issues, and hope this feature could be avaiable soon.
Conclusion
As I mentioned in the beggining, Rspack really does a good job building CSS and JavaScript files comparing with Webpack, and it won’t cost a lot of times migrating from Webpack. So if you want to reduce your building time, don’t hesitate, try it!
I hope this article helps, and if you have any suggestions, please let me know. See you next month!
References: