关于node.js:Nodejs-应用编译构建提速建议-京东云技术团队

3次阅读

共计 5682 个字符,预计需要花费 15 分钟才能阅读完成。

编译构建的整体过程

  1. 拉取编译镜像
  2. 拉取缓存镜像
  3. 拉取我的项目源码
  4. 挂载缓存目录
  5. 执行编译命令(用户自定义)
  6. 长久化缓存
  7. 上传编译镜像

为什么在本地构建就快, 但编译机上很慢

在编辑机上每次的构建环境都是全新的, 实现一次构建比本地须要多一些步骤:

  1. 现成的全局包缓存 VS 从新构建缓存: 咱能够先简略了解为咱应用 npm 的时候那个全局的缓存目录, 编辑机须要筹备长久化的缓存的环境, 包含下载、挂载以重建缓存, 如果缓存内容过大, 工夫也会绝对更长, 本地构建间接应用了稳固的本地文件系统;
  2. 增量装置依赖 VS 全量装置依赖: 本地不太常常须要执行 install 的过程, 即便须要, 也因为有长久的 node\_modules 目录存在, 不须要全量装置, 但编辑机环境每次须要重新安装这个我的项目须要的所有依赖;
  3. 增量构建 VS 全量构建: 本地构建默认会将构建缓存放到 node\_modules 目录下, 第二次构建的时候这些构建就能被用起来, 使得前面的构建更快, 但这个构建的默认缓存地位在编辑机上不会被长久化, 也就是每次须要全量构建.
  4. 网络环境: 有些依赖包装置依赖内部网络甚至海内网络, 本地的网络环境比拟顺畅, 但编辑机的网络对与海内网的拜访没有保障.
  5. 难以利用的劣势: 多核大内存, nodejs 我的项目的构建, 大部分工作都在一个线程上执行了, 不好间接利用编译机的多核优势
  6. 额定的步骤: 编译机须要下载镜像、制作并上传运行镜像、缓存内容长久化, 而本地个别只是产出包.

所以从以上角度动手, 咱们能够基于这样的一些思路进行构建速度的优化:

  1. 优化镜像大小;
  2. 善用长久化缓存实现增量构建(编辑机会对 /cache/ 目录下的内容进行长久缓存)
  3. 充分利用多核优势:

    比方 ts-loader 的类型校验就能够通过其它插件在独自的线程执行, eslint-loader 也反对多线程(但目前有 bug, 不倡议应用).

    再比方咱们能够对我的项目的各功能模块解耦, 拆成多个构建同时进行。

  4. 缩小不必要的构建:

    比方合理配置 exclude 以精简构建文件范畴;

    对于不常变动的文件, 拆出来一次构建, 下次复用.

  5. 判断是否可能有其它形式去掉对外网依赖的包

如何剖析构建速度

  1. 查看 /cache/ 目录大小:
  2. 在编译命令中退出:du -sh /cache, 通过构建日志查看目录大小
  3. 在整体编译命令前后都加上date, 能够看本人我的项目的构建过程耗时, 即编译命令执行工夫
  4. 在次要的编译命令的每一行后面加上 time, eg:time npm install 能够看 install 过程的理论耗时, build 过程同理.
  5. 比照整体构建工夫 (网页上间接显示的工作工夫) 与编译命令执行工夫(开端的 date 工夫 – 结尾的 date 工夫), 如果整体工夫超过编译命令执行工夫很多(> 1min30s), 可能是 /cache/ 目录或镜像过大导致的。

以下为详情介绍:

应用更小的运行镜像

如果有较大的镜像, 倡议分割运维进行优化.

善用长久缓存

缓存能够对利用构建带来提速的成果, 但如果缓存目录持续增长, 大到肯定水平反倒可能让速度变慢.

理解缓存机制:

1. 缓存目录: /cache/

2. 默认行为: 对于 nodejs 的利用, 目前长久缓存会为 npm, pnpm 提供安装包的缓存, 以放慢 npm install / pnpm install 的过程

3. 工作原理: 

    3.1 /cache/ 目录下的内容会构建胜利后主动上传到服务器进行存储, 并在下次构建工作执行前进行挂载

    3.2 /cache/ 与 当前工作目录(即 './', 拉取的源码寄存地位) 不在同一个文件系统(相当于是缓存在 C 盘而源码在 D 盘), pnpm install 的行为将从 hark link 回退为文件复制(硬链接的形式绝对于大量小文件的拷贝, 速度要快很多)

    3.3 /cache/ 的工作波及上传、下载过程, 如果过大也将会影响整个构建过程的速度

排除全局缓存对构建速度的影响

查看 /cache/ 的大小, 能够在编译命令中退出:du -sh /cache, 查看日志, 如果文件夹超过 1G(仅供参考), 倡议咚咚分割行云部署 (j-one) 对利用缓存进行清理

