npm工作机制

Packages(包) and Modules(模块)

深入理解一套编程生态系统的途径之一是学习它的所有概念。nodejs和npm对包和模块有特定的定义,但是又很容易混淆。我们在这里讨论它们的定义并进行区分,并解释默认文件命名规则 My first method exposes how to print a message in JavaScript and Go.

快速总结

Package是使用package.json文件描述的文件或者文件列表。 Module是可以通过nodejs的require()加载的文件或者文件列表。

什么是package

有以下特征的都可以称为package: (1)一个包含被package.json描述的项目的文件夹 (2)包含第一条所列的压缩打包工具 (3)指向第二条的url (4)在registry上发布的<name>@带有第三条特征的url的 (5)指向<name>@的<name>@ (6)有一个最新的满足第五条的tag的 (7)使用git克隆类似第一条的内容时的url

什么是module

module可以在nodejs的项目中通过require()加载。以下是所有可以通过require()作为模块加载的东西: (1)带有package.json文件的文件夹,并且package.json中包含'main'字段 (2)包含index.js的文件夹 (3)一个js文件

大部分的包都是模块

nodejs和npm系统中的文件和文件夹

package.json文件定义了包 nodemodules文件夹是nodejs寻找模块的地方 比如,如果你在_node_modules下创建了foo.js,然后在项目中执行 _var f = require('foo.js') 它将会加载这个模块。但是在这种情况下,因为foo.js没有 package.json的文件,所以不是包。 相应的,如果你创建了一个没有index.js的包或者一个 package.json文件中没有main字段的包,那么这个包不是模块,因为虽然它在node_modules中安装了,但是无法作为require()的参数。

npm v2 关于同包不同版本依赖处理

实例1

npm v2实例 A/B/C三个模块,A requires B at v1.0, and C also requires B, but at v2.0.

下面创建一个APP引入A模块和C模块

依赖混乱

包管理器会提供一个版本的B模块,但是对于引入的B模块的版本是一个问题。

npm不会试图使用某一个版本来满足两种版本的要求,而是同时引入了B模块的两个版本。

在npm中结构 通过 npm ls 可以看到各文件之间的依赖关系。 通过 npm ls --depth=0 可以看到最上层文件。

npm and the Node.js Module Loader

npm只这样做是不够的,除了这样被嵌套在不同文件夹下情况可以同时加载同一模块的不同版本,大多数情况下是不可以加在同一模块的不同版本的。但是nodejs的模块加载器处理了这种情况,可以加载同一模块的不同版本,只要版本之间不存在冲突。

npm v3的依赖处理

npm3和npm2对依赖的处理是不同的。 npm2通过嵌套的方式安装了所有的依赖,npm3试着减少文件树的层级和嵌套造成的冗余。为此npm3在引入的基础依赖中以更加扁平的方式在该列表下安装了一些二级依赖(即依赖的依赖)。 这两者主要的区别是: (1)依赖在文件列表中的层级位置并不代表着这个依赖的类型(如一级依赖,二级依赖) (2)通过安装的顺序处理依赖,或者说依赖的安装顺序改变了node模块的结构。

实例2

npmv3实例 一个A模块,需要引入B模块。

下面,我们创建一个应用,引入A模块。 在npm安装时,v3会在同一层级下同时安装A模块和它的依赖模块B, v2会以层级安装,如下图:

我们再引入模块C,C依赖B,但是和A依赖的不是同一版本。

在应用的根部已经定义了V2.0的B模块,npmv3通过在C模块下嵌套另一版本的B模块实现。 通过 npm ls 可以看到各文件之间的依赖关系。 通过 npm ls --depth=0 可以看到最上层文件。

npm3 数据去重

继续之前的例子,当前应用 (1)A依赖Bv1.0 (2) C依赖Bv2.0 但是我们如果再引入一个依赖Bv2.0或者Bv1.0的模块呢

实例3

假设现在我们需要再引入模块D,它和C模块一样依赖于Bv2.0

Bv1.0已经是最高层级的依赖,我们不能再第一级依赖中加入Bv2.0了,所以我们要在已经安装了Bv2.0的情况下再在模块D下嵌套Bv2.0

