Vue2.6+Webpack4项目迁移至Rsbuild

3,006 阅读4分钟

🎉🎉🎉恭喜Rsbuild1.0版本发布!!!期望后续发展更好!!!

zepto兼容出现巨大翻车,详情请见下方zepto一节

原项目介绍

原项目是一个上古移动端Hybrid工程:

  • 项目主要依赖:vue@2.6.11 + webpack@4.41.6
  • CSS预处理器:node-sass@4.14.1 + sass-loader@8.0.2
  • 前朝遗孽依赖:zepto@1.2.0

Node版本切换

Node 18+

主要依赖安装

Rsbuild核心依赖:

// package.json
{
  "@rsbuild/core": "^1.4.0",
  "@rsbuild/plugin-vue2": "^1.4.0",
}

Rsbuild支持Vue最低版本为Vue@2.7,因此需要把旧项目从2.6升级到2.7

// package.json
{
  "vue": "^2.7.14",
  "vuex": "3.4.0"
}

项目根目录新增rsbuild.config.mjs

import { defineConfig } from '@rsbuild/core';
import { pluginVue2 } from '@rsbuild/plugin-vue2';

export default defineConfig({
  plugins: [
    pluginVue2()
  ],
});


npm命令替换:

// package.json
+ {
+   "dev": "rsbuild dev --open",
+   "build": "rsbuild build",
+ }

打包入口entry

原工程是一个MPA多页应用,因此需要配置多入口打包。根据官方文档使用source.entry来配置入口:

参考www.rspack.dev/zh/config/e…

// rsbuild.config.mjs
export default defineConfig({
  source: {
    entry: {
      home: './src/Home/index.js',
      about: './src/About/index.js',
    },
  },
});

多页面dev的时候rsbuild默认输出所有页面的server URL地址,当页面很多的时候会占据一整屏shell空间,强迫症实在接受不了,因此参考官方自定义URL文档自定义输出:

import { logger } from "rslog"
import { defineConfig } from "@rsbuild/core";

export default defineConfig({
  server: {
    printUrls({ urls }) {
      for(const url of urls) {
        logger.greet(`${url}`)
      }
    },
  },
});

原工程每个页面HTML都有一些个性化js引用,且使用#app节点进行Vue的挂载。而官方默认打包后的HTML文件使用#root进行挂载。因此需要配置入口HTML。根据官方文档使用html.template来配置:

参考rsbuild.dev/zh/config/h…

// rsbuild.config.mjs
export default defineConfig({
  html: {
    template({ entryName }) {
      const templates = {
        home: './src/Home/index.html',
        about: './src/About/index.html',
      };
      return templates[entryName]
    }
  },
});

VueRouter

按原工程依赖版本号固定版本号:

// package.json
{
  "vue-router": "3.1.6",
}

原工程大量页面在路由守卫beforeRouteEnternext方法中恢复路由的滚动高度:

export default {
  beforeRouteEnter(to, from, next) {
    next(vm => {
      document.documentElement.scrollTop = document.body.scrollTop = vm.scrollTop
    })
  },

  beforeRouteLeave(to, from, next) {
    this.scrollTop = document.documentElement.scrollTop || document.body.scrollTop
    next()
  },
}

但是更新VueRouter小版本后发现此方法失效:vm.scrollTop有值,滚动条高度却为0。调试后发现可能失效的原因:新版本的next方法在Vue组件渲染前执行,页面还未产生滚动条,因此赋值滚动条高度失败。思虑再三按旧项目固定此依赖的版本号。

alias

根据官方文档使用resolve.alias配置路径别名

参考rsbuild.rs/zh/config/r…

// rsbuild.config.mjs
export default defineConfig({
  resolve: {
    alias: {
      '@styles': './assets/styles',
    },
  },
});

Sass

原工程node-sass仅支持Node 14,因此需要进行依赖更新以适配Node 18。去除原本的node-sasssass-loader。Rsbuild内置了最新的sass处理,根据官方文档安装插件@rsbuild/plugin-sass即可

参考rsbuild.dev/zh/plugins/…

// package.json
{
  "@rsbuild/plugin-sass": "^1.3.2",
}
// rsbuild.config.mjs
import { pluginSass } from '@rsbuild/plugin-sass';

export default defineConfig({
  plugins: [
    pluginSass(),
  ]
});

部分语法弃用警告

最新版本的rsbuild默认添加了配置来忽略 @import 的警告

最新的sass 版本为1.80+,运行项目会出现一堆即将废弃的语法警告:

image.png

如果条件允许,建议根据sass官方指引针对性更改。

我们工程迁移不允许大面积更改代码,因此选择屏蔽掉这些警告。起初参考的是node-sass更换为dart-sass警告消除这篇文章。后来发现Rsbuild官网就有解决方法忽略 Sass 废弃提示。因此多看官网文档是一个好习惯。

样式覆盖

原工程node-sass使用/deep/来进行组件样式覆盖,而dart-sass使用::v-deep来进行组件样式覆盖,因此需要全局替换工程中的/deep/::v-deep

