Skip to content

工程化管理工具

NPM 机制及原理

npm 安装机制及企业级私服原理

关于 npm 的问题

  • 删除node_modules和lockfiles,再重新install,是否存在风险?
  • 所有依赖都安装到dependencies,不区分 devDependencies 会有问题吗?
  • 应用依赖公共库A和B,同时A也依赖B,那么库B会被多次安装或重复打包吗?
  • 一个项目中,既用npm,又用Yarn,会引发什么问题?
  • 是否应该提交lockfiles文件到代码仓库?

npm 的核心目标

  • “Bring the best of open source to you, your team and your company.”
  • “给你和你的团队、你的公司带来最好的开源库和依赖。”

npm的安装机制和背后思想

  • 图示:npm-install

i

  • 构建依赖树时,遵循扁平化原则:优先放置在node_modules根目录

npm 缓存机制

  • 查看缓存配置:npm config get cache;(通常在:~/.npm/_cacache)
  • 清除缓存:npm cache clean --force
  • 打开 ~/.npm/_cacache

    • content-v2:二进制文件
    • index-v5:content-v2文件索引
    • tmp
  • 缓存如何被存储,并被利用?

    • 1)npm install执行时,通过pacote把对应包解压到 node_modules 下
    • 2)pacote依赖 npm-registry-fetch 来下载包,根据 RFC 7234 生成缓存数据
    • 3)每次安装资源时,根据 package-lock.json 存储的 integrity、version、name 信息生成唯一的key
    • 4)如果发现又缓存资源,就找到 tar 的hash,再次通过 pacote 把对应二进制解压到 node_modules 下

npm 不完全小技巧

  • 自定义npm init:npm config set init-module ~\.npm-init.js
  • 利用 npm link,在本地项目调试验证包的可用性(类似软链接)
  • npx 的作用

    • 可以直接执行 node_modues/.bin 文件架夹下的文件
    • npx 执行模块时会优先安装依赖,但在安装完后,删除依赖,避免来全局安装带来的问题

npm 多源镜像和企业级部署私服原理

  • 钩子:"preinstall": "node ./bin/preinstall.js"
  • preinstall.js:执行镜像源切换
// npm install 前,自动进行源切换
require('child_process').exec(
  'npm config get registry',
  function(error, stdout, stderr) {
  if (!stdout.toString().match(/registry\.x\.com/)) {
    exec('npm config set @xscope:registry https://xxx.com/npm/');
  }
});
  • nrm

    • npm 的镜像源管理工具
    • 方便快速的镜像源之间切换
  • 如何部署一个私有npm镜像?

    • nexus
    • verdaccio
    • cnpm

.npmrc文件优先级

  • env 环境变量 > 项目级 > 用户级 > 全局级 > npm 内置 .npmrc

Yarn 理念

Yarn 的安装理念及如何破解依赖管理困境

yarn 解决的问题

  • 确定性

    • yarn.lock 保证不管安装顺序如何,相同的依赖关系在不同环境安装时不变;npm 做不到。
  • 采用模块扁平安装:避免造成冗余

  • 网络性能更好:并发连接池排队
  • 缓存机制,实现离线模式

yarn vs npm

  • synp 工具

    • 将yarn.lock 转换为package-lock.json
  • yarn cache dir

    • 查看缓存目录及内容
  • yarn 独有

    • yarn import
    • yarn license
    • yarn pack
    • yarn why
    • yarn autoclean
  • npm 独有

    • npm rebuild

Yarn 安装过程

  • 1)检测包(checking)

    • 检测项目中是否存在一些npm相关文件
    • 检测系统 OS、CPU 等信息
  • 2)解析包(resolving packages)

    • 获取当前项目中package.json定义;
    • 采取 遍历首层依赖 的方式获取依赖包的版本信息;
    • 递归解析依赖

      • 若没有解析过包 A,尝试从 yarn.lock 中获取到版本信息,并标记为已解析
      • 如果在 yarn.lock 没找到包 A,则向 Registry 发起请求,获取满足版本范围内已知最高版本包信息,获取后将包标记为已解析
  • 3)获取包(fetching packages)

    • 检查缓存中是够存在当前依赖包;
    • 将缓存中不存在的依赖包下载到缓存目录, yarn cache dir
    • 如何判断缓存中是否存在当前依赖包?

      • Yarn 根据cacheFolder+slug+node_modules+pkg.name 生成一个 path;
      • 若存在,则命中缓存。
  • 4)链接包(linking packages)

    • 将项目中的依赖复制到 node_modules 下,遵循扁平化原则
    • 解析peerDependencies -> 出现冲突(则提示) -> 扁平化依赖树 -> 执行拷贝任务到node_modules下
  • 5)构建包(building packages)

    • 若依赖包中存在二进制包,进行编译