解决缓存跨盘造成的性能损失

次要思路: 使源码与 /cache/ 处于同一个文件系统. 目前对于 pnpm 的利用举荐该形式.

原理: 使源码与 /cache/ 处于同一个文件系统, 这能够让 pnpm 的 hard link 形式失效, 绝对于 node\_modules 那些数以万计的小文件复制, 执行效率会失去可观的晋升. 参考:Pnpm 是否能够跨多个驱动器或文件系统工作?

形式: 将当前工作目录的代码复制到 /cache/ 下再执行 install、build 命令.

参考命令:

    # 记下当前工作目录
    CUR_WORKSPACE=`pwd`
    # 寄存源码
    # 咱对立用 /cache/source 放源码就好, 尽管也能够改成其它目录的名字
    mkdir -p /cache/source
    # 拷贝当前目录的代码, 到 /cache/source 下
    rsync -r ./ /cache/source --exclude=node_modules --exclude=.git
    # 切换 workspace
    cd  /cache/source
    ########## 这里替换成本人须要的内容  ###########
    # 执行 install
    pnpm i
    # 执行 build
    pnpm run build
    ########## 这里替换成本人须要的内容  ###########

    # 将构建后果拷贝到抽包地址
    ########## 如果不是 dist, 请依据须要换成其它目录, 就是你我的项目构建完生成的指标代码目录
    cp -r ./dist/* ${CUR_WORKSPACE}/.build
    # 删除不须要被缓存的文件
    cd ../ && rm -rf /cache/source

以上编译命令基于行云部署前端我的项目自身精简 \
请大家在了解原理、思路的根底上依据本身须要批改.

缓存构建后果

webpack 及其插件, 会对构建后果进行缓存. 咱们能够利用 /cache/ 的长久化缓存来实现代码构建缓存. 其它构建工具也能够参考相干文档进行配置.

如果应用 webpack4 或依赖 webpack4 的构建工具, 比方 @vue/cli-service 等, 通常会应用 cache-loader 对构建后果进行缓存, babel-loader 也会有本人的构建缓存, 但默认都放在 node\_modules/.cache 目录下, 倡议参考相干文档将 cache 目录设置为 /cache/build (或者其它 /cache/ 的子目录)

对于 webpack5, 本人就曾经集成了 cache 性能, 能够删掉 cache-loader 等插件, 缩小不必要的工作. 参考:webpack cache

如果是 monorepo 的利用, 还能够实现子项目级别的缓存, 比方应用 nx 进行 monorepo 的治理, 则能够配置 NX\_CACHE\_DIRECTORY 来设置缓存地址, eg:

export NX_CACHE_DIRECTORY=/cache/jdos3-console-ui/.nx

eslint 也是一个很费时的操作, 它也反对缓存, 但默认不开启, 如果有须要也能够开启缓存, 但缓存策略须要应用 ‘content’, 因为每次构建文件的 createTime 都会扭转, metadata 的策略会失灵. 参考:eslint cache

通常咱们须要同时兼容本地开发和行云部署的构建, 能够通过环境变量的形式实现, 以 webpack5 为例:

webpack5 的缓存配置:

{
    cache: {
        type: 'filesystem',
        profile: true,
        cacheDirectory: process.env.BUILD_CACHE_DIRECTORY,
        compression: 'gzip',
    },
}

同时在行云部署的编译命令中减少:

export BUILD_CACHE_DIRECTORY=/cache/.webpack

另一种利用缓存的思路: 缓存 node\_modules

(编译团队提出了这种思路, 我目前没有进行相干尝试, 产品上针对该思路的通用解决方案在摸索中)\
次要思路 : 模仿本地构建(本地构建会长久保留 node\_modules 目录)\
收益:\
1. 减速 install 的过程, 缩小包的装置.\
2. 利用代码构建缓存: webpack5 或 babel-loader 等个别会在 node\_modules/.cache 目录下寄存构建缓存, 这也是很多利用本地构建较快的起因. 当然 .cache 目录会持续增长, 须要定时清理, 有趣味大家能够看看本地的代码里是否有这个目录, 占多大空间.

参考命令 :\
大体上与下面 ‘ 解决缓存跨盘造成的性能损失 ’ 过程雷同, 只是最初 rm 的过程保留 node\_modules 目录, 以供下次应用

    ####### 与下面 解决缓存跨盘造成的性能损失 统一 #########
    # 记下当前工作目录
    CUR_WORKSPACE=`pwd`
    # 寄存源码
    mkdir -p /cache/source
    # 拷贝当前目录代码到 /cache/ 下
    rsync -r ./ /cache/source --exclude=node_modules --exclude=.git
    # 切换 workspace
    cd  /cache/source
    # 执行 install
    npm i
    # 执行 build
    npm run build
    # 将构建后果拷贝到抽包地址
    cp -r ./dist/* ${CUR_WORKSPACE}/.build
    
    ####### 差别: 删除时排除 node_modules 目录 #########
    # 删除不须要被缓存的文件
    ls -A | grep -vE "^\.$|^\.\.$|^node_modules"|xargs rm -rf

缩小源码

防止在 coding 中提交 node\_modules 以及各种大的二进制文件

优化编译过程

优化依赖包装置的过程

  1. 有些我的项目依赖了 image-minimizer-webpack-plugin, 这是一个用于压缩图片的工具, 该资源依赖的 cwebp-bin 等资源须要从海内的网站下载, 这个过程可能会很慢甚至失败. 如果可能, 倡议间接提交压缩后的图片到代码库, 同时去掉对这个插件的援用.
  2. 能够在编译命令前加上 time, 比方 time pnpm install 来察看这一步骤的耗时, 如果这一步骤很长, 能够看是否有能够去掉的依赖包, 或者禁用对可选依赖包的装置, 有时候降级构建工具也能使包依赖失去优化.

优化构建过程

  1. 对于 webpack 构建的利用, 对 rules、plugin(如果反对) 查看是否正确设置了 exclude, 用以缩小不必要的文件构建
  2. 启用构建缓存(但缓存的持续增长还是须要关注, 缓存过大的问题后续可能从产品层面得以优化)
  3. ts-loader 通常能够开启 transpileOnly: true, 并通过 fork-ts-checker-webpack-plugin 进行类型查看
  4. eslint 的优化, 能够对规定进行优化, 有些校验规定是十分耗时的, 但同时受害并不是很大, 能够思考敞开. 具体能够这么做:

4.1 设置 \_\_TIMING\_\_环境变量, 能够启用对每个 eslint rule 的性能剖析,export TIMING = 1;\
4.2 在本地失常执行构建, 检测 eslint rule performance 的输入, 剖析耗时较长的规定, 确认是否必要

补充:

  • 对于 eslint 的 多线程 问题: 对 eslint 开启多线程之后会导致 build 过程发现的规定异样不能抛出, 导致规定理论会生效. 该问题参考 Issue, 这个问题挺久了, 始终没有失去无效解决.
  • 同时也能够思考将 eslint 的校验作为 git hook 执行, 防止提交不标准的代码, 此时在 build 过程能够省略这一步骤.

5. 代码 minify 的过程, 举荐应用 esbuild, 在 webpack 外面就能够配置.

{
   optimization: {
       minimize: true,
       minimizer: [
           new TerserPlugin({minify: TerserPlugin.esbuildMinify,}),
       ],
   }
}

6. 对于不常常变动的局部, 倡议提前编译, 或通过 DllPlugin 进行优化. 比方行云部署我的项目自身依赖 monaco editor, 但每次对它的源码进行构建很耗时, 所以间接将提前编译好的代码提交了, 后续间接用.

7. 留神防止一个我的项目被 build 屡次, 比方:\
7.1 对于应用 vue-cli-service 的利用, v5.0.0-beta.0 开始, 可能会依据浏览器列表配置生成不同的包, 会导致屡次构建 \
7.2 有一些我的项目须要微前端接入, 可能会为独立运行时、子利用模式采纳不同的入口, 从而构建两次. 比方 JModule 的用户, 因为极晚期 webpack-jmodule-plugin 的版本不能自定义入口文件, 通常会构建两次, 倡议降级为最新的 @jmodule/plugin-webpack, 并且采纳同一个入口文件构建一次.

8. 如果是一个绝对简略的利用, 能够思考换其它构建工具, 比方 esbuild、swc, 编程语言带来的性能差别, 的确能造成降维打击.

9. 如果可能, 剖析我的项目代码间的依赖, 拆分为多个构建并行执行, 编译机的最大劣势就是多核, 咱能够充分利用.

10. 降级 webpack 以及其它构建插件, 通常也能带来肯定水平的速度晋升, 咱们 jci 我的项目的编译就从降级中取得了一些受害.

补充:

  1. webpack 的更多细节优化, 能够参考 https://webpack.docschina.org/configuration/cache/
  2. 同样这里也能够思考在 build 命令前加 time, 比方time npm run build, 便于察看这一步的工夫.
  3. 还能够用‘speed-measure-webpack-plugin’对 webpack 的构建时长进行辅助剖析.

前端构建的提速是一项比较复杂且细节的工程, 目前产品上在继续跟踪构建慢的利用, 致力优化编译速度, 但前端自身领有一个比拟自在的技术环境, 没有对立的构建工具与流程, 另外语言自身的执行效率、单线程的构建也不好让编译机施展其最大能力, 所以目前全局的通用优化伎俩还是会比拟局限, 还是依赖我的项目本身的优化. 心愿大家一起致力共建美妙的今天.

作者:京东科技 林光芒

内容起源:京东云开发者社区

正文完
 0