前端工程化学习笔记

拉勾教育《前端工程化精讲》课程

课程二维码

开发效率

  • 脚手架(Scaffold)工具
    • 快速生成
      • 基础代码和目录
      • package.json, *.lock 文件
      • 项目技术栈
      • webpack 配置文件
      • 检查工具、单元测试工具
    • 最佳实践
    • 自定义模板
  • 云开发
  • 无代码工具

脚手架工具

  • Yeoman 通用的,用于一些开发流程里特定片段代码的生成
  • Create-React-App(CRA)
    • react-rewired
    • customize-cra
    • react-scripts 开发流程集成工具
  • Vue CLI 有交互定制化选项
    • @vue/cli 全局命令
    • @vue/cli-service 项目内集成工具
    • @vue/cli-plugin- 功能插件系统

webpack

  • loaders
    • bable-loader
  • plugins
  • optimize
    • TerserPlugin 压缩 js
    • splitChunks 自动分包
  • resolve
    • PnpWebpackPlugin 加速使用 Yarn 模块安装和解析

定制化脚手架模板

  • 团队内部定制,统一风格,减少重复事务
  • Yeoman 生成器(Generator)
    • writing()
      • copyTpl()
    • install()
  • CRA 自定义模板
    • README.md
    • package.json
    • template.json
    • template 目录
      • public/index.html
      • src/index.js
  • Vue CLI
    • meta.js/json
    • vue init [template-name] [app-name]

浏览器的热更新

  • webpack-dev-server

类似的技术:

  • Auto Compile 自动编译
  • Live Reload 自动刷新
  • HMR(Hot Module Replacement) 模块热替换

webpack 配置

  • watch 自动编译: npm run build:watch
  • reload 自动刷新: npm run dev:reload, /sockjs-node websocket 连接,刷新后一些操作或数据会消失。
  • hmr 模块热替换:npm run dev:hmrhot:true ,新增 style-loadercss-loader

webpack 术语

  • module 代码模块
  • chunk 包含多个 module
  • chunk group 通过配置入口点(entry point)区分的块组
  • bundling
  • asset/bundle

构建效率

打包提效

  • 提升当前环节任务的工作效率
  • 提升优化效果,减少传递给下一环节任务的数据量,从而提升后续环节的工作效率

面向 js 的压缩工具

  • Terser整体上比UglifyJS好些
  • Webpack 4 中内置了 TerserWebpackPlugin

面向 css 的压缩工具

  • CSSMinimizerWebpackPlugin 支持缓存和多进程
  • 三个插件都默认基于 cssnano
  • 设置为 chunks:'all' 可以将所有信赖都进行分包处理,从而减少了重复引入相同模块代码的情况。

Tree Shaking

  • 使用 ES6 类型的模块才能进行 Tree Shaking, commonjs 不支持。
  • 以 default 方式引入的模块,无法被 Tree Shaking,这也是为什么说不要用 export default 的原因之一。
  • 只有 sideEffects 为 false 的信赖包(或不在 sideEffects 对应数组中的文件),才可以被 Tree Shaking
    • 如 lodash 替换为 lodash-es,就可以 Tree Shaking,因为 lodash-es 的 package.json 中描述: sideEffects: false
  • 在 Babel 7 之前 modules 默认为 commonjs,不能被 Tree Shaking,而在 Babel 7以后, @babel/preset-env 中默认为 auto,则可以使 ES6 的模块被 Tree Shaking

缓存优化

  • Webpack4 内置压缩插件 TerserWebpackPlugin,默认开启了缓存参数。
  • Babel-loader
    • cacheDirectory: 默认为 false,改为 true 则默认目录,改为目录时,则为指定目录。
    • cacheIdentifier: 计算缓存标识符,决定缓存是否被命中。
    • cacheCompression: 默认为 true
  • Cache-loader 存储的 Buffer 形式的数据处理效率更高
  • 尽可能将不变的处理成本高的模块打入单独 Chunk,以便利用缓存。
  • 使用 splitChunks 优化缓存利用率
  • 注意在 CI/CD 中,要将缓存设置到公共缓存目录下保留,以免清除了缓存,反而低效。

增量构建:只编译打包所改动的文件

  • 增量构建效果:watch 配置为 true,且 cache 配置为 true 就可以实现增量效果。
  • Webpack 4 中, 一般情况下 cache 默认为 false,而在开发模式开启 watch配置时, cache 默认为 true
  • 基于内存的缓存数据无法运用到生产环境中。
  • Webpack 5 中支持基于文件系统的持久化缓存。

