从 VueCLI 迁移到 Rsbuild

公司有许多项目仍然停留在 Vue 2 上,每次通过 VueCLI 启动都需要等待大约 30 秒(在 M1 芯片上),每次热重载的速度也让人难以忍受,构建速度同样缓慢。虽然可以通过 esbuild 等工具改善,但在某天多次修改需求后,我决定忍无可忍,于是有了这篇文章。

决定迁移的原因主要有两点:

  1. 对 Node 22 支持不友好。由于项目基于 VueCLI 3,Node 版本的诸多变更导致无法启动,必须通过 nvm 切换到 16 版本。
  2. 构建和热更新速度慢。

在决定使用 Rsbuild 之后,我也进行了一番调研,备选方案有四个:

  1. Webpack+SWC 或 Webpack+Esbuild
  2. Vite
  3. Rsbuild
  4. 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的,
        },
      },
    },
  },
});
相关推荐
章若楠圈外男友6 分钟前
HTML <input> accept 属性
java·前端·html
fmc12110416 分钟前
照片信息的读取与分类(1)
java·前端·数据库
雷特IT34 分钟前
jQuery基础——Ajax
前端·ajax·jquery
^草莓牛乳茶^36 分钟前
【word导出带图片】使用docxtemplater导出word,通知书形式的word
前端·vue.js·word
谢尔登39 分钟前
【React】Vite 构建 React
前端·react.js·前端框架
qiao若huan喜41 分钟前
06_React ajax
前端·react.js·ajax
mez_Blog43 分钟前
第一个React程序
前端·javascript·笔记·react.js·前端框架
一直在学习的小白~44 分钟前
在 React 中,Input 失去焦点时获取失去焦点的位置并插入值
前端·javascript·react.js
problc44 分钟前
react lazy加载资源找不到的问题
前端·react.js·前端框架
小学都没毕业的前端攻城狮1 小时前
react、vue 提供的 hook 函数对比
javascript·vue.js·react.js