rspack迁移vue-element-admin实践指南

背景

前端不断在探索打包,构建的天花板,产出了 vite , Rspack, Turbopack 等一系列优秀的打包构建工具

在好奇心的驱使下, 尝试 以 vue-element-admin 这个仓库作为改造目标, 首先向我们的前辈 【花裤衩】致敬! 产出如此优秀的后台管理系统模版

  • 这个仓库代表了大部分的国内中后台系统, 它改造通过,且数据惊人,会在一定程度上驱使大家尝试Rspack
  • 仓库基于 @vue/cli @4.4.4 版本, 背后是 webpack @4.x , 我们亲自尝试一下,从 webpack 迁移到Rspack需要改造哪些内容
  • 该仓库包含了N种基础的业务(echart, 拖动, element-ui),看看我们接入Rspack对业务的侵入性有多大
  • 从实际数据看,Rspack VS webpack 冷启动,HMR, 打包耗时,是否有巨大提升

步骤

所有操作均基于 npm 安装包依赖, 未使用 pnpm

基于webpack启动项目

先说明一下, 老的原始代码,是基于 node: 14.21.3 运行的, 高版本的Node 可能有些 npm包不兼容,无法启动项目

  1. 下载仓库代码
bash 复制代码
git clone https://github.com/PanJiaChen/vue-element-admin.git
  1. 移除掉富文本相关的内容, 这个在 node 14.21.3 下面,否则无法启动项目

package.json

json 复制代码
"tui-editor": "1.3.3",

移除这个包

有几个字段也移除一下, 虽然没啥影响

json 复制代码
 "engines": {
    "node": ">=8.9",
    "npm": ">= 3.0.0"
  },
 "browserslist": [
    "> 1%",
    "last 2 versions"
  ],

移除业务代码入口,或者直接注释掉

js 复制代码
src/components/MarkdownEditor/default-options.js

src/components/MarkdownEditor/index.vue

// 移除路由入口
src/router/modules/components.js

启动项目, 查看耗时

71.495 s

打包时间也挺久,没有记录,大家可以clone 我 gitee 仓库的 master分支代码,自行尝试

下面上我们的 主菜

基于rspack启动项目

原始的package.json里面的 devDependencies 有很多依赖,全部干掉,重新从0开始配置

新建一个分支, 我是把 github 原仓库的代码, clone 到自己的 gitee 仓库,解决了 报错

现在我们基于 gitee 仓库的 master分支开始改造步骤

shell 复制代码
git checkout -b rspack

安装rspack 依赖

node 可以使用 20.10.0

shell 复制代码
npm i @rspack/cli @rspack/core -D

安装 vue-laoder

vue2 只能安装到如下版本

shell 复制代码
npm i vue-loader@15.11.1 vue-style-loader@4.1.3 -D

原始仓库项目使用的是 vue2.6版本, 这里我们顺便升级为 2.7.16

shell 复制代码
npm i vue@2.7.16 -S

对了, vue 在 2.7 以后,就不需要 vue-template-compiler 这个包了,大家都知道吧。 😊😊😊 快去检查一下你们公司的项目。

配置sass/less

是的,这些包,在使用当前文档,均为最新!最新!最新! 版本

css 复制代码
npm i less less-loader sass sass-loader -D

到此,我们已经安装了基础的所需要的依赖

配置静态资源(图片,svg, 字体文件)

webpack 使用 module.rules , rspack也是一样的

js 复制代码
module.exports = {
    module: {
        rules: [
          {
            test: /\.(png|svg|jpg|jpeg|gif|webp)$/i,
            type: 'asset/resource',
          },
          {
            test: /\.(woff|woff2|eot|ttf|otf)$/i,
            type: 'asset',
          }
        ]
    }
}

如果是原始的基于 webpack 而非 @vue/cli 脚手架的配置, 这块可以copy过来, 静态资源也不需要指定 loader

rspack 内置帮我们处理好了, 指定 type 字段即可, 这里跟最新的 webpack@5 的用法一致

