先上vue-cli 升级示例项目https://github.com/1438343098/rsbuild-less-module-bug
官方迁移地址 https://rsbuild.rs/zh/guide/migration/vue-cli
| 类型 | 热更新 | 启动时间 | 新页面打开时间 | 打包时长 |
|---|---|---|---|---|
| vue-cli | 5-10秒,浏览器刷新失效 | 60秒 | 1秒 | 7分钟 |
| rs-build | 第一次加载时间长一点后面都是秒级 | 3秒 | 第一次要加载根据页面复杂度 后续都是秒级 | 3分钟 |
心路历程
一开始是考虑的vite,然后迁移到一半发现很多地方需要改项目里面的代码,这个在多人开发的情况下是不允许的。怎么可能随随便便改别人的代码,所以放弃了。改造vite参考这个库进行的改造 https://github.com/originjs/webpack-to-vite/blob/main/README-zh.md
然后后面想的试试开发用rspack,打包还是用vue,改造到一半发现,只保留开发模式有很多问题,各个包的兼容性和对齐修改,最后发现不太行。开发模式情况下对齐又要修改webpack版本,这样子还是会影响打包。而且这个时候启动项目也是非常久也是放弃了,而且有一些包只有rsbuild才有,所以最后还是重新准备用rsbuild了。
rsbuild和rspack本身兼容大部分的webpack配置所以只需要修改部分代码就可以进行移植。
迁移过程
我们遵循了 Rsbuild 的官方迁移指南,并结合项目特点逐步实施:
移除旧依赖:首先卸载 Vue CLI 相关的依赖(如
@vue/cli-service、@vue/cli-plugin-babel、@vue/cli-plugin-eslint、core-js等),避免冲突rsbuild.rs。安装 Rsbuild 依赖:安装
@rsbuild/core及相应插件。Vue2 项目需要用@rsbuild/plugin-vue2(Vue3 用@rsbuild/plugin-vue),我们还额外加入了常用插件,如@rsbuild/plugin-sass(支持 Sass)、@rsbuild/plugin-pug(支持 Pug)、@rsbuild/plugin-node-polyfill(Node 内置库兼容)、@rsbuild/plugin-basic-ssl(开发环境启用 HTTPS)等-
更新 npm 脚本:修改
package.json中的脚本,将serve/build命令替换为 Rsbuild 的 CLI。例如:"scripts": { "serve": "rsbuild dev", "build": "rsbuild build", "preview": "rsbuild preview" }-
创建配置文件:在项目根目录创建
rsbuild.config.ts配置文件。基础内容包括引入所需插件和定义入口,比如:
import { defineConfig, loadEnv } from '@rsbuild/core'; import { pluginVue2 } from '@rsbuild/plugin-vue2'; import { pluginSass } from '@rsbuild/plugin-sass'; import { pluginPug } from '@rsbuild/plugin-pug'; import { pluginNodePolyfill } from '@rsbuild/plugin-node-polyfill'; import { pluginBasicSsl } from '@rsbuild/plugin-basic-ssl'; // 读取 VUE_APP_* 环境变量 const { publicVars } = loadEnv({ prefixes: ['VUE_APP_'] }); export default defineConfig({ plugins: [ pluginNodePolyfill(), pluginVue2(), pluginSass(), pluginPug(), pluginBasicSsl() ], source: { entry: { index: './src/main.js' }, define: publicVars // 注入环境变量 }, html: { template: './public/index.html' } // 其它配置... });上述配置示例参考官方文档rsbuild.rsrsbuild.rs。其中,通过
loadEnv读取VUE_APP_前缀的环境变量,并在source.define中注入,以保持与 Vue CLI 的行为兼容rsbuild.rs。如果原项目有多页(pages)或自定义publicPath,也需要在 Rsbuild 中相应设置(如html.template、output.assetPrefix)rsbuild.rs。 -
创建配置文件:在项目根目录创建
调整别名与资源:Rsbuild 支持大部分 Webpack 别名语法,我们在配置中将
@指向src目录,并注意将vue$指向运行时版本(如vue/dist/vue.runtime.esm.js),以保证兼容性。此外,如果项目使用 CDN 或外部脚本,还可以通过配置output.assetPrefix或在 HTML 模板中使用类似 Vue CLI 的占位变量{% assetPrefix %}来兼容旧代码逻辑rsbuild.rs。
遇到的问题与解决方案
在迁移过程中,我们遇到了几个常见“坑”,并通过手动调整解决了它们:
-
CSS 模块(
<style module>)不生效:这个是我Leader解决并提供的解决方案 原项目大量使用了<style module lang="less">,直接使用官方的@rsbuild/plugin-less遇到兼容问题。我们选择移除pluginLess(),手动添加 Less 的 Webpack 规则。在tools.rspack钩子中,用css-loader配合modules: true选项,以及vue-style-loader、postcss-loader、less-loader构建完整的 Loader 队列,并通过globalVars统一注入全局 Less 变量。示例配置:rspack(config, { addRules }) { ... // 过滤掉所有 less 和 css 相关规则,由自定义的 less 和 css 规则接管 config.module.rules = (config.module.rules || []).filter(rule => { if (!rule.test) return true const testStr = rule.test.toString() // 过滤掉 less 和 css 规则 if (testStr.includes('less')) return false if (testStr.includes('css') || testStr.includes('pcss') || testStr.includes('postcss')) return false return true }) // ------------------- // 加新的 less 规则(保证 globalVars 生效) // 匹配 .less 和 .vue.less(Vue SFC 中的 less 样式) // ------------------- config.module.rules.unshift({ test: /\.less$/, type: 'javascript/auto', use: [ 'vue-style-loader', { loader: 'css-loader', options: { // 使用 auto 函数自动检测是否需要启用 CSS Modules modules: { auto: (resourcePath, resourceQuery) => { // 检查 resourceQuery 中是否包含 module const hasModule = resourceQuery && resourceQuery.includes('module') // if (hasModule) { // console.log('🔍 CSS Modules enabled for:', resourcePath, resourceQuery) // } return hasModule }, mode: 'local', localIdentName: '[local]--[hash:base64:5]', exportLocalsConvention: 'asIs', namedExport: false }, importLoaders: 2 // esModule: false } }, 'postcss-loader', { loader: 'less-loader', options: { lessOptions: { javascriptEnabled: true, globalVars: lessVars, math: 'always' // 恢复 Less 3.x 的数学运算行为 }, additionalData: sharedLessImports } } ] }) ...同时,官方建议关闭
experiments.css(默认 true 会导致 CSS Loader 重复执行的问题),将其设为false以兼容 Vue 2 项目。 -
Vue 2 JSX 兼容:Vue2 的 JSX 语法需要特殊支持。原先我们安装了
@rsbuild/plugin-vue2-jsx,但在 Windows 路径下经常无法匹配相关包。最终使用 Babel 预设@vue/babel-preset-jsx来处理 JSX,同时调整pluginBabel的include路径(使用[\/]兼容 Windows),配置示例:pluginBabel({ include: [/src/, /@kuaizi[\/]saas-components/], babelLoaderOptions: { presets: [ ['@babel/preset-env', { targets: 'defaults' }], ['@vue/babel-preset-jsx', { compositionAPI: false }] ] } })这样确保 Vue 2 和 JSX 代码都能被正确编译。
-
HTML 模板与全局变量:Vue CLI 项目通常在
public/index.html中使用htmlWebpackPlugin变量或BASE_URL等。为了不修改太多 HTML,我们在 Rsbuild 配置的html.templateParameters中注入一个兼容对象(模拟原htmlWebpackPlugin.options),并统一前缀,如下:html: { template: './public/index.html', templateParameters: (compilation, assets, assetTags) => ({ htmlWebpackPlugin: { options: { title: '项目标题', assetPrefix: assets.publicPath } }, ... // 其它需要注入的变量 }) }这样 HTML 中原来的
<%= htmlWebpackPlugin.options.title %>等用法无需修改即可继续使用。 process.env兼容:Vue CLI 会注入以VUE_APP_前缀的环境变量到客户端代码,Rsbuild 默认只注入PUBLIC_前缀。我们通过loadEnv(如上所述)将所有process.env.*统一注入。同时也可使用define将其他全局常量注入环境,如需兼容旧业务代码的process.env.NODE_ENV等,只需在source.define中添加对应键值。 需要通过配置处理,否则页面无法正常获取
...
source: {
entry: {
index: './src/main.js'
},
include: [/@kuaizi[\\/]saas-components/, /kz-saas-components/],
define: {
//这里进行批量处理
...(function () {
return Object.fromEntries(
Object.entries(process.env).map(([key, value]) => [
`process.env.${key}`,
JSON.stringify(value)
])
)
})()
}
},
...
Node 模块兼容:如果业务代码或依赖中使用了 Node 内置模块(如
path、crypto、net等),Rsbuild 自身不包含 polyfills。我们使用@rsbuild/plugin-node-polyfill()来自动为这些模块提供浏览器端的替代实现,否则会抛错。例如,处理 Net 模块时,只需引入该插件即可。-
.deep 深度选择器:Vue 2 的老写法
/deep/或:deep()可能不被 PostCSS 默认识别。我们创建了自定义的 PostCSS 插件,将:deep(...)、/deep/等统一转换为::v-deep语法,以兼容 Vue-loader 解析。配置示例:// postcss.config.js module.exports = { plugins: [ require('autoprefixer'), { postcssPlugin: 'postcss-vue-deep-selector', Rule(rule) { if (!rule.selector) return; let sel = rule.selector.replace(/:deep(([^)]+))/g, '::v-deep $1').replace(//deep//g, '::v-deep'); if (sel !== rule.selector) rule.selector = sel; } } ] };如此可无缝支持 Vue 2 代码中的深度选择器写法。