美文网首页
冷启动 4min -> 2s 的构建优化,怎么做到的?(下篇)

冷启动 4min -> 2s 的构建优化,怎么做到的?(下篇)

作者: 懂会悟 | 来源:发表于2022-08-28 08:56 被阅读0次

    优化生产构建

    上篇我们基本已经完成了整个开发阶段的构建优化。下一步是优化生产构建。

    我们的生产发布是基于 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 时间的消耗等

    同时,我们也必须看到,前端技术日新月异,各种构建工具目不暇给。前端从最早期的刀耕火种,到逐步向工程化迈进,到如今的泛前端工程化囊括的各式各样的标准、规范、各种提效的工具。构建效率优化可能会处于一种一直在路上的状态。当然,这里不一定有最佳实践,只有最适合我们项目的实践,需要我们不断地去摸索尝试。

    相关文章

      网友评论

          本文标题:冷启动 4min -> 2s 的构建优化,怎么做到的?(下篇)

          本文链接:https://www.haomeiwen.com/subject/tklcnrtx.html