破解依赖管理困境

  • 如何理解“嵌套地狱”?

    • 项目依赖树的层级非常深,不利于调试和排查问题
    • 依赖树的不同分支里,可能存在同样版本的相同依赖
    • npm 包的安装顺对于依赖树的影响很大
  • 方法一:删除 node_modules,重新安装,利用 npm 依赖分析能力,得到一个更清爽的结构

  • 方法二:使用 npm dedupe 命名
  • 方法三:Yarn 会自动执行 dedupe 命令

NMP CI

CI 环境上的 npm 优化及更多工程化问题分析

npm ci vs install

  • npm ci 要求必需有 package-lock.json 或 npm-shrinkwrap.json 文件存在
  • npm ci 会检测如果 node_modules 已经存在,则先删除再进行安装操作
  • 如果 lock 与 package.json 中版本不匹配,npm ci 直接报错中断,而不是更新 lock 文件
  • npm ci 不能用来安装单个依赖,只能用来安装整个项目的依赖
  • 不会更新 package.json 或 package-lock.json 文件,整个安装过程是锁死的
  • 缓存 npm ci --cache .npm
  • npm ci 时建议加上 --quiet --no-progress 关闭进度和其他无用 log,否则产生的日志会很大。
  • 所以 ci 时推荐完整的命令为 npm ci --cache .npm --quiet --no-progress

why lockfiles

  • 问题:为什么要lockfiles,要不要提交lockfiles?
  • package-lock.json 文件所用时锁定依赖安装结构;保证任意机器安装完全相同node_modules
  • 单一package.json不能确定唯一依赖树;npm install 根据package.json中的 semver-range version 更新依赖
  • 并非所有子依赖都有 dependencies属性,只有子依赖的依赖和当前已安装在根目录的 node_modules 中的依赖冲突后,才会有这个属性。
  • 如果开发完整应用,建议提交 lockfiles;
  • 如果开发发布类库,不提交 lockfiles,减少依赖重复和体积。

依赖类型声明

  • dependencies 项目依赖
  • devDependencies 开发依赖

    • 也可能被打包
  • peerDependencies 同版本依赖

    • 插件安装时,依赖的核心库必须先下载安装
  • bundleDependencies 捆绑依赖

  • optionalDependencies 可选依赖

最佳实践

  • 使用 npm v5.4.2 之后的版本,保证先进性和稳定性
  • 依靠 npm update 升级最新小版本
  • npm install @ 升级大版本
  • 如果 package-lock.json 冲突,建议删除本地,拉取远程,重新 npm install

主流构建工具

横向对比主流构建工具,了解构建工具的设计考量

主流构建工具

  • browserify
  • parcel

    • 零配置,开箱即用
    • 内置多核并行构建、及文件系统缓存
  • rollup

  • webpack

    • 依赖plugin和loader,功能强大
    • 监听文件增量构建,初始构建时间过长

Tooling.Report

  • 构建工具对比平台
  • 核心指标

    • Code splitting

      • webpack/rollup表现最好
      • 代码分割,能够导出公共模块,避免重复打包;
      • 以及在页面加载允许时,实现最合理的按需加载策略
      • 直接影响来前端项目性能
    • Hashing

      • 表现一致
      • 对打包资源进行版本信息映射,重点时最大化利用缓存机制
      • 根据各模块依赖关系,支持开发者自定义哈希策略
      • webpack中的哈希策略

        • hash:反映项目的构架版本,同一次构建,各文件hash一致
        • chunkhash:会根据入口文件(Entry),进行依赖分析
        • contenthash:会根据文件内容,生成hash值
    • Importing modules

      • 表现一致
      • 模块依赖机制
    • Non-JavaScript resources

      • parcel 表现最好
      • 非JavaScript类型资源的支持功能
    • Output module formats

      • parcel/rollup表现最好
      • 构建输出内容的模块化方式
    • Transformations

      • parcel表现最好
      • 对于类似 JSX 、vue 文件的编译,不会内置都构建工具中;而是利用Babel等社区能力

Vite

Vite 实现:从源码出发,构建bundleless开发工程

webpack 不足

vite 设计哲学

  • 理念:Bundleless
  • 开发环境:利用 浏览器 去解析 imports,在 服务端 按需编译返回,完全跳过打包这个概念(bundless)。
  • 生成环境:利用 rollup 打包
  • 源码解读