Vue2/CLI 项目从 webpack 迁移到 rspack 的完整指南
在前端开发领域,构建工具的选择对项目的开发效率和性能有着重要影响。近年来,rspack 作为一个由 Rust 语言编写的高性能 JavaScript 打包工具,因其卓越的构建速度和与 webpack 良好的兼容性,受到了越来越多开发者的关注。本文将详细记录如何将一个基于 Vue2/CLI(webpack)的项目迁移到 rspack,以获得更快的构建速度和更好的开发体验。
迁移背景
为什么选择 rspack?
- 更快的构建速度:rspack 基于 Rust 开发,构建速度明显快于 webpack
- 兼容性好:rspack 在设计上保持了与 webpack 的高度兼容
- 更少的配置:rspack 简化了很多配置,使项目设置更加清晰
- 现代化:支持最新的 JavaScript 特性和模块系统
迁移步骤
1. 移除 Vue CLI 相关依赖
首先,我们需要移除项目中与 Vue CLI 相关的依赖:
npm remove @vue/cli-service @vue/cli-plugin-babel @vue/cli-plugin-eslint core-js
2. 安装 rspack 相关依赖
接下来,安装 rspack 及其插件:
npm add @rsbuild/core @rsbuild/plugin-vue2 @rsbuild/plugin-babel @rsbuild/plugin-less @rsbuild/plugin-node-polyfill @rsbuild/plugin-sass cross-env dotenv-webpack postcss postcss-replace -D
这些依赖包各自的作用:
@rsbuild/core: rspack 的核心包@rsbuild/plugin-vue2: 用于支持 Vue2 的插件@rsbuild/plugin-babel: 用于 JS 转译的 Babel 插件@rsbuild/plugin-less: 用于处理 Less 文件@rsbuild/plugin-node-polyfill: 用于提供 Node.js 核心模块的 polyfill@rsbuild/plugin-sass: 用于处理 Sass 文件cross-env: 用于跨平台设置环境变量dotenv-webpack: 用于加载 .env 文件postcss和postcss-replace: 用于处理 CSS,特别是解决 Vue2 中的/deep/选择器问题
3. 更新 package.json 中的 scripts
修改 package.json 文件中的 scripts 部分:
"scripts": {
"serve": "cross-env NODE_ENV=development rsbuild dev --mode=development",
"build": "cross-env NODE_ENV=production rsbuild build --mode=production",
"build:stage": "cross-env NODE_ENV=staging rsbuild build --mode=production",
"preview": "rsbuild preview",
"lint": "rsbuild lint"
}
4. 创建 rspack 配置文件
在项目根目录创建 rsbuild.config.js 文件,这将是 rspack 的主要配置文件:
import { defineConfig, loadEnv, rspack } from '@rsbuild/core';
import { pluginVue2 } from '@rsbuild/plugin-vue2';
import { pluginBabel } from "@rsbuild/plugin-babel";
import { pluginLess } from "@rsbuild/plugin-less";
import { pluginSass } from "@rsbuild/plugin-sass";
import { pluginNodePolyfill } from "@rsbuild/plugin-node-polyfill"
import path from 'path';
// 加载环境变量
const { publicVars } = loadEnv({ prefixes: ['VUE_APP_', "BASE_URL", "NODE_ENV"] });
const isProduction = process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'staging';
export default defineConfig({
mode: isProduction ? 'production' : 'development',
plugins: [
pluginVue2(),
pluginBabel(),
pluginLess(),
pluginSass(),
pluginNodePolyfill()
],
source: {
// 指定入口文件
entry: {
index: './src/main.js',
},
define: publicVars,
include: ['src'],
exclude: ['node_modules']
},
output: {
path: 'dist',
filename: 'static/js/[name].[contenthash:8].js',
distPath: {
root: 'dist' //dist目录
},
clean: true, //清理dist目录
publicPath: '/',
polyfill: "usage" // 浏览器兼容性
},
resolve: {
extensions: ['.js', '.vue', '.ts', '.tsx', '.jsx'],
alias: {
'@': path.resolve(__dirname, './src')
}
},
modules: {
rules: [
{
test: /\.vue$/,
use: ['vue-loader', 'postcss-loader', {
loader: 'postcss-loader',
options: {
postcssOptions: {
config: true // 使用postcss.config.mjs
}
}
}]
},
{
test: /\.(css|less|sass|scss)$/,
use: ['style-loader', 'css-loader', 'less-loader', 'sass-loader', {
loader: 'postcss-loader', options: {
postcssOptions: {
config: true // 使用postcss.config.mjs
}
}
}],
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/i,
type: 'asset',
generator: {
filename: 'static/img/[name].[contenthash:8][ext]'
}
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
generator: {
filename: 'static/font/[name].[contenthash:8][ext]'
}
},
{
test: /\.js$/,
exclude: /node_modules[\\/]core-js/,
use: {
loader: 'builtin:swc-loader',
options: {
jsc: {
target: 'es2020',
},
env: {
mode: 'usage',
coreJs: '3.26.1',
targets: [
'chrome >= 87',
'edge >= 88',
'firefox >= 75',
'safari >= 14',
],
},
isModule: 'unknown',
// ...other options
},
},
},
]
},
html: {
template: './public/index.html', //设置html的模板
},
server: {
proxy: [
{
context: ['/api'],
target: 'http://localhost:3000',
changeOrigin: true,
// pathRewrite:{
// '^/api':''
// }
secure: false,
ws: false,
logLevel: 'debug',
}
],
open: true,
hot: true,
port: 8080,
},
optimization: {
moduleIds: isProduction ? 'deterministic' : 'named',
chunkIds: isProduction ? 'deterministic' : 'named',
mergeDuplicateChunks: true,
removeEmptyChunks: true,
runtimeChunk: 'single',
realContentHash: isProduction,
innerGraph: isProduction,
providedExports: isProduction,
usedExports: isProduction,
sideEffects: isProduction,
concatenateModules: isProduction,
minimize: true,
minimizer: [
],
splitChunks: {
chunks: 'async',
minChunks: 1,
minSize: 20000,
maxAsyncRequests: 30,
maxInitialRequests: 30,
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
}
}
}
},
tools: {
rspack: {
plugins: [
new rspack.optimize.LimitChunkCountPlugin({
maxChunks: 5
}),
new rspack.SwcJsMinimizerRspackPlugin({
extractComments: false, //移除注释
minimizerOptions: {
test: /\.[cm]?js$/,
format: {
comments: false // 移除注释
},
compress: {
passes: 8
}
}
}),
new rspack.LightningCssMinimizerRspackPlugin({
minimizerOptions: {
errorRecovery: false, // 错误恢复
nonStandard: true, // 非标准语法
}
})
]
}
}
});
5. 处理 /deep/ 选择器问题
Vue2 项目中常用的 /deep/ 选择器在新的构建系统中可能会有兼容性问题,我们需要创建一个 PostCSS 配置文件来处理这个问题。
在项目根目录创建 postcss.config.mjs 文件:
// 为了解决/deep/问题,需要在postcss.config.mjs中添加如下代码
import postcss from 'postcss'
import postcssSelectorParser from 'postcss-selector-parser'
export default {
plugins: [
postcss.plugin('replace-deep', () => {
return (root) => {
root.walkRules((rule) => {
rule.selector = postcssSelectorParser((selectors) => {
selectors.walk((selector) => {
if (selector.type === 'combinator' && selector.value === '/deep/') {
selector.value = '::v-deep ';
}
})
}).processSync(rule.selector);
});
}
})
]
}
这个配置会将 CSS 中的 /deep/ 选择器自动转换为 ::v-deep,以保持兼容性。
配置详解
核心概念
让我们更详细地解释 rsbuild.config.js 文件中的关键配置项:
1. 模式和插件
mode: isProduction ? 'production' : 'development',
plugins: [
pluginVue2(),
pluginBabel(),
pluginLess(),
pluginSass(),
pluginNodePolyfill()
],
mode根据环境变量设置为开发模式或生产模式plugins加载各种必要的插件来支持 Vue2、Babel、Less、Sass 和 Node.js polyfill
2. 源代码配置
source: {
entry: {
index: './src/main.js',
},
define: publicVars,
include: ['src'],
exclude: ['node_modules']
},
entry指定应用的入口文件,与 Vue CLI 项目保持一致define将环境变量注入到代码中,类似于 webpack 的 DefinePlugininclude/exclude指定哪些文件应该被处理,哪些应该被忽略
3. 输出配置
output: {
path: 'dist',
filename: 'static/js/[name].[contenthash:8].js',
distPath: {
root: 'dist'
},
clean: true,
publicPath: '/',
polyfill: "usage"
},
path和distPath指定输出目录filename指定输出文件的名称和路径clean在每次构建前清理输出目录polyfill设置为 "usage" 表示自动添加所需的 polyfill
4. 解析配置
resolve: {
extensions: ['.js', '.vue', '.ts', '.tsx', '.jsx'],
alias: {
'@': path.resolve(__dirname, './src')
}
},
extensions指定可以省略的扩展名alias设置模块别名,保持与 Vue CLI 项目一致,使@指向src目录
5. 模块规则
modules: {
rules: [
// Vue 文件处理
{
test: /\.vue$/,
use: ['vue-loader', 'postcss-loader', {
loader: 'postcss-loader',
options: {
postcssOptions: {
config: true
}
}
}]
},
// CSS 处理
{
test: /\.(css|less|sass|scss)$/,
use: ['style-loader', 'css-loader', 'less-loader', 'sass-loader', {
loader: 'postcss-loader', options: {
postcssOptions: {
config: true
}
}
}],
},
// 图片处理
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/i,
type: 'asset',
generator: {
filename: 'static/img/[name].[contenthash:8][ext]'
}
},
// 字体处理
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
generator: {
filename: 'static/font/[name].[contenthash:8][ext]'
}
}
]
},
这部分配置定义了如何处理不同类型的文件:
- Vue 单文件组件
- CSS、Less 和 Sass 样式文件
- 图片和 SVG 文件
- 字体文件
6. HTML 模板配置
html: {
template: './public/index.html',
},
6.1 HTML 的模板
将 <link rel="icon" href="<%= BASE_URL %>favicon.ico"> 改为下边的
+ <link rel="icon" href="<%= assetPrefix %>/favicon.ico">
指定 HTML 模板文件位置,与 Vue CLI 项目保持一致。
7. 开发服务器配置
server: {
proxy: [
{
context: ['/api'],
target: 'http://localhost:3000',
changeOrigin: true,
secure: false,
ws: false,
logLevel: 'debug',
}
],
open: true,
hot: true,
port: 8080,
},
配置开发服务器,包括:
- API 代理设置
- 自动打开浏览器
- 热更新
- 端口设置
8. 优化配置
optimization: {
moduleIds: isProduction ? 'deterministic' : 'named',
chunkIds: isProduction ? 'deterministic' : 'named',
mergeDuplicateChunks: true,
removeEmptyChunks: true,
runtimeChunk: 'single',
realContentHash: isProduction,
innerGraph: isProduction,
providedExports: isProduction,
usedExports: isProduction,
sideEffects: isProduction,
concatenateModules: isProduction,
minimize: true,
// ... 压缩配置和代码分割配置
}
这部分配置关注性能优化,根据环境(开发或生产)设置不同的优化选项。
迁移后的性能改进
从 webpack 迁移到 rspack 后,你可能会观察到以下性能改进:
- 构建速度显著提升:rspack 基于 Rust 开发,构建速度可能比 webpack 快 5-10 倍
- 内存占用减少:rspack 通常比 webpack 消耗更少的内存
- 热更新速度更快:开发模式下的修改反映更迅速
- 构建产物优化:通过优化配置,可以得到更小、更高效的输出文件
迁移中可能遇到的问题及解决方案
1. 插件兼容性问题
问题:webpack 有丰富的插件生态,但并非所有插件都与 rspack 兼容。
解决方案:
- 优先使用 rspack 官方提供的插件
- 寻找替代插件或解决方案
- 当实在没有替代方案时,可能需要保留部分 webpack 配置或修改代码
2. 样式处理问题
问题:Vue2 中特有的 /deep/ 选择器可能会导致样式失效。
解决方案:
如我们在上面配置的 postcss.config.mjs,通过 PostCSS 插件将 /deep/ 转换为 ::v-deep。
3. 环境变量处理
问题:Vue CLI 和 rspack 处理环境变量的方式有所不同。
解决方案:
使用 loadEnv 函数并指定正确的前缀(如 'VUE_APP_')来加载环境变量。
总结
将 Vue2/CLI 项目从 webpack 迁移到 rspack 可能需要一定的工作,但回报是显著的性能提升和更好的开发体验。本文详细介绍了迁移步骤、配置详情和潜在问题的解决方案,希望能帮助你顺利完成迁移过程。
rspack 作为一个相对较新的打包工具,其生态系统仍在发展中,但其与 webpack 的高兼容性使得迁移过程相对平滑。随着项目的发展和 rspack 的不断完善,你可能需要进一步调整配置以满足特定需求。