.test {
-  /deep/ .van-radio__label {
+  ::v-deep ..van-radio__label {
   color: red;
}

如果不想手动修改代码而通过构建工具统一替换,可以看我另一篇文章Rsbuild迁移之node-sass引发的血案

样式计算

dart-sass不支持 $padding / 2的写法,因此需要进行转换:

更新:是Dart Sass 2.0.0不支持此样式计算方法。使用Dart Sass 1版本依然可行,但Rsbuild会打印Warning日志,屏蔽即可。

$padding: 16px;

.test {
- padding: $padding / 2;
+ padding: calc($padding / 2);
}

不当语法修改

之前的工程有许多不当的Sass语句,编译没有问题,迁移到Rsbuild之后编译报错,需要修正过来:

.demo {
+    color: blue;

    .child {
        color: red'
    }
    
-    color: blue;
}

构建的时候如果有语法冲突,Rsbuild在终端中有修改提示,按照提示进行改造即可。为官方点赞👍

PostCSS

Rsbuild默认集成PostCSS。官方文档让使用tools.postcss进行配置。但经过测试,Rsbuild可以识别工程中postcss.config.js 原有的配置,因此删除旧工程中的相关依赖即可:

// package.json
- "postcss-loader": "3.0.0",

zepto

原工程有一个zepto编写的的JSBridge核心包,我实在不敢乱动,因此还是考虑进行兼容

原迁移方法使用commonjs-zepto包进行替换。但发现commonjs-zepto和html2canvas有重大兼容问题:使用commonjs-zepto后html2canvas生成的canvas中莫名出现很多normal字样,且样式布局错乱。因此使用zepto-webpack替换,目前测试没有问题

zepto是AMD格式的包,Webpack、Rsbuild直接使用会报错。旧项目通过exports-loaderscript-loader 转换成CommonJS:

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: require.resolve('zepto'),
        loader: 'exports-loader?window.Zepto!script-loader'
      }
    ]
  }
}

然后在代码使用:

import $ from "zepto"

迁移Rsbuild时考虑去除多余的loader。因此转换思路使用commonjs-zepto zepto-webpack包替换,然后新建一个zeptoalias进行代码兼容。

// rsbuild.config.mjs
export default defineConfig({
  source: {
    alias: {
      'zepto': 'zepto-webpack',
    },
  },
});

静态资源

Rsbuild内置对静态资源的处理,因此不需要进行额外的改造,把原项目中处理静态资源的相关loader删除即可。

Rsbuild构建的CSS产物中,静态资源路径是一个基于output.assetPrefix 的绝对路径。比如:

// css output
.test {
    background: transparent url(prefix/static/image/a.24ae3b16.png) no-repeat;
}

我们项目的Hybrid App客户端将远端页面代码拉至本地,再通过file:// 加载。上述这种CSS文件通过绝对路径加载静态资源的方式会失效,找不到对应资源。

询问AI回答如下:

CSS 中的url()路径解析则更 “静态”,它的基准路径是CSS 文件本身所在的位置,而不是页面路径,当assetPrefix设置的绝对路径(逻辑路径)和本地文件的物理路径不匹配时,就会加载失败。

因此需要将CSS中的静态资源访问改为相对路径。Webpack中通过配置mini-css-extract-pluginpublicPath 来实现此功能。而在Rsbuild 中通过tools.cssExtract进行配置:

import { defineConfig } from "@rsbuild/core";

export default defineConfig({
  tools: {
    cssExtract: {
      loaderOptions: {
        publicPath: "../../"
      }
    }
  },
});

由于Rsbuild 构建会将异步CSS单独放入static/css/async目录下,因此此文件夹中的CSS文件,配置../../ 的publicPath也不会生效。需要将异步CSS也放入一般CSS文件目录下:

import { defineConfig } from "@rsbuild/core";

export default defineConfig({
  output: {
    assetPrefix: "prefix",
    distPath: {
      cssAsync: 'static/css',
    }
  },
  tools: {
    cssExtract: {
      loaderOptions: {
        publicPath: "../../"
      }
    }
  },
});

浏览器兼容性

语法降级和polyfill相关可以看这篇文章,要不要添加polyfill可以自行斟酌。我们项目算是上古项目,为保证迁移后的兼容性,还是加了polyfill。

旧项目通过引入@babel/polyfill ,兼容低版本浏览器。Rsbuild使用SWC 进行代码转换,转换@babel/polyfill时会报错,因此需要全量去掉项目中的引用:

- import '@babel/polyfill';

然后在项目根目录添加.browserslistrc 文件配置浏览器兼容范围:

参考rsbuild.dev/zh/guide/ad…

下面的配置是我们项目的目标兼容范围,可以根据你们项目组的兼容范围自定义

>0.3%, last 2 versions, since 2018, not dead

然后在配置文件中根据官方文档配置适合自己项目的Polyfill方案:

import { defineConfig } from "@rsbuild/core";

export default defineConfig({
  output: {
    polyfill: "usage"
  },
});

构建产物比较

webpack4.4构建

image.png

Rsbuild构建

image.png

结语

上面构建产物统计可以看出新工具的提升巨大,老旧项目可以尝试迁移。感谢社区的努力,让上古僵尸项目可以焕发新生。❤️❤️❤️