Npm原理深入了解

217 阅读2分钟
  • npm 模块安装机制 npm install
    • 查询 node_modules 目录中是否已经存在指定模块
    • npm 模块仓库提供了 registry 查询服务,可以查询模块压缩包的网址
    • 下载压缩包,存放在根目录下的.npm 目录下
    • 解压压缩包到当前项目的 node_modules 目录
    • 若存在,不再重新安装(即使有新版本)
    • 若不存在
  • npm 实现原理

    • 执行工程本身的 preinstall(如果当前工程定义了 preinstall 钩子) 在 scripts 中指定 preinstall 钩子,例如

      "scripts": {
          "preinstall": "node ./scripts/preinstall.js"
      }
      
      // preinstall.js
      ...
      
    • 确定首层依赖模块

      确定 package.json 中的 dependencies 和 devDependencies 属性中直接指定的模块

      工程自身是整颗依赖树的根节点,每个首层依赖模块都是根节点下的一个子树,npm 会开启多进程从首层依赖模块开始逐步寻找更深层的节点

    • 获取模块

      获取模块是一个递归的过程

      1. 获取模块信息 下载一个模块之前,需要确定版本号,因为 package.json 往往是语义化版本

        此时如果版本描述文件 package-lock.json 中有该模块信息,直接拿;如果没有就从仓库获取(从 registry 查询) 比如:package.json 中某个包的版本是^1.1.0,npm 就会去仓库获取符合 1.x.x 形式的最新版本

      2. 获取模块实体 第一步可以获取到压缩包的地址(resolve 字段),npm 会用此地址检查缓存,如果有就直接拿,没有就从仓库下载

      3. 查找该模块的依赖 如果该模块有依赖回到第一步,没有则停止

    • 模块扁平化处理 上一步处理完成可以获取到一棵完整的依赖树,其中可能包括大量重复的模块(不同模块依赖了相同的模块);从 npm3 版本加入了 dedup 过程,会遍历所有的节点,将模块逐个放在根节点下边,如果有重复模块,就丢弃

    • 装置模块 更新 node_modules 并执行 模块中的生命周期函数(按照 preinstall postinstall 顺序)

    • 执行工程自身的生命周期 当前 npm 如果定义了钩子此时会被执行 最后一步生成或更新版本描述文件

简单实现思路 :

// 定义 allDeps = {}

// 获取 package.json 文件,path.resolve(...), 将 json 转为对象

// 获取 package.json 中的 dependencies 属性

// 遍历依赖列表:

* 如果要安装的包和 package.json 版本一致,跳过;

* 如果不一致: 如果 package.json 版本低,跳过,

* 否则记录下冲突 allDeps[depName].conflict = true

// 4. 将版本信息记录到 allDeps

allDeps[depName] = {
    depname:'xxx',
    url: '',
    branch: '',
    version: ''
}

// 5. 下载

* 下载到全局缓存目录,移动到本地目录

* 如果全局目录已经有了,直接移动

* 如果不存在,先下载,再移动