路由中省略.vue的问题,无法直接识别vue文件

见下面的 extensions

处理 vue文件

不管是基于 webpack 还是 rspack, 还是 vite, 都不能直接识别 .vue文件, 都是通过 插件等将.vue转成普通的js语法

js 复制代码
const path = require('path')
const { VueLoaderPlugin } = require("vue-loader");
const { defineConfig } = require("@rspack/cli");

module.exports = defineConfig({
    resolve: {
        alias: {
            "@": path.resolve(__dirname, 'src')
        },

        extensions: [".js", ".json", ".wasm", '.vue', '.jsx', '.tsx']
    },

    module: {
        rules: [
            {
                test: /\.vue$/,
                use: [
                    {
                        loader: "vue-loader",
                        options: {
                          experimentalInlineMatchResource: true
                        }
                    }
                ]
            },
        ]
    }
})

vue2 支持jsx

jsx语法在 vue的生态真是个麻烦的东西,确实,如果手摸手 配置过 rollup 打包 vue2 且支持jsx, webpack 处理vue2的jsx ,就能体会这句话

loader 可能不更新了,然后生态断层了等等问题 😭😭😭

sql 复制代码
npm i @vue/babel-preset-jsx @vue/babel-helper-vue-jsx-merge-props -D

调整 rspack.config.js

js 复制代码
const path = require('path')
const { defineConfig } = require("@rspack/cli");

module.exports = defineConfig({
  resolve: {
    alias: {
      "@": path.resolve(__dirname, 'src')
    },

    extensions: [".js", ".json", ".wasm", '.vue', '.jsx', '.tsx']
  },
  module: {
    rules: [
      {
        test: /\.[jt]sx$/,
        use: {
          loader: "babel-loader",
          options: {
            presets: [
              ["@vue/babel-preset-jsx", { compositionAPI: true }]
            ]
          }
        }
      },
    ]
  }

})

这里我们添加了 babel-loader, @vue/babel-preset-jsx , 还有上面的 文件后缀扩展的支持

以前在vue里面直接写 render jsx 语法,是不需要 写lang="jsx", 这里 稍微改一下

process.env 改造

vite 还有 @vue/cli 都可以读取 项目根目录里面的 .env 文件, 这里依旧实现

shell 复制代码
npm i dotenv dotenv-expand -D

dotenv-expand 是让 env 文件中支持变量的包

js 复制代码
const { defineConfig } = require("@rspack/cli");

const env = process.env.NODE_ENV || "development";
const dotEnvFiles =
  env === "development" ? [".env.development"] : [".env.production"];

dotEnvFiles.forEach((doteEnvFile) => {
  let content = dotenv.config({ path: doteEnvFile })
  console.log('读取到的内容');

  console.log(content);
  expand(content)
});
const VUE_APP = /^VUE_APP_/i;

const filterEnv = {};
const define = Object.keys(process.env)
  .filter((key) => VUE_APP.test(key))
  .reduce((env, key) => {
    filterEnv[key] = process.env[key];
    env[`process.env.${key}`] = JSON.stringify(process.env[key]);
    return env;
  }, {});

module.exports = defineConfig({
  plugins: [
    new rspack.DefinePlugin({
      "process.env": JSON.stringify(filterEnv)
    })
  ]
})

在页面等使用到 process.env.xxx的地方测试一下,变量是否注入

我们在 .env.development 文件中,看到有这个 VUE_APP_BASE_API

测试接口, 已经注入到 axios的 baseURL 中

mockjs改造

我们前面删除了所有的 dev 依赖, 这里重新安装

shell 复制代码
npm i mockjs -D

在 vue.config.js 中我们这样去配置 mock 服务, 我们在 rspack 需要类似配置

js 复制代码
const { defineConfig } = require("@rspack/cli");