Webpack5 的优化细节

  • Persistent Caching 持久化缓存
cache: {
  type: 'filesystem',
  cacheDirectory: '...',
  name: '...', // name 可以保留之前的多套缓存,切换时缓存失效,恢复时则再次使用之前的缓存。
  cacheLocation: path.resolve(), //覆盖 cacheDirectory 和 name
  buildDependencies: {defaultWebpack: []}, // 当信赖项变化时,构建缓存失效。
  version: process.env.NODE_ENV, // 不同环境切换时,缓存失效。
}
  • Webpack 5 会忽略插件的缓存设置,由引擎自身提供构建各环节的缓存读写逻辑。
  • Tree Shaking
    • 增加了对嵌套模块的导出跟踪功能,能够找到内层未使用的模块属性。
    • optimization.innerGraph 生产环境默认开启。
    • 支持了一些 CommonJS 风格模块代码的静态分析功能。
  • Logs 增加了许多内部处理过程日志
    • stats: {logging: “verbose”}

Webpack 5 稳定版本,于 2020年10月10日发布。

无包构建(不打包的构建)

  • 在构建时只需处理模块的编译而无须打包,把模块间的依赖关系完全交给浏览器来处理。
  • 这种通过浏览器原生的模块进行解析的方式称: Native-ESM (Native ES Module)
  • 支持 ES Module 的现代浏览器,会忽略 type=“nomodule” 属性的 script,这通常用于旧浏览器的降级方案。
  • 带有 type="module"属性的 script 在浏览器中通过 defer 的方式异步执行(异步下载,不阻塞 HTML,顺次执行)
  • 带有 type="module"属性且带有 async 属性的 script,在浏览器中通过 async 的方式异步执行,按下载完成顺序执行。
  • 即使多次加载相同模块,也只会执行一次。
  • 模块内信赖的引用,只能使用 import … from ‘…’ ES6风格模块导入。
  • 模块内信赖的引用,只支持相对路径和 URL 方式,不支持直接包名开头的方式。
  • 模块内信赖的引用,只支持MIME Type 为 text/javascript 方式的模块,不支持其他如 CSS 类型文件的加载。

无包构建的优点:

  • 构建速度快,尤其是初次构建速度快很多。
  • 按需编译,根据入口模块分析加载所需模块,编译过程按需处理。
  • 增量构建速度快

无包构建的缺点:

  • 浏览器网络请求数量剧增,对于稳定性和访问性能要求高的生产环境较难接受。 支持 HTTP/2 的服务器会好些。
  • 浏览器的兼容性,对于需要兼容旧浏览器的项目生产环境下不能使用。

Vite

  • 在开发环境下基于 Native-ESM 处理构建过程,只编译不打包
  • 在生产环境下则基于 Rollup 打包。
  • 支持在 React 和 Preact 项目中使用。
  • 支持热更新(HMR)功能。
  • 支持自定义配置文件:config.ts
  • 支持使用 HTTPS 和 HTTP/2
  • 支持配置代理服务,将部分请求代理到第三方服务。
  • 支持模式 mode 和环境变量 .env.production.local
  • 仅面向 ES6 的现代浏览器,最低支持 ES2015
  • 仅限于 Vue3 以上版本,不兼容更低版本的 Vue。
  • 内置了对 Vue 的大量构建优化,有更好的开发体验。

Snowpack

  • 基本与 Vite 类似,主要差异:在生产环境下默认使用无包构建而非打包模式。
  • 提供较完善的插件体系,支持用户和社区发布自定义插件,而 Vite 未提供自定义插件的相关文档。
  • 如需打包功能,则可引入打包插件: @snowpack/plugin-webpack 和 @snowpack/pluin-parcel,暂未提供 Rollup 对应的插件。

部署效率

一般部署流程

  1. 获取代码
  2. 安装信赖
  3. 源码构建
  4. 产物打包
  5. 推送代码
  6. 重启服务

本地部署的问题

  1. 环境一致性问题:使用远程统一的部署系统,可以避免不同开发人员的本地环境差异性。同时,部署系统的工作环境也可以与线上服务环境保持一致,从而降低环境不一致的风险。
  2. 过程一致性问题:通过部署系统将完整处理过程写入代码,减少本地人工操作的风险,同时,系统可以记录每次部署操作的细节日志,方便跟踪问题。
  3. 可回溯性问题:日志可帮助定位问题,构建产物包也会被分别保存,方便问题排查和快速回滚发布。
  4. 人员分工问题:个别开发者会需要中断当前工作,无法快速响应多人的发布请求,如果多人都有权限发布,又会混乱。

