公司有许多项目仍然停留在 Vue 2 上,每次通过 VueCLI 启动都需要等待大约 30 秒(在 M1 芯片上),每次热重载的速度也让人难以忍受,构建速度同样缓慢。虽然可以通过 esbuild 等工具改善,但在某天多次修改需求后,我决定忍无可忍,于是有了这篇文章。
决定迁移的原因主要有两点:
- 对 Node 22 支持不友好。由于项目基于 VueCLI 3,Node 版本的诸多变更导致无法启动,必须通过 nvm 切换到 16 版本。
- 构建和热更新速度慢。
在决定使用 Rsbuild 之后,我也进行了一番调研,备选方案有四个:
- Webpack+SWC 或 Webpack+Esbuild
- Vite
- Rsbuild
- Turbopack
以下是一些考虑:
- 方案 1:速度虽然有所改善,但仍然基于 Webpack,整体优化空间有限。
- Vite:虽然非常想迁移,但由于有许多使用 CommonJS 的包,迁移成本太大。
- Rsbuild:Rust 版本的 Webpack,大部分 Webpack 配置都兼容,是首选。
- Turbopack:暂时不支持 Vue 等框架。
1. 遵循官方迁移指南
官方提供了一个简短的迁移指南,可以根据文档对项目中涉及的部分进行更改。
2. 删除与 @vue/cli 相关的依赖
例如,我删除了以下依赖:
json
{
"@vue/cli-plugin-babel": "~4.5.13",
"@vue/cli-plugin-eslint": "~4.5.13",
"@vue/cli-plugin-router": "~4.5.13",
"@vue/cli-plugin-vuex": "~4.5.13",
"@vue/cli-service": "~4.5.13"
}
3. 修订别名和路径简写
在项目中,通常会使用 @/xxx
的形式来引入模块,这属于 Webpack 的 alias 功能。此外,在 import
导入文件时可以省略 .vue
的扩展名,这也是 Webpack 的 extensions
功能。
在 Rsbuild 中,可以通过以下方式定义:
javascript
export default defineConfig({
tools: {
rspack: {
devtool: "source-map",
resolve: {
extensions: [".vue", ".js", ".jsx", ".tsx", ".ts", ".json"],
alias: {
"@": path.resolve(__dirname, "src"),
},
},
},
},
});
4. 安装 JSX、Less 和 SCSS 支持
项目中经常会使用 Less,例如引入的 ant-design-vue 就使用了,因此需要支持 Less。
由于之前没有统一标准,项目需要同时支持 Less 和 SCSS。
Less 官方维护了一个插件,可以按照使用方式安装:Less 插件
对于 Sass 和 JSX,按照官方指南安装相应插件:Sass 插件 Vue2 JSX 插件
整理后的配置文件如下:
javascript
import { defineConfig } from "@rsbuild/core";
import { pluginLess } from "@rsbuild/plugin-less";
import { pluginSass } from "@rsbuild/plugin-sass";
import { pluginBabel } from "@rsbuild/plugin-babel";
import { pluginVue2 } from "@rsbuild/plugin-vue2";
import { pluginVue2Jsx } from "@rsbuild/plugin-vue2-jsx";
export default defineConfig({
plugins: [
pluginBabel({
include: /\.(?:jsx|tsx)$/,
}),
pluginVue2(),
pluginVue2Jsx(),
pluginLess({
lessLoaderOptions: {
lessOptions: {
modifyVars: {
"primary-color": "#5384FE",
"error-color": "#ED4D37",
"btn-danger-bg": "#ED4D37",
"btn-danger-border": "#ED4D37",
"success-color": "#48BB78",
"text-color": "#4A5568",
"text-color-secondary": "#A0AEC0",
"btn-default-color": "#718096",
"border-color-base": "#CBD5E0",
"input-placeholder-color": "#A0AEC0",
"tooltip-bg": "rgba(26,32,44,.8)",
"heading-color": "#4A5568",
"background-color-base": "#F7F8FA",
},
javascriptEnabled: true,
math: "always",
},
},
}),
pluginSass({
sassLoaderOptions: {
additionalData:
'@import "@/styles/variables.scss";@import "@/styles/mixin.scss";', // @import "@/styles/localIcon.scss";
},
}),
],
});
需要注意的是,在使用 JSX 时,需要为 <script>
标签指定 lang
属性,下面是一个示例:
html
<script lang="jsx">
export default {
render() {
return (
<div class="default">
xxx
</div>
);
},
};
此外,直接在 JS 中编写 JSX 似乎没有什么方法能实现。之前有一个 mixin.js
文件,后来改为 .vue
文件返回 JSX 信息。
5. Node Polyfill
某些包可能使用了 Node 的核心库,例如 node:util
,而 Node 核心库默认情况下无法在浏览器环境下运行。
安装 Node Polyfill 插件,它会按需注入所需的库。
javascript
import { pluginNodePolyfill } from "@rsbuild/plugin-node-polyfill";
export default defineConfig({
plugins: [pluginNodePolyfill()],
});
其他注意事项
SCSS 语法升级
在迁移过程中,需要对 SCSS 写法进行升级,具体可以查看警告信息。例如,这里的 SCSS 写法不被支持:
scss
.key-copy {
position: absolute;
right: 24px;
top: 50%;
// padding-left: 30px;
@include btn-hover;
transform: translateY(-50%);
}
需要调整为:
scss
.key-copy {
@include btn-hover;
& {
position: absolute;
right: 24px;
top: 50%;
// padding-left: 30px;
transform: translateY(-50%);
}
}
对不规范的 import 语法调整
之前使用的写法是:
html
<style lang="scss" scoped>
@import url("~@/layout/style.scss");
</style>
调整为:
css
@import "@/layout/style.scss";
无效的 ::v-deep
调整
只有在 scoped
标签下 ::v-deep
才是有效的语法,需要删除未添加 scoped
的 style 标签。
按需引入 UI 库
在 VueCLI 中可能通过 babel-import 的形式来引入,在 Rsbuild 中调整为:
js
export default defineConfig({
source: {
transformImport: [
{ libraryName: "ant-design-vue", libraryDirectory: "es", style: true },
],
},
});
具体可以参考文档 transform-import
polyfill 按需注入
在使用 ES6 的语法时,默认情况下不会保证兼容性,可以通过 polyfill 来开启。
js
export default {
output: {
polyfill: "usage",
},
};
删除 console、debugger
js
export default {
performance: {
removeConsole: true,
removeMomentLocale: true,
},
};
更多优化点可以查看 performance
编译 node_modules 下的包
可能某些原因需要对 node_modules
文件夹进行编译,可以使用下面方式:
js
import path from "node:path";
export default {
source: {
include: [
// 所有包含 `node_modules/query-string/` 的路径都会被匹配到
/node_modules[\\/]query-string[\\/]/,
],
},
};
详细文档参见:source include
最后
运行到这里基本就迁移成功了之后删除掉不需要的文件:
- vue.config.js
- babel.config.js
迁移下来速度从启动的 30s 到 5s 提升了大概 6 倍,不过更重要的是热重载基本更改后就生效。最后如果有什么错误之处欢迎指出。
下面是一份我最终使用的配置文件
rsbuild.config.ts
js
import { defineConfig, loadEnv } from "@rsbuild/core";
import path from "path";
import { pluginLess } from "@rsbuild/plugin-less";
import { pluginSass } from "@rsbuild/plugin-sass";
import { pluginBabel } from "@rsbuild/plugin-babel";
import { pluginVue2 } from "@rsbuild/plugin-vue2";
import { pluginVue2Jsx } from "@rsbuild/plugin-vue2-jsx";
import { pluginNodePolyfill } from "@rsbuild/plugin-node-polyfill";
const { publicVars } = loadEnv({ prefixes: ["VUE_APP_"] });
export default defineConfig({
output: {
polyfill: "usage",
},
performance: {
chunkSplit: {
strategy: "split-by-experience",
},
removeConsole: true,
removeMomentLocale: true,
},
plugins: [
pluginBabel({
include: /\.(?:jsx|tsx)$/,
}),
pluginVue2(),
pluginVue2Jsx(),
pluginLess({
lessLoaderOptions: {
lessOptions: {
modifyVars: {
"primary-color": "#5384FE",
"error-color": "#ED4D37",
"btn-danger-bg": "#ED4D37",
"btn-danger-border": "#ED4D37",
"success-color": "#48BB78",
"text-color": "#4A5568",
"text-color-secondary": "#A0AEC0",
"btn-default-color": "#718096",
"border-color-base": "#CBD5E0",
"input-placeholder-color": "#A0AEC0",
"tooltip-bg": "rgba(26,32,44,.8)",
"heading-color": "#4A5568",
"background-color-base": "#F7F8FA",
},
javascriptEnabled: true,
math: "always",
},
},
}),
pluginSass({
sassLoaderOptions: {
additionalData:
'@import "@/styles/variables.scss";@import "@/styles/mixin.scss";', // @import "@/styles/localIcon.scss";
},
}),
pluginNodePolyfill(),
],
source: {
entry: {
index: "./src/main.js", // 指定入口文件
},
define: publicVars,
include: [
/node_modules\/transpileDependencies/,
/node_modules\/@antv/,
/node_modules\/antv\/x6/,
/node_modules\/vuex-persist/,
/node_modules\/gm-crypt/,
/node_modules\/sm-crypto/,
],
transformImport: [
{ libraryName: "ant-design-vue", libraryDirectory: "es", style: true },
],
},
html: {
template: "./public/index.html",
},
tools: {
rspack: {
resolve: {
extensions: [".vue", ".js", ".jsx", ".tsx", ".ts", ".json"],
alias: {
"@": path.resolve(__dirname, "src"),
},
},
},
bundlerChain: (chain, { CHAIN_ID }) => {
chain.module.rule(CHAIN_ID.RULE.SVG).oneOfs.clear();
chain.module
.rule(CHAIN_ID.RULE.SVG)
.use("vue-svg-loader")
.loader("vue-svg-loader");
},
},
server: {
port: 8888,
proxy: {
"/api": {
target: process.env.VUE_APP_API_BASE_URL,
ws: false,
changeOrigin: true,
pathRewrite: {
"^/api": "", // 需要rewrite的,
},
},
},
},
});