module.exports = defineConfig({
	devServer: {
    historyApiFallback: true,

    setupMiddlewares (middlewares, devServer) {
      let server = require('./mock/mock-server.js')

      server(devServer.app)

      return middlewares
    }
  },
})
bash 复制代码
http://localhost:8080/dev-api/vue-element-admin/user/info?token=admin-token

vscode终端,有日志输出

解决index.html中变量问题

html 复制代码
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="renderer" content="webkit">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title><%= webpackConfig.name %></title>
  </head>
  <body>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

里面有变量: BASE_URL, 目前rspack 还不认识, 但是我们一般期望在运行后, 替换为 /favicon.cion 这种, 或者类似于 publicPath 等

js 复制代码
module.exports = defineConfig({
	plugins: [
    new rspack.HtmlRspackPlugin({
      template: "./public/index.html",
      templateParameters: {
        BASE_URL: '/'
      }
    }),
  ]
})

重启项目,终端就不会报错, 目前图标正常展示

解决element-ui样式编译问题

原项目中,直接引入的是 element-ui的 scss 文件,我们现在不直接使用这个文件, 直接使用编译好的css文件

因为我们现在下载的 sass 属于比较新的版本, element-ui用的以前的版本node-sass 去编译的 element-ui 里面的sass文件

js 复制代码
// element-ui@2.15.14
"node-sass": "^4.11.0",

语法存在较大差异, sass语法的变化在 sass官网有说明

main.js

js 复制代码
import Element from 'element-ui'
// 之前在src/styles/element-variables.scss 里面引入,去掉,改为这里引入
import 'element-ui/lib/theme-chalk/index.css';

sass变量在vue中使用问题

sass变量一般是不能直接在 vue中直接导入使用的,现在改一下实现

侧边栏 组件 有使用 sass 里面的一些变量

src/layout/components/Sidebar/index.vue

删除掉导入, 都是非标准的一些用法, 我们现在改为 css 原生支持的 变量实现,效果是一样的

上面 template 中的部分不用改任何代码, 修改 computed 计算属性

js 复制代码
<script>
  export default {
    computed: {
      variables() {
        // 去掉,只要保证我们导出一个上面模版可以使用的对象即可
        // return variables
        return {
          menuBg: 'var(--menuBg)',
          menuText: 'var(--menuText)',
          menuActiveText: 'var(--menuActiveText)'
        }
      },
    }
  }
</script>

现在我们稍微修改一下 scss文件

styles/variables.scss

scss 复制代码
:root {
  --menuText: #{$menuText};
  --menuActiveText: #{$menuActiveText};
  --subMenuActiveText: #{$subMenuActiveText};
  --menuBg: #{$menuBg};

  --menuHover: #{$menuHover};
  --subMenuBg: #{$subMenuBg};
  --subMenuHover: #{$subMenuHover};
  --sideBarWidth: #{$sideBarWidth};
}

这里变量名都没有改,是避免原始代码中 template 里面也要改字段,比较麻烦,保持代码前后统一问题

vue SFC 单文件中的sass语法改造问题

src/components/MDinput/index.vue

js 复制代码
<style lang="scss" scoped>
  // 需要引入这个
@use "sass:math";

$bottomPadding: $spacer - $apixel * 10;
$leftPadding:  math.div($spacer,2);

.material-input {
  // 原来: padding: $spacer $spacer $spacer - $apixel * 10 $spacer / 2;
  padding: $spacer $spacer  $bottomPadding  $leftPadding;
}
</style>

主要就是 sass除法的改造, 这在 sass 废弃语法中有提到

解决svg图标不展示问题

这个主要是svg-sprite-loader 可能跟 webpack 5.x 的一个适配问题导致的, 在 rspack的 issue中我也有提到 Issue #5450 · web-infra-dev/rspack, 调试了一下,没有解决。

我们从代码里面看,本质是svg 加载的问题

目前官方并没有说明svg 属于一等公民,暂时没有很好的处理方式,简单粗暴的,把代码copy到 index.html中

或者直接把svg 作为普通的HTML 放在 vue组件中

