优化生产构建
上篇我们基本已经完成了整个开发阶段的构建优化。下一步是优化生产构建。
我们的生产发布是基于 GitLab 及 Jenkins 的完整 CI/CD 流。
在优化之前,看看我们的整个项目线上发布的耗时:
可以看到,生产环境构建时间较长, build 平均耗时约 9 分钟,整体发布构建时长在 15 分钟左右,整体构建环节耗时过长, 效率低下,严重影响测试以及回滚 。
那我们看看,整个构建流程,都需要做什么事情:
其中, Build base 和 Build Region 阶段存在较大优化空间。
Build base 阶段的优化,涉及到环境准备,镜像拉取,依赖的安装。前端能发挥的空间不大,这一块主要和 SRE 团队沟通,共同进行优化,可以做的有增加缓存处理、外挂文件系统、将依赖写进容器等方式。
我们的优化,主要关注 Build Region 阶段,也就是核心关注如何减少 npm run build 的时间。
文章开头有贴过 npm run build 的耗时分析,简单再贴下:一般而言, 代码编译时间和代码规模正相关。
根据以往优化经验,代码静态检查可能会占据比较多时间,目光锁定在 eslint-loader 上。
在生产构建阶段,eslint 提示信息价值不大,考虑在 build 阶段去除,步骤前置。
同时,我们了解到,可以通过 esbuild-loader 插件去替代非常耗时的 babel-loader、ts-loader 等 loader。
因此,我们的整体优化方向就是:
1、改写打包脚本,引入 esbuild 插件
2、优化构架逻辑,减少 build 阶段不必要的检查
优化前后流程对比:
优化构架逻辑,减少 build 阶段不必要的检查
这个上面说了,还是比较好理解的,在生产构建阶段,eslint 提示信息价值不大,考虑在 build 阶段去除,步骤前置。
比如在 git commit 的时候利用 lint-staged 及 git hook 做检查, 或者利用 CI 在 git merge 的时候加一条流水线任务,专门做静态检查。
我们两种方式都有做,简单给出接入 Gitlab CI 的代码:
// .gitlab-ci.yml
stages:
- eslint
eslint-job:
image: node:14.13.0
stage: eslint
script:
- npm run lint
- echo 'eslint success'
retry: 1
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "test"'
通过 .gitlab-ci.yml 配置文件,指定固定的时机进行 lint 指令,前置步骤。
改写打包脚本,引入 esbuild 插件
这里,我们主要借助了 esbuild-loader。
上面其实我们也有提到 esbuild,Vite 使用 esbuild 进行预构建依赖。这里我们借助的是 esbuild-loader,它把 esbuild 的能力包装成 Webpack 的 loader 来实现 Javascript、TypeScript、CSS 等资源的编译。以及提供更快的资源压缩方案。
接入起来也非常简单。我们的项目是基于 Vue CLi 的,主要修改 vue.config.js,改造如下:
// vue.config.js
const { ESBuildMinifyPlugin } = require('esbuild-loader');
module.exports = {
// ...
chainWebpack: (config) => {
// 使用 esbuild 编译 js 文件
const rule = config.module.rule('js');
// 清理自带的 babel-loader
rule.uses.clear();
// 添加 esbuild-loader
rule
.use('esbuild-loader')
.loader('esbuild-loader')
.options({
loader: 'ts', // 如果使用了 ts, 或者 vue 的 class 装饰器,则需要加上这个 option 配置, 否则会报错:ERROR: Unexpected "@"
target: 'es2015',
tsconfigRaw: require('./tsconfig.json')
})
// 删除底层 terser, 换用 esbuild-minimize-plugin
config.optimization.minimizers.delete('terser');
// 使用 esbuild 优化 css 压缩
config.optimization
.minimizer('esbuild')
.use(ESBuildMinifyPlugin, [{ minify: true, css: true }]);
}
}
移除 ESLint,以及接入 esbuild-loader 这一番组合拳打完,本地单次构建可以优化到 90 秒。
再看看线上的 Jenkins 构建耗时,也有了一个非常明显的提升:
前端工程化的演进及后续规划
整体而言,上述优化完成后,对整个项目的打包构建效率是有着一个比较大的提升的,但是这并非已经做到了最好。
看看我们旁边兄弟组的 Live 构建耗时:
在项目体量差不多的情况下,他们的生产构建耗时(npm run build)在 2 分钟出头,细究其原因在于:
1、他们的项目是 React + TSX,我这次优化的项目是 Vue,在文件的处理上就需要多过一层 vue-loader;
2、他们的项目采用了微前端,对项目对了拆分,主项目只需要加载基座相关的代码,子应用各自构建。需要构建的主应用代码量大大减少,这是主要原因;
是的,后续我们还有许多可以尝试的方向,譬如我们正在做的一些尝试有:
1、对项目进行微前端拆分,将相对独立的模块拆解出来,做到独立部署
2、基于 Jenkinks 构建时,在 Build Base 阶段优化的提升,譬如将构建流程前置,结合 CDN 做快速回滚,以及将依赖预置进 Docker 容器中,减少在容器中每次 npm install 时间的消耗等
同时,我们也必须看到,前端技术日新月异,各种构建工具目不暇给。前端从最早期的刀耕火种,到逐步向工程化迈进,到如今的泛前端工程化囊括的各式各样的标准、规范、各种提效的工具。构建效率优化可能会处于一种一直在路上的状态。当然,这里不一定有最佳实践,只有最适合我们项目的实践,需要我们不断地去摸索尝试。