Vue2 项目从 Webpack3 迁移到 Rspack 的实践与性能优化

771 阅读4分钟

前言

rspack作为字节撼动前端构建界的一大利器,业界缺少实际项目的支撑经验。本着优化项目第一,锻炼技术第二的目的,我就这样出发了。

rspack 由rust开发,如果说vite将速度从自行车推到了摩托车时代,那么rust将前端构建从摩托车到了飞行器体验的时代,无论是vue旧版本还是react新版本,都将由rspack至快的速度来驱动。

下面就我几个项目的优化和升级的经验分享一下,本着个人经验不具备普遍的前提原则下,还望大家轻拍。

1. 项目背景

  • 项目类型:xx后台管理系统

  • 技术栈:Vue 2.5 + Element UI

  • 项目规模:

    • 代码量:约 23w+ 行

    • 页面数:200+ 个业务页面

    • 基于 vue-element-admin(Webpack3 分支)模板开发

  • 设备:MacBook Pro M1 Pro 32GB

1.2 现存问题

  1. 开发效率低下

    • 首次加载时间:41.1 秒

    • 打包时间:55.48 秒

    • 热更新时间:4.01 秒

    • 每次操作都需要等待将近 1 分钟

  2. 已尝试的优化方案

    • 添加缓存优化

    • 优化 loader 配置

    • 调整 Webpack 配置

但效果均不明显,仍然存在性能瓶颈。

2. 方案调研

2.1 可选方案对比

方案优势劣势迁移成本
Webpack 5生态成熟,稳定性高,广泛的社区支持配置复杂,构建速度较慢,性能瓶颈
Vite开发体验极佳,启动和热更新快,配置简洁生产构建慢,插件生态不完善
Rspack性能优越,兼容 Webpack 生态,资源消耗少相对较新,部分功能待完善

2.2 选择 Rspack 的原因

  1. 性能优势

    • 与 Webpack 相比有显著提升

    • 接近 Vite 的开发体验

    • 基于Rust开发

  2. 迁移成本低

    • 兼容大部分 Webpack 配置

    • 支持渐进式迁移

  3. 生态兼容

    • 复用现有 Webpack 生态

    • 支持主流 loader 和 plugin

3. 迁移实施

3.1 环境准备

  1. Node 版本升级
{
  "volta": {
    "node": "20.10.0",  // 从 14.16.0 升级
    "yarn": "1.22.5"
  }
}
  1. 安装核心依赖
npm i @rspack/cli @rspack/core -D
npm i vue-loader@15.11.1 vue-style-loader@4.1.3 babel-loader @vue/babel-preset-jsx @vue/babel-helper-vue-jsx-merge-props -D

3.2 核心配置改造

  1. 基础配置
const path = require('path');
const { defineConfig } = require('@rspack/cli');

module.exports = defineConfig({
  entry: {
    main: "./src/main.js"
  },
  devtool: process.env.NODE_ENV === 'development' ? 'eval' : false,
  resolve: {
    alias: {
      "@": path.resolve(__dirname, 'src'),
      vue$: 'vue/dist/vue.esm.js',
      '@remote-common': path.resolve('remote-common')
    },
    extensions: [".js", ".json", ".wasm", '.vue', '.jsx', '.tsx'],
    modules: [
      path.resolve(__dirname, 'src'),
      'node_modules'
    ],
  }
})
  1. Vue 相关配置
module.exports = defineConfig({
  plugins: [
    new VueLoaderPlugin(),
  ],
  module: {
    rules: [
      {
        test: /\.vue$/,
        use: [
          {
            loader: "vue-loader",
            options: {
              experimentalInlineMatchResource: true
            }
          }
        ]
      },
      {
        test: /\.[jt]sx$/,
        use: {
          loader: "babel-loader",
          options: {
            presets: [
              ["@vue/babel-preset-jsx", { compositionAPI: true }]
            ]
          }
        },
      }
    ]
  }
})
  1. 样式处理
module.exports = defineConfig({
  module: {
    rules: [
      {
        test: /\.less$/,
        loader: "less-loader",
        type: "css",
      },
      {
        test: /\.scss$/,
        loader: "sass-loader",
        type: "css",
        exclude: /node_modules/,
      }
    ]
  }
})
  1. 静态资源处理