vue2 官方文档有说明 svg在 vue中如何使用,我们项目如果迁移到 rspack , 这个也不是什么大问题。我在rspack改造完本项目以后,也试一下, 就把svg当做普通的HTML 拷贝到某个vue文件中,可以加载svg

echart 报错

javascript 复制代码
ReferenceError: exports is not defined

升级echart 到 V5版本,修改vue中引用的方式

js 复制代码
"echarts": "4.2.1", 升级到 "echarts": "^5.4.3",
js 复制代码
import * as echarts from 'echarts';

别的不需要改

element-ui 表格 空白问题

css 复制代码
npm i element-ui@2.9.0 -S

降低element-ui 版本

有说跟 core-js 版本太高有关系。

参考链接

打包指定输出目录

js, css ,图片做分类

js 复制代码
module.exports = defineConfig({
  output: {
    clean: true,
    filename: 'js/[name].js',
    cssFilename: 'css/[name].css',
    assetModuleFilename: 'assets/[hash][ext]'
  },
})

接入Rsdoctor

截止到 2024-01-26 这个插件在 rspack中会报错,相信官方团队很快会解决 Bug #158
介绍: Rspack 和 Webpack 构建分析器

Rsdoctor 地址

shell 复制代码
npm add @rsdoctor/rspack-plugin -D

安装 cross-env

shell 复制代码
npm i cross-env -D

在 package.json中增加一个 脚本

json 复制代码
"scripts": {
    "doctor": "cross-env RSDOCTOR=true npm run build"
},
shell 复制代码
npm run doctor
js 复制代码
// 分析插件
const { RsdoctorRspackPlugin } = require('@rsdoctor/rspack-plugin');

let plugins = [
  new VueLoaderPlugin(),
  new rspack.HtmlRspackPlugin({
    template: "./public/index.html",
    templateParameters: {
      BASE_URL: '/'
    }
  }),

  new rspack.DefinePlugin({
    "process.env": JSON.stringify(filterEnv)
  }),

]

// 仅在 RSDOCTOR 为 true 时注册插件,因为插件会增加构建耗时
if (process.env.RSDOCTOR) {
  plugins.push(
    new RsdoctorRspackPlugin({
      // 插件选项
    })
  )
}

部分小毛病

  • 目前项目中有的 .vue 文件的 热更新存在问题,改动了内容以后,热更新失效了,需要重启项目才可以,官方已经在跟进 vue2项目,热更新失效 · Issue #5445 · web-infra-dev/rspack (github.com)
  • rsdoctor目前有一点点兼容问题, 前面也说到了,相信不久就可以解决,问题不大。
  • svg 这个loader 生态接入问题,如果项目没有非要跟webpack 项目一样的效果,其实无影响

这些其实相比 rspack带来的变化,可以忽略

还有 rspack 跟 webpack其实是一脉的, 项目越大, 路由越多, vite生态 + vue 可能在路由跳转上并没有那么丝滑,这个问题目前vite也解决不了, rspack 因为打包了资源,资源也都下载到客户端了,所以,不会存在切换路由的卡顿感, 处理机制不一样

完整配置 rspack.config.js

或者去仓库 vue-element-admin rspack分支 copy 查看, 因为rspack 升级后,我可能不定期跟着更新,测试rspack的变化

rspack.config.js

js 复制代码
const path = require('path')
const rspack = require("@rspack/core");
const { VueLoaderPlugin } = require("vue-loader");
const { defineConfig } = require("@rspack/cli");

const dotenv = require('dotenv')
const { expand } = require('dotenv-expand')
// 分析插件
const { RsdoctorRspackPlugin } = require('@rsdoctor/rspack-plugin');


const env = process.env.NODE_ENV || "development";
const dotEnvFiles =
  env === "development" ? [".env.development"] : [".env.production"];

dotEnvFiles.forEach((doteEnvFile) => {
  let content = dotenv.config({ path: doteEnvFile })
  // console.log('读取到的内容');

  // console.log(content);
  expand(content)
});
const VUE_APP = /^VUE_APP_/i;