如果一个二级依赖被多个模块引入,但是这个二级依赖又不能安装在第一级列表中,那么它会在引用它的多个基础依赖中被多次嵌套和复制。 但是,如果这个二级依赖被多个模块引入,但是在第一级列表中安装,那么就可以在多个模块中共享。 下面我们建立一个对E模块的依赖,像A模块一样,它也需要引入Bv1.0.

Bv1.0处于根目录下,我们不需要再复制它,E和A可以共享Bv1.0 在客户机上如下: 下一步,我们更新模块A到2.0,同时它不在依赖B1而是依赖B2.

关键是牢记安装顺序很重要

模块A首先通过package.json文件被安装,使用npm install命令最后安装模块A2.0 当我们运行npm install mod-a@2 --save时npm3会做以下工作: (1)它移除A1 (2)它安装A2 (3)保留B1,因为E1还依赖B1 (4)在A2下嵌套安装B2,因为B1已经在列表的根部安装了 在客户端的结构如下: 最后,我们更新模块E到E2,它也不在依赖B1,而是依赖B2

(1)它移除E1 (2)它安装E2 (3)移除B1,因为没有什么依赖B1 (4)在列表的根部安装B2,因为在列表根部没有B模块 在客户端的结构如下: 但是,现在还不够理想,在ACD模块下还有B2,。我们可以通过npm dedupe来去重 这个命令将所有对B2的依赖指向在列表根部的B2模块,并去掉所有的嵌套在模块下一级的副本 此时在客户端的结构如下:

npm3 Non-determinism

在之前的例子中我们提到:

如果你和你的开发团队使用了package.json文件,并且也用了npm install来安装包,那么你本地的node_modules目录很可能和同事的node_modules以及你的测试环境和生产环境的node_modules目录不一样。 难道是npm3安装依赖的规则是不确定的吗? 这个貌似不太可能,在这里我们讨论一下为什么会这样,并且这和你的应用没有任何关系,也解释了可靠地创建一个单独的、持久的node_modules目录的步骤。

实例

让我们回到之前的例子中

在这个例子中,我们的应用的package.json是这样的:

{
  "name": "example3",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "mod-a": "^1.0.0",
    "mod-c": "^1.0.0",
    "mod-d": "^1.0.0",
    "mod-e": "^1.0.0"
  }
}

在客户端执行npm install我们会看到:

现在我们要在应用中增加一个新的功能,需要将A更新到v2,同时A依赖的B模块也要更新为2.0.

我们使用npm install来安装A的新版本,并且将更改保存到package.json文件中:

npm install mod-a@2 --save

在客户端输出如下:

现在我们的应用结构像这样 现在我们在新版本的A模块下完成这个功能,并把应用push到测试服务器,并在新的package.json文件下执行npm install

{
  "name": "example3",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "mod-a": "^2.0.0",
    "mod-c": "^1.0.0",
    "mod-d": "^1.0.0",
    "mod-e": "^1.0.0"
  }
}

测试服务器的日志输出如下: 结构如下图

这个树和开发者本机上的是不同的,为什么呢???

安装顺序很重要

当开发者使用npm install更新A1.0成2.0的时候,A2.0是最后一个安装的包,因为我们在项目启动时就执行了npm install命令,所有在package.json文件中所列的模块都已经安装在nodemodules文件夹下了,然后A2.0才安装。 因为先安装了A1.0,所以它依赖的B1.0也先安装在根目录下,以至于E1.0依赖于根目录下的B1.0.在安装C模块时,需要安装B2.0,但是因为根目录下已经存在B1.0的安装包,所以B2.0只能安装在C模块的目录下,D模块也是如此。 我们再来看一下测试服务器的情况。这个项目只有一个空目录,不存在node_modules,如果运行_npm install,则会根据package.json文件内容来安装依赖。 这时package.json文件中已经包含A2.0了,而且由于npm install安装字母顺序依次安装,所以A2.0最先安装 在一个空的node_modules目录下A2.0的依赖B2.0可以被安装在最上层目录下。 当安装E1.0时,由于node_modules目录下已经安装了B2.0,所以无法再安装B1.0了,只能嵌套在E模块下。

不同的依赖树对应用并没有影响

怎样使node_modules保持一致?

在根据package.json文件使用npm install命令安装时,会生成相同的文件树,因为安装顺序都是按照在package.json文件中的字母顺序来的。 在修改了package.json后也可以删除nodemodules目录下的所有文件,然后运行_npm install得到相同结构的依赖树。

results matching ""

    No results matching ""