module.exports = defineConfig({
  module: {
    rules: [
      {
        test: /\.svg$/,
        include: [path.join(__dirname, 'src/icons/svg')],
        use: [
          {
            loader: 'svg-sprite-loader',
            options: {
              symbolId: 'icon-[name]'
            }
          }
        ]
      },
      {
        test: /\.(png|jpg|jpeg|gif|webp)$/i,
        type: 'asset',
        parser: {
          dataUrlCondition: {
            maxSize: 4 * 1024,
          },
        },
        generator: {
          filename: 'images/[hash][ext]',
        },
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        type: 'asset',
        parser: {
          dataUrlCondition: {
            maxSize: 4 * 1024,
          },
        },
        generator: {
          filename: 'fonts/[hash][ext]'
        }
      }
    ]
  }
})

3.3 环境变量配置

const dotenv = require('dotenv');
const DotenvWebpack = require('dotenv-webpack');

module.exports = defineConfig({
  plugins: [
    new DotenvWebpack({
      path: `.env.${process.env.NODE_ENV}`,
      safe: false,
      allowEmptyValues: true,
      systemvars: true,
      silent: false,
      defaults: false,
    })
  ],
  optimization: {
    minimize: !isDev,
    nodeEnv: false, // 这很重要,可以自定义 .env.[mode]
    moduleIds: 'deterministic',
    splitChunks: !isDev ? {
      chunks: 'all',
      minSize: 200000,
      maxSize: 0,
      cacheGroups: {
        styles: {
          name: 'styles',
          type: 'css/mini-extract',
          chunks: 'all',
          enforce: true,
        },
      },
    } : false,
  }
})

4. 性能对比

4.1 详细指标对比

指标WebpackRspack提升比例
首次加载41.1s13.43s-67.3%
二次加载40.11s12.11s-69.8%
构建时间55.48s7.69s-86.1%
热更新4.01s1.45s-63.8%

4.2 构建产物对比

  • 总体积减少约 15%

  • chunk 分割更合理

  • GZIP 压缩后体积进一步优化

5. 遇到的问题与解决方案

  1. rspack 启动依赖报错
[rspack-cli] Error: Cannot find module 'webpack/lib/rules/BasicEffectRulePlugin'

解决方案:手动再添加webpack5 相关issues

企业微信截图_d090061c-cfaf-46a1-bc7f-dead1894e6f5.png

  1. sass-loader 警告
module.exports = defineConfig({
  ignoreWarnings: [
    (warning) => warning.message.includes('sass-loader')
  ],
})

5.2 静态资源处理

  1. 图片加载问题
  • 使用 asset 模块替代 url-loader

  • 调整资源大小阈值

  1. 字体文件处理
  • 统一使用 asset 类型处理

  • 配置合适的输出路径

6. 经验总结

6.1 迁移建议

  1. 循序渐进

    • 先进行基础配置迁移

    • 逐步替换 loader 和 plugin

    • 保持功能的完整性验证

  2. 版本选择

    • 选择稳定版本

    • 关注官方更新动态

    • 及时跟进 bug 修复

6.2 注意事项

  1. 兼容性处理

    • 检查第三方依赖兼容性

    • 保留必要的 polyfill

    • 注意浏览器支持范围

  2. 性能优化

    • 合理配置 splitChunks

    • 启用 GZIP 压缩

    • 使用 RsDoctor 进行性能分析

6.3 后续计划

  1. 持续优化

    • 进一步优化构建配置

    • 探索更多性能提升空间

    • 跟进 Rspack 新特性

  2. 监控完善

    • 添加构建性能监控

    • 完善错误收集机制

    • 建立性能基准数据

总结

从实践效果来看,Rspack 确实为项目带来了显著的性能提升,特别是在开发体验和构建速度方面。虽然 Rspack 仍在快速发展中,但其兼容 Webpack 生态的特性使得迁移成本相对可控。随着 Rspack 的不断完善,相信未来会带来更多的性能优化和功能改进。

这次迁移不仅提升了团队的开发效率,也为后续的项目优化提供了新的思路和可能性。建议有类似需求的团队可以考虑尝试 Rspack,但要注意评估项目的具体情况和可能遇到的风险。