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

package.json

"tui-editor": "1.3.3",

移除这个包

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


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

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

src/components/MarkdownEditor/default-options.js

src/components/MarkdownEditor/index.vue

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

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

启动项目, 查看耗时

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

71.495 s

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

下面上我们的 主菜

基于rspack启动项目

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

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

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

git checkout -b rspack

安装rspack 依赖

node 可以使用 20.10.0

npm i @rspack/cli @rspack/core -D

安装 vue-laoder

vue2 只能安装到如下版本

npm i vue-loader@15.11.1 vue-style-loader@4.1.3 -D

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

npm i vue@2.7.16 -S

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

配置sass/less

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

npm i less less-loader sass sass-loader -D

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

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

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

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语法

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 可能不更新了,然后生态断层了等等问题 😭😭😭

npm i @vue/babel-preset-jsx @vue/babel-helper-vue-jsx-merge-props -D

调整 rspack.config.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”, 这里 稍微改一下

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

process.env 改造

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

npm i dotenv dotenv-expand -D

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

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 中

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

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

mockjs改造

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

npm i mockjs -D

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

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

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
    }
  },
})
http://localhost:8080/dev-api/vue-element-admin/user/info?token=admin-token

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

vscode终端,有日志输出

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

解决index.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 等

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

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

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

解决element-ui样式编译问题

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

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

// element-ui@2.15.14
"node-sass": "^4.11.0",

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

main.js

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

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

sass变量在vue中使用问题

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

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

src/layout/components/Sidebar/index.vue

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

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

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

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

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

styles/variables.scss


:root {
  --menuText: #{$menuText};
  --menuActiveText: #{$menuActiveText};
  --subMenuActiveText: #{$subMenuActiveText};
  --menuBg: #{$menuBg};

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

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

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

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

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

src/components/MDinput/index.vue

<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 废弃语法中有提到

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

解决svg图标不展示问题

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

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

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

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

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

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

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

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

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

echart 报错

ReferenceError: exports is not defined

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

"echarts": "4.2.1", 升级到 "echarts": "^5.4.3",
import * as echarts from 'echarts';

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

别的不需要改

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

element-ui 表格 空白问题

npm i element-ui@2.9.0 -S

降低element-ui 版本

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

参考链接

打包指定输出目录

js, css ,图片做分类

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

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

接入Rsdoctor

截止到 2024-01-26 这个插件在 rspack中会报错,相信官方团队很快会解决 Bug #158

介绍: Rspack 和 Webpack 构建分析器

Rsdoctor 地址

npm add @rsdoctor/rspack-plugin -D

安装 cross-env

npm i cross-env -D

在 package.json中增加一个 脚本

"scripts": {
    "doctor": "cross-env RSDOCTOR=true npm run build"
},
npm run doctor
// 分析插件
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({
      // 插件选项
    })
  )
}

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

部分小毛病

  • 目前项目中有的 .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

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, 希望如果你有改造业务系统的计划,可以有帮助和参考

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

仓库地址: vue-element-admin

最终

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

运行效果截图

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

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

构建耗时

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

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

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

Rspack 打包时间在 3.87s

结语

总之:牛逼~

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


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

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

原文链接:https://juejin.cn/post/7328213780310196264 作者:知了清语

(0)
上一篇 2024年1月26日 下午4:21
下一篇 2024年1月26日 下午4:32

相关推荐

发表回复

登录后才能评论