流行的代码部署工具

Jenkins

  • 完全免费的开源产品
  • 有丰富的插件系统
  • 有 API 调用功能

CircleCI

  • 云端服务,免费仅一个 Job 及环境限制等。
  • 收费版本支持更多构建数、环境,企业内部使用本地化搭建也是收费的。

Github Actions (GHA)

  • 对公开仓库、自运维执行器的情况下免费。
  • 对私有仓库超过额度的免费执行时间和存储空间后收费。

Gitlab CI

  • 社区版本免费,商业版本收费
  • 使用 .gitlab-ci.yml 配置 CI/CD 流程
  • 需要单独安装执行器: gitlab-runner

信赖安装效率优化

  • npm
  • Yarn
  • Yarn with PnP
  • Yarn v2
  • pnpm

获取执行时间:

time npm i
time yarn
time pnpm i

总体执行时间: npm < pnpm < Yarn v1 PnP < Yarn v1 < Yarn v2

Lock 文件主要优化的是信赖解析阶段的时间。

优化思路

  • 提升信赖下载速度
    • 注册国内下载源 yarn/npm config set registry xxxx
    • 二进制下载源,解决 node-sass, puppeteer 等的下载问题
npm config set sass-binary-site https://npm.taobao.org/mirrors/node-sass
npm config set puppeteer_download_host https://npm.taobao.org/mirrors
  • 多项目共用信赖缓存
    • 让使用相同信赖工具的项目共用相同服务器
    • 让技术栈相同的项目共用相同的服务器
  • 在 CI 系统中对项目的持久化缓存数据做单独的备份与还原。
  • 打包阶段提升压缩效率的工具:
    • Pigz 2xGzip ~ 4xGzip, 与 Gzip 兼容。
    • Zstd 15xGzip ~ 16xGzip, 与 Gzip 不兼容。
    • 原因主要是并行处理,CPU和内存的占用会比较高。

容器化方案

  • node:14 镜像,包含 node14 和 Yarn 的 Linux 系统环境。

镜像阶段

  • 构建镜像的具体内容写在 Dockerfile 文件中
FROM node:12-slim
RUN apt-get update
RUN apt-get install -y git
RUN apt-get install -y build-essential
RUN apt-get install -y curl
  • 在 Dockerfile 所在目录执行构建命令:
docker build --network host --tag foo:bar .

容器阶段

  • 基于项目的工作镜像创建执行部署过程的容器
  • 操作容器执行相应的各部署环节:获取代码、安装信赖、执行构建、产物打包、推送产物等。
# 创建容器
docker run -dit --name container_1 foo:bar bash

# 容器内执行命令
docker exec -it container_1 xxxx

挑战及建议

  1. 缓存问题
  2. 信赖缓存:生成容器时挂载宿主环境信赖缓存目录,安装目录缓存。
  3. 构建缓存:在宿主环境中创建构建缓存目录并挂载到容器中,并在项目构建配置中将缓存目录设置为该目录。
  4. 性能问题
  5. 容器资源限制:在创建容器时,可以通过参数来限制容器使用的 CPU 核心数和内存大小。
  6. copy-to-write:在使用容器化部署时,需要尽量避免将可变数据写入镜像中。

搭建高效部署系统

  • 产物部署分两种模式:

    • Push 模式:通过 scp 等方式推送到目标服务器,并执行解压、重启等发布流程。
    • Pull 模式:提供下载接口,由下游发布环节调用下载获取,然后执行解压、重启等发布流程。
  • 部署服务器环境准备:全局信赖工具、系统配置文件、环境变量、目录规划、监控及清理策略。

  • Webhook:在部署系统中新增接收 Webhook 消息的路由,将此 Webhook 接口地址写入 CVS 系统的 Webhook 列表中,并配置相应参数,如:只监听特定分支或只监听 Tag Push 等。

  • 任务队列:需限制同时执行任务数(Concurrency)避免过多任务同时执行,耗尽计算资源。

  • 构建任务与插件系统

  • 任务命令与子进程

  • 状态、事件与 Socket

目前个人理解,使用类似 Gitlab CI 的功能,可以不用深入这么多细节,只需配置好 .gitlab-ci.yml 即可。