老项目改造 vue-cli 2.6 升级 rsbuild 打包10分到3分,开发启动运行时间 从70秒--->2秒

先上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-eslintcore-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.templateoutput.assetPrefixrsbuild.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-loaderpostcss-loaderless-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,同时调整 pluginBabelinclude 路径(使用 [\/] 兼容 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 内置模块(如 pathcryptonet 等),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 代码中的深度选择器写法。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容