一、背景概要
(一) 当前现状及问题
在最近的项目优化过程中,我们针对冷构建时间进行了深入的优化工作。最初,项目的冷构建时间长达120秒,这显然影响了开发效率。经过一系列的努力,我们成功将这一时间缩短至60秒,这是一个显著的进步。然而,在使用Webpack进行优化的过程中,我们发现已经达到了一个瓶颈,进一步的优化效果并不明显。为了继续提升构建性能,我们决定探索其他构建工具的可能性。经过详细的对比分析,我们最终选择了rsbuild作为新的构建工具。rsbuild不仅提供了更快的构建速度,还具有更好的资源管理和更灵活的配置选项。通过引入rsbuild,我们期待能够进一步减少构建时间,提高开发效率,为团队带来更加流畅的开发体验。
(二) 为什么选择Rsbuild
1. 兼容性
Rsbuild 在构建工具领域中扮演了一个兼容性极强的角色,它不仅能够兼容大部分的 Webpack 插件,还能无缝对接所有 Rspack 插件。这使得 Rsbuild 成为了一个灵活多变的构建工具,能够满足不同项目的需求。 相比之下,Vite 则选择了另一条路径,它主要兼容 Rollup 插件。Rollup 插件生态虽然不如 Webpack 那么庞大,但在现代前端开发中也有着广泛的应用。Vite 的这种设计使其在快速启动和热更新方面表现优异,特别适合现代前端应用的开发流程。 值得一提的是,Rspack 在社区支持方面也取得了显著进展。它已经兼容了社区中几乎所有的 Webpack loader,这意味着开发者可以继续使用他们熟悉的工具和配置,而无需进行大量的迁移工作。在下载量最高的 50 个 Webpack 插件中,超过 80% 的插件可以直接在 Rspack 中使用,或者有相应的替代方案。这不仅大大降低了迁移成本,还为开发者提供了更多的选择和灵活性。
2. 生产一致性
Rsbuild 在开发阶段和生产构建过程中都采用了 Rspack 进行打包,这确保了开发环境和生产环境下的构建产物具有高度的一致性。这种一致性类似于你在家里练习烹饪和在餐厅正式上菜时,使用的食材和烹饪方法完全相同,因此无论是在家里还是在餐厅,你做出来的菜肴味道都是一样的。同样地,Rsbuild 通过统一的打包工具 Rspack,使得开发者在开发阶段看到的效果与最终上线的产品效果几乎一致,减少了因环境差异导致的问题。 而这一点也是 Vite 通过 Rollup 想要实现的目标之一。Vite 希望通过优化构建工具和流程,使得开发环境和生产环境之间的差距尽可能小,从而提高开发效率和产品质量。就像一个优秀的厨师不仅需要在厨房里熟练掌握各种烹饪技巧,还需要确保这些技巧能够在不同的厨房环境中得到一致的应用。
3. 性能优势
4. 框架解耦
Rsbuild 作为一个高度灵活的构建工具,它设计之初就秉持着不与任何特定的前端 UI 框架绑定的原则。这意味着无论你是 React、Vue、Svelte、Solid 还是 Preact 的忠实粉丝,Rsbuild 都能通过其强大的插件系统为你提供无缝的支持。这种设计不仅确保了 Rsbuild 的广泛适用性,还为开发者提供了极大的自由度,可以根据项目需求和个人偏好选择最合适的前端框架。 更进一步地说,Rsbuild 的插件机制不仅仅局限于当前流行的框架,它还预留了足够的扩展空间,以适应未来社区中可能出现的新框架或技术。这使得 Rsbuild 成为了一个面向未来的构建工具,能够持续满足不断变化的开发需求,帮助开发者在快速发展的前端领域中保持竞争力。
5. 产物稳定性
Rsbuild 设计时充分考虑了构建产物的稳定性,它的开发和生产构建产物具备较强的一致性,并自动完成语法降级和 polyfill 注入。Rsbuild 也提供插件来进行类型检查和产物语法检查,以避免线上代码的质量问题和兼容性问题。
二、构建方案
(一) 配置指令集
dev:本地构建
build:生产打包
preview:构建产物预览
dev:rsdoctor:本地构建产物诊断
build:rsdoctor:生产打包产物诊断
inspect:查看rsbuild和rspack生成的最终配置
{
"scripts": {
"dev": "export NODE_ENV=development; rsbuild dev",
"build": "rsbuild build",
"preview": "export NODE_ENV=development; rsbuild preview",
"dev:rsdoctor": "RSDOCTOR=true rsbuild dev",
"build:rsdoctor": "RSDOCTOR=true rsbuild build",
"inspect": "npx rsbuild inspect",
},
}
(二) 支持vue架构
import { pluginVue } from '@rsbuild/plugin-vue';
export default defineConfig({
plugins: [
pluginVue(),
],
});
(三) 支持vue-jsx写法
import { pluginVue } from '@rsbuild/plugin-vue';
import { pluginVueJsx } from '@rsbuild/plugin-vue-jsx';
import { pluginBabel } from '@rsbuild/plugin-babel';
export default defineConfig({
plugins: [
// Vue 的 JSX 插件依赖 Babel 进行编译
pluginBabel({
include: /.(?:jsx|tsx)$/,
}),
pluginVue(),
pluginVueJsx(),
],
});
(四) 支持less语法
import { pluginLess } from '@rsbuild/plugin-less';
export default defineConfig({
plugins: [
pluginLess({
lessLoaderOptions: {
lessOptions: {
modifyVars: {
// 或者可以通过 less 文件覆盖(文件路径为绝对路径)
hack: 'true; @import "/src/const/theme.less";',
},
},
},
}),
],
});
(五) 配置打包入口/路径别名/全局变量
source: {
entry: {
index: './src/main.js',
},
// 路径别名
alias: {
'@': './src',
},
define: {
'process.env.NODE_ENV': process.env.NODE_ENV,
'process.env.VUE_APP_ENV': process.env.VUE_APP_ENV,
},
},
(六) 构建输出配置
const ENV = process.env.VUE_APP_ENV || 'dev';
const outputDir = path.resolve(__dirname, `./dist/${ENV}`, pkg.version);
const notPro = ENV !== 'pro';
const localIdentName = process.env.NODE_ENV === 'development'
? '[local]_[hash:base64:4]'
: '_[hash:base64:8]';
const ONLINE_PUBLIC_PATH = ENV === 'pro'
? `https://生产域名/${pkg.name}/latest/`
: `https://生产域名/${pkg.name}/${ENV}/latest/`;
output: {
assetPrefix: ONLINE_PUBLIC_PATH,
distPath: {
root: outputDir,
},
cssModules: {
localIdentName,
},
externals: {
vue: 'Vue',
'vue-router': 'VueRouter',
vuex: 'Vuex',
axios: 'axios',
},
},
(七) 优化打包性能
performance: {
buildCache: true, // 开启babel缓存
chunkSplit: {
strategy: 'split-by-experience', // 分包策略
override: {
cacheGroups: {
vant: {
test: /node_modules[\/](vant)[\/]/,
name: 'chunk-vant',
chunks: 'all',
},
},
},
},
},
(八) 本地开发构建配置
dev: {
progressBar: {
id: '本地构建',
},
hmr: true,
lazyCompilation: false, // 打开后初构建快,进入页面后编译
},
三、问题记录
(一) less计算异常
@size: 0.16rem;
border-radius: @size / 2;
// 被计算成 0.16rem/2px 导致显示异常
问题原因:less4的语法不支持这种写法,项目用的是less3,
解决方式:进飞书群问了一下得到解决方案,将less切换成当前项目版本后解决
pluginLess({
lessLoaderOptions: {
implementation: require('less'),
},
}),
(二) :global()与&搭配使用无法解析
&:global(.van-dropdown-menu) {
height: auto;
background-color: transparent;
box-shadow: none;
&__item {
height: auto;
background-color: transparent;
box-shadow: none;
}
}
问题原因:lightningcssLoader的bug
解决方式:初步判断是less-loader版本问题,rsbuild使用的是12.2.0,切换成7.3.0以后依然有问题,最终禁用lighttingcss以后没有问题
tools: {
lightningcssLoader: false,
},
(三) 打包编译出现css警告
⚠ Invalid dangling combinator in selector at static/css/async/city-container.39dfb20f.css:4:12
⚠ Invalid dangling combinator in selector at static/css/async/city-container.39dfb20f.css:7:12
⚠ Invalid dangling combinator in selector at static/css/async/list.034796af.css:9:13
⚠ Invalid dangling combinator in selector at static/css/async/list.034796af.css:13:13
⚠ Invalid dangling combinator in selector at static/css/async/list.034796af.css:17:13
⚠ Invalid dangling combinator in selector at static/css/async/address-container.2bfc719c.css:131:12
⚠ Invalid dangling combinator in selector at static/css/async/address-container.2bfc719c.css:134:12
⚠ Invalid dangling combinator in selector at static/css/index.8a4f2104.css:947:12
⚠ Invalid dangling combinator in selector at static/css/async/order-submit.c7f14eec.css:10:12
⚠ Invalid dangling combinator in selector at static/css/async/order-submit.c7f14eec.css:13:12
⚠ Invalid empty selector at static/css/async/order-submit.c7f14eec.css:412:2
⚠ Invalid empty selector at static/css/async/order-submit.c7f14eec.css:419:2
⚠ Invalid dangling combinator in selector at static/css/async/3058.e0f31f02.css:6387:22
⚠ Invalid dangling combinator in selector at static/css/async/3058.e0f31f02.css:6392:22
⚠ Unsupported pseudo class or element: last-child at static/css/async/3058.e0f31f02.css:7040:19
⚠ Invalid pseudo class after pseudo element, only user action pseudo classes (e.g. :hover, :active) are allowed at static/css/async/3058.e0f31f02.css:7040:19
⚠ Invalid dangling combinator in selector at static/css/async/3058.e0f31f02.css:8859:12
问题原因:/deep/ @deep: ~'>>>'等语法无法被编译解析
解决方式:后续对不规范的less语法进行统一处理
(四) 样式异常
问题原因:样式写的是width:100,rsbuild打包过后加上了单位编译成了width:100px,根因是rsbuild使用了lightningcss进行css压缩
解决方式:使用@rsbuild/plugin-css-minimizer代替lightningcss进行压缩
"@rsbuild/plugin-css-minimizer": "1.0.2",
import { pluginCssMinimizer } from '@rsbuild/plugin-css-minimizer';
这个压缩方式让本地冷构建时间从7s升到了8s,发布平台冷构建时间从26s升到了30s,二次构建达到了20s
尝试使用esbuild-loader对css进行压缩
"esbuild-loader": "4.2.2",
bundlerChain(chain, { CHAIN_ID }) {
chain.optimization
.minimizer(CHAIN_ID.MINIMIZER.CSS)
.use(EsbuildPlugin, [{ target: 'chrome80', css: true }]);
},
(五) H5在安卓mpaas内打开白屏
问题原因:安卓的mpaas内核较老,无法识别?.的语法,而项目里存在之前本就没有使用到的.browserslistrc,导致rsbuild识别到后编译时忽略了大部分浏览器兼容
> 1%
last 2 versions
not dead
解决方式:删除.browserslistrc,在package.json中配置浏览器支持列表
"browserslist": [
"android >= 4.4",
"ios >= 9"
],
(六) showactionsheet方法会根据上一次打开的actionsheet来进行递增,比如A-actionsheet设置了zindex是1000,在打开A之前actionsheet的zindex是2001 2002 2003的递增,打开A-actionsheet后在调用showactionsheet则从1001开始
问题原因:rsbuild的配置与wepack配置有差异
解决方式:增加babel配置
babelLoaderOptions: (config) => {
// 添加一个插件,比如配置某个组件库的按需引入
config.plugins ||= []
'babel-plugin-import',
{
libraryName: 'vant',
libraryDirectory: 'es',
style: true,
},
]);
}
四、数据分析
指标 | webpack(现状) | rsbuild(重构) |
---|---|---|
发布平台冷构建时间 | 50s | 26s |
发布平台二次构建时间 | 13s | 17s |
五、参考资料
Rsbuild官网
Rspack官网
社区文档