const filterEnv = {};
const define = Object.keys(process.env)
  .filter((key) => VUE_APP.test(key))
  .reduce((env, key) => {
    filterEnv[key] = process.env[key];
    env[`process.env.${key}`] = JSON.stringify(process.env[key]);
    return env;
  }, {});

// console.log('拿到的内容');
// console.log(define);

let plugins = [
  new VueLoaderPlugin(),
  new rspack.HtmlRspackPlugin({
    template: "./public/index.html",
    templateParameters: {
      BASE_URL: '/'
    }
  }),

  new rspack.DefinePlugin({
    "process.env": JSON.stringify(filterEnv)
  }),

]

// 仅在 RSDOCTOR 为 true 时注册插件,因为插件会增加构建耗时
if (process.env.RSDOCTOR) {
  plugins.push(
    new RsdoctorRspackPlugin({
      // 插件选项
    })
  )
}

/**
 * @type {import('@rspack/cli').Configuration}
 */
module.exports = defineConfig({
  context: __dirname,
  entry: {
    main: "./src/main.js"
  },
  output: {
    clean: true,
    filename: 'js/[name].js',
    cssFilename: 'css/[name].css',
    assetModuleFilename: 'assets/[hash][ext]'
  },

  devServer: {
    historyApiFallback: true,

    setupMiddlewares (middlewares, devServer) {
      let server = require('./mock/mock-server.js')

      server(devServer.app)

      return middlewares
    }
  },
  plugins,

  resolve: {
    alias: {
      "@": path.resolve(__dirname, 'src')
    },

    extensions: [".js", ".json", ".wasm", '.vue', '.jsx', '.tsx']
  },
  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 }]
            ]
          }
        },

      },

      {
        test: /\.less$/,
        loader: "less-loader",
        type: "css",

      },
      {
        test: /\.scss$/,
        loader: "sass-loader",
        type: "css",
        exclude: /node_modules/,

      },
      {
        test: /\.(png|svg|jpg|jpeg|gif|webp)$/i,
        type: 'asset/resource',
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        type: 'asset',
      }
    ]
  }
})

改动的步骤细节

改动的每一个细节,我都单独的提交一个 commit, 希望如果你有改造业务系统的计划,可以有帮助和参考

仓库地址: vue-element-admin

最终

好了, 到此,我们改造了这个仓库,里面所有涉及的坑全部排查完毕, 也给出了对应的解决方案, 我们来看一看最终的成绩吧 😊😊😊

运行效果截图

构建耗时

对比webpack, 成绩相当不错(撒花 😊), 构建时间从 71.495s 到 6s 。

Rspack 打包时间在 3.87s

结语

总之:牛逼~

希望rspack 继续发力,兼容前端大部分的应用场景


文章编写不易,希望给一个小小的 点赞,转发, 谢谢

再贴一次仓库地址: gitee.com/luoriwushen...

相关推荐
s912360101几秒前
【Rust】m2 mac 编译linux 、aarch、win 程序
rust
天天进步201517 分钟前
CSS Grid与Flexbox:2025年响应式布局终极指南
前端·css
Source.Liu28 分钟前
【ISO8601库】日期时间解析器测试套件详解(tests.rs)
rust·time·iso8601
Boop_wu1 小时前
[Java EE] 计算机基础
java·服务器·前端
Novlan11 小时前
TDesign UniApp 组件库来了
前端
用户47949283569151 小时前
React DevTools 组件名乱码?揭秘从开发到生产的代码变形记
前端·react.js
alwaysrun1 小时前
Rust中数组简介
rust·数组·array·切片
百锦再1 小时前
第12章 测试编写
android·java·开发语言·python·rust·go·erlang
顾安r2 小时前
11.8 脚本网页 打砖块max
服务器·前端·html·css3
倚栏听风雨2 小时前
typescript 方法前面加* 是什么意思
前端