2018 年了,你照样只会 npm install 吗

nodejs 社区以致 Web 前端工程化范畴发展到本日,作为 node 自带的包治理东西的 npm 已经成为每一个前端开辟者必备的东西。然则现实状态是,我们很多人对这个nodejs基础设施的运用和相识还停留在: 会用 npm install 这里(一言不合就删除全部 node_modules 目次然后从新 install 这类事你没做过吗?)

固然 npm 能成为如今天下上最大局限的包治理体系,很大程度上确切归功于它充足用户友爱,你看纵然我只会实行 install 也没必要太忧郁出什么大岔子. 然则 npm 的功用远不止于 install 一下那末简朴,这篇文章帮你扒一扒那些你能够不晓得的 npm 道理、特征、技能,以及(我以为的)最好实践。

你懒得读的 npm 文档,我帮你翻译然后实验整顿过来了 ???

1. npm init

我们都晓得 package.json 文件是用来定义一个 package 的形貌文件, 也晓得npm init 敕令用来初始化一个简朴的 package.json 文件,实行该敕令后终端会顺次讯问 name, version, description 等字段。

1.1 npm init 实行默许行动

而如果想要偷懒步免除一向按 enter,在敕令后追加 –yes 参数即可,其作用与一同下一步雷同。

npm init --yes

1.2 自定义 npm init 行动

npm init 敕令的道理并不庞杂,挪用剧本,输出一个初始化的 package.json 文件就是了。所以响应地,定制 npm init 敕令的完成体式格局也很简朴,在 Home 目次建立一个 .npm-init.js 即可,该文件的 module.exports 即为 package.json 设置内容,须要猎取用户输入时刻,运用 prompt() 要领即可。

比方编写如许的 ~/.npm-init.js

const desc = prompt('description?', 'A new package...')
const bar = prompt('bar?', '')
const count = prompt('count?', '42')

module.exports = {
  key: 'value',
  foo: {
    bar: bar,
    count: count
  },
  name: prompt('name?', process.cwd().split('/').pop()),
  version: prompt('version?', '0.1.0'),
  description: desc,
  main: 'index.js',
}

此时在 ~/hello 目次下实行 npm init 将会取得如许的 package.json:

{
  "key": "value",
  "foo": {
    "bar": "",
    "count": "42"
  },
  "name": "hello",
  "version": "0.1.0",
  "description": "A new package...",
  "main": "index.js"
}

除了天生 package.json, 因为 .npm-init.js 是一个通例的模块,意味着我们能够实行随意什么 node 剧本能够实行的使命。比方经过历程 fs 建立 README, .eslintrc 等项目必需文件,完成项目脚手架的作用。

2. 依靠包装置

依靠治理是 npm 的中心功用,道理就是实行 npm install 从 package.json 中的 dependencies, devDependencies 将依靠包装置到当前目次的 ./node_modules 文件夹中。

2.1 package定义

我们都晓得要手动装置一个包时,实行 npm install <package> 敕令即可。这里的第三个参数 package 平常就是我们所要装置的包名,默许设置下 npm 会从默许的源 (Registry) 中查找该包名对应的包地点,并下载装置。但在 npm 的天下里,除了简朴的指定包名, package 还能够是一个指向有用包名的 http url/git url/文件夹途径。

浏览 npm的文档, 我们会发明package 准确的定义,只需相符以下 a) 到 g) 个中之一条件,就是一个 package:

#申明例子
a)一个包括了顺序和形貌该顺序的 package.json 文件 的 文件夹./local-module/
b)一个包括了 (a) 的 gzip 压缩文件./module.tar.gz
c)一个能够下载取得 (b) 资本的 url (平常是 http(s) url)https://registry.npmjs.org/we…
d)一个花样为 <name>@<version> 的字符串,可指向 npm 源(平常是官方源 npmjs.org)上已宣布的可接见 url,且该 url 满足条件 (c)webpack@4.1.0
e)一个花样为 <name>@<tag> 的字符串,在 npm 源上该<tag>指向某 <version> 取得 <name>@<version>,后者满足条件 (d)webpack@latest
f)一个花样为 <name> 的字符串,默许增加 latest 标签所取得的 <name>@latest 满足条件 (e)webpack
g)一个 git url, 该 url 所指向的代码库满足条件 (a)git@github.com:webpack/webpack.git

2.2 装置当地包/长途git堆栈包

上面表格的定义意味着,我们在同享依靠包时,并不黑白要将包宣布到 npm 源上才能够供应给运用者来装置。这关于私有的不轻易 publish 到长途源(纵然是私有源),或许须要对某官方源举行革新,但依旧须要把包同享出去的场景来讲异常有用。

场景1: 当地模块援用

nodejs 运用开辟中不可避免有模块间挪用,比方在实践中常常会把须要被频仍援用的设置模块放到运用根目次;因而在建立了很多层级的目次、文件后,极能够会碰到如许的代码:

const config = require('../../../../config.js');

除了看上去很丑以外,如许的途径援用也不利于代码的重构。而且身为顺序员的自我教养通知我们,如许反复的代码多了也就意味着是时刻把这个模块分离出来供运用内其他模块同享了。比方这个例子里的 config.js 异常合适封装为 package 放到 node_modules 目次下,同享给同运用内其他模块。

无需手动拷贝文件或许建立软链接到 node_modules 目次,npm 有更文雅的处理计划。

计划:

  1. 建立 config 包:
    新增 config 文件夹; 重命名 config.js 为 config/index.js 文件; 建立 package.json 定义 config 包

    {
        "name": "config",
        "main": "index.js",
        "version": "0.1.0"
    }
  2. 在运用层 package.json 文件中新增依靠项,然后实行 npm install; 或直接实行第 3 步

    {
        "dependencies": {
            "config": "file:./config"
        }
    }
  3. (等价于第 2 步)直接在运用目次实行 npm install file:./config

    此时,检察 node_modules 目次我们会发明多出来一个名为 config,指向上层 config/ 文件夹的软链接。这是因为 npm 辨认 file: 协定的url,得知这个包须要直接从文件体系中猎取,会自动建立软链接到 node_modules 中,完成“装置”历程。

    比拟手动软链,我们既不须要体贴 windows 和 linux 敕令差别,又能够显式地将依靠信息固化到 dependencies 字段中,开辟团队其他成员能够实行 npm install 后直接运用。

场景2: 私有 git 同享 package

有些时刻,我们一个团队内会有一些代码/公用库须要在团队内差别项目间同享,但能够因为包括了敏感内容,或许代码太烂拿不出手等缘由,不轻易宣布到源。

这类状态下,我们能够简朴地将被依靠的包托管在私有的 git 堆栈中,然后将该 git url 保留到 dependencies 中. npm 会直接挪用体系的 git 敕令从 git 堆栈拉取包的内容到 node_modules 中。

npm 支撑的 git url 花样:

<protocol>://[<user>[:<password>]@]<hostname>[:<port>][:][/]<path>[#<commit-ish> | #semver:<semver>]

git 途径后能够运用 # 指定特定的 git branch/commit/tag, 也能够 #semver: 指定特定的 semver range.

比方:

git+ssh://git@github.com:npm/npm.git#v1.0.27
git+ssh://git@github.com:npm/npm#semver:^5.0
git+https://isaacs@github.com/npm/npm.git
git://github.com/npm/npm.git#v1.0.27

场景3: 开源 package 题目修复

运用某个 npm 包时发明它有某个严峻bug,但或许最初作者已不再保护代码了,或许我们事变紧要,没有充足的时候提 issue 给作者再逐步等作者宣布新的修复版本到 npm 源。

此时我们能够手动进入 node_modules 目次下修正响应的包内容,或许修正了一行代码就修复了题目。然则这类做法异常不明智!

起首 node_modules 本身不应该放进版本掌握体系,对 node_modules 文件夹中内容的修正不会被纪录进 git 提交纪录;其次,就算我们非要反情势,把 node_modules 放进版本掌握中,你的修正内容也很轻易在下次 team 中某位成员实行 npm installnpm update 时被掩盖,而如许的一次提交极能够包括了几十几百个包的更新,你本身所做的修正很轻易就被淹没在巨大的 diff 文件列表中了。

计划:

最好的方法应该是 fork 原作者的 git 库,在本身所属的 repo 下修复题目后,将 dependencies 中响应的依靠项变动成本身修复后版本的 git url 即可处理题目。(Fork 代码库后,也便于向原作者提交 PR 修复题目。上游代码库修复题目后,再次更新我们的依靠设置也不迟。)

3. npm install 怎样事变 —— node_modules 目次构造

npm install 实行终了后,我们能够在 node_modules 中看到一切依靠的包。虽然运用者无需关注这个目次里的文件夹构造细节,尽管在营业代码中援用依靠包即可,但相识 node_modules 的内容能够帮我们更好明白 npm 怎样事变,相识从 npm 2 到 npm 5 有哪些变化和革新。

为简朴起见,我们假定运用目次为 app, 用两个盛行的包 webpack, nconf 作为依靠包做示例申明。而且为了平常装置,运用了“上古” npm 2 时期的版本 webpack@1.15.0, nconf@0.8.5.

3.1 npm 2

npm 2 在装置依靠包时,采纳简朴的递归装置要领。实行 npm install 后,npm 2 顺次递归装置 webpacknconf 两个包到 node_modules 中。实行终了后,我们会看到 ./node_modules 这层目次只含有这两个子目次。

node_modules/
├── nconf/
└── webpack/

进入更深一层 nconf 或 webpack 目次,将看到这两个包各自的 node_modules 中,已经过 npm 递归地装置好本身的依靠包。包括 ./node_modules/webpack/node_modules/webpack-core , ./node_modules/conf/node_modules/async 等等。而每一个包都有本身的依靠包,每一个包本身的依靠都装置在了本身的 node_modules 中。依靠关联层层递进,构成了一全部依靠树,这个依靠树与文件体系中的文件构造树恰好层层对应。

最轻易的检察依靠树的体式格局是直接在 app 目次下实行 npm ls 敕令。

app@0.1.0
├─┬ nconf@0.8.5
│ ├── async@1.5.2
│ ├── ini@1.3.5
│ ├── secure-keys@1.0.0
│ └── yargs@3.32.0
└─┬ webpack@1.15.0
  ├── acorn@3.3.0
  ├── async@1.5.2
  ├── clone@1.0.3
  ├── ...
  ├── optimist@0.6.1
  ├── supports-color@3.2.3
  ├── tapable@0.1.10
  ├── uglify-js@2.7.5
  ├── watchpack@0.2.9
  └─┬ webpack-core@0.6.9
    ├── source-list-map@0.1.8
    └── source-map@0.4.4

如许的目次构造长处在于层级构造显著,便于举行傻瓜式的治理:

  1. 比方新装一个依靠包,能够立即在第一层 node_modules 中看到子目次
  2. 在已知所需包名和版本号时,以至能够从别的文件夹手动拷贝须要的包到 node_modules 文件夹中,再手动修正 package.json 中的依靠设置
  3. 要删除这个包,也能够简朴地手动删除这个包的子目次,并删除 package.json 文件中响应的一行即可

现实上,很多人在 npm 2 时期也确实都这么实践过,确实也都能够装置和删除胜利,并不会致使什么过失。

但如许的文件构造也有很显著的题目:

  1. 对庞杂的工程, node_modules 内目次构造能够会太深,致使深层的文件途径太长而触发 windows 文件体系中,文件途径不能超过 260 个字符长的毛病
  2. 部分被多个包所依靠的包,极能够在运用 node_modules 目次中的很多处所被反复装置。随着工程局限愈来愈大,依靠树愈来愈庞杂,如许的包状态会愈来愈多,形成大批的冗余。

——在我们的示例中就有这个题目,webpacknconf 都依靠 async 这个包,所以在文件体系中,webpack 和 nconf 的 node_modules 子目次中都装置了雷同的 async 包,而且是雷同的版本。

+-------------------------------------------+
|                   app/                    |
+----------+------------------------+-------+
           |                        |
           |                        |
+----------v------+       +---------v-------+
|                 |       |                 |
|  webpack@1.15.0 |       |  nconf@0.8.5    |
|                 |       |                 |
+--------+--------+       +--------+--------+
         |                         |
   +-----v-----+             +-----v-----+
   |async@1.5.2|             |async@1.5.2|
   +-----------+             +-----------+

3.2 npm 3 – 扁平构造

主要为相识决以上题目,npm 3 的 node_modules 目次改成了越发扁平状的层级构造。文件体系中 webpack, nconf, async 的层级关联变成了平级关联,处于一致级目次中。

         +-------------------------------------------+
         |                   app/                    |
         +-+---------------------------------------+-+
           |                                       |
           |                                       |
+----------v------+    +-------------+   +---------v-------+
|                 |    |             |   |                 |
|  webpack@1.15.0 |    | async@1.5.2 |   |  nconf@0.8.5    |
|                 |    |             |   |                 |
+-----------------+    +-------------+   +-----------------+

虽然如许一来 webpack/node_modules 和 nconf/node_modules 中都不再有 async 文件夹,但得益于 node 的模块加载机制,他们都能够在上一级 node_modules 目次中找到 async 库。所以 webpack 和 nconf 的库代码中 require('async') 语句的实行都不会有任何题目。

这只是最简朴的例子,现实的工程项目中,依靠树不可避免地会有很多层级,很多依靠包,个中会有很多同名但版本差别的包存在于差别的依靠层级,对这些庞杂的状态, npm 3 都邑在装置时遍历全部依靠树,计算出最合理的文件夹装置体式格局,使得一切被反复依靠的包都能够去重装置。

npm 文档供应了更直观的例子诠释这类状态:

如果
package{dep} 写法代表包和包的依靠,那末
A{B,C},
B{C},
C{D} 的依靠构造在装置以后的 node_modules 是如许的构造:

A
+-- B
+-- C
+-- D

这里之所以 D 也装置到了与 B C 一致级目次,是因为 npm 会默许会在无争执的条件下,尽能够将包装置到较高的层级。

如果是
A{B,C},
B{C,D@1},
C{D@2} 的依靠关联,取得的装置后构造是:

A
+-- B
+-- C
   `-- D@2
+-- D@1

这里是因为,关于 npm 来讲同名但差别版本的包是两个自力的包,而同层不能有两个同名子目次,所以个中的 D@2 放到了 C 的子目次而另一个 D@1 被放到了再上一层目次。

很显著在 npm 3 以后 npm 的依靠树构造不再与文件夹层级一一对应了。想要检察 app 的直接依靠项,要经过历程 npm ls 敕令指定 --depth 参数来检察:

npm ls --depth 1

PS: 与当地依靠包差别,如果我们经过历程
npm install --global 全局装置包到全局目次时,取得的目次依旧是“传统的”目次构造。而如果运用 npm 3 想要取得“传统”情势的当地 node_modules 目次,运用
npm install --global-style 敕令即可。

3.3 npm 5 – package-lock 文件

npm 5 宣布于 2017 年也是现在最新的 npm 版本,这一版本依旧相沿 npm 3 以后扁平化的依靠包装置体式格局,另外最大的变化是增加了 package-lock.json 文件。

package-lock.json 的作用是锁定依靠装置构造,如果检察这个 json 的构造,会发明与 node_modules 目次的文件层级构造是一一对应的。

以依靠关联为: app{webpack} 的 ‘app’ 项目为例, 其 package-lock 文件包括了如许的片断。

{
    "name":  "app",
    "version":  "0.1.0",
    "lockfileVersion":  1,
    "requires":  true,
    "dependencies": {
        // ... 其他依靠包
        "webpack": {
            "version": "1.8.11",
            "resolved": "https://registry.npmjs.org/webpack/-/webpack-1.8.11.tgz",
            "integrity": "sha1-Yu0hnstBy/qcKuanu6laSYtgkcI=",
            "requires": {
                "async": "0.9.2",
                "clone": "0.1.19",
                "enhanced-resolve": "0.8.6",
                "esprima": "1.2.5",
                "interpret": "0.5.2",
                "memory-fs": "0.2.0",
                "mkdirp": "0.5.1",
                "node-libs-browser": "0.4.3",
                "optimist": "0.6.1",
                "supports-color": "1.3.1",
                "tapable": "0.1.10",
                "uglify-js": "2.4.24",
                "watchpack": "0.2.9",
                "webpack-core": "0.6.9"
            }
        },
        "webpack-core": {
            "version": "0.6.9",
            "resolved": "https://registry.npmjs.org/webpack-core/-/webpack-core-0.6.9.tgz",
            "integrity": "sha1-/FcViMhVjad76e+23r3Fo7FyvcI=",
            "requires": {
                "source-list-map": "0.1.8",
                "source-map": "0.4.4"
            },
            "dependencies": {
                "source-map": {
                    "version": "0.4.4",
                    "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
                    "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
                    "requires": {
                        "amdefine": "1.0.1"
                    }
                }
            }
        },
        //... 其他依靠包
    }
}

看懂 package-lock 文件并不难,其构造是一样范例的几个字段嵌套起来的,重如果 version, resolved, integrity, requires, dependencies 这几个字段罢了。

  • version, resolved, integrity 用来纪录包的准确版本号、内容hash、装置源的,决议了要装置的包的准确“身份”信息
  • 假定挡住其他字段,只关注文件中的 dependencies: {} 我们会发明,全部文件的 JSON 设置里的 dependencies 条理构造与文件体系中 node_modules 的文件夹条理构造是完整对照的
  • 只关注 requires: {} 字段又会发明,除最外层的 requires 属性为 true 以外, 其他层的 requires 属性都对应着这个包的 package.json 里纪录的本身的依靠项

因为这个文件纪录了 node_modules 里一切包的构造、层级和版本号以至装置源,它也就事实上供应了 “保留” node_modules 状态的才能。只需有如许一个 lock 文件,不论在那一台机械上实行 npm install 都邑取得完整雷同的 node_modules 结果。

这就是 package-lock 文件致力于优化的场景:在夙昔仅仅用 package.json 纪录依靠,因为 semver range 的机制;一个月前由 A 天生的 package.json 文件,B 在一个月后依据它实行 npm install 所取得的 node_modules 结果极能够很多包都存在差别的差别,虽然 semver 机制的限定使得一致份 package.json 不会取得大版本差别的依靠包,但一致份代码在差别环境装置出差别的依靠包,依旧是能够致使不测的潜伏要素。

雷同作用的文件在 npm 5 之前就有,称为 npm shrinkwrap 文件,两者作用完整雷同,差别的是后者须要手动天生,而 npm 5 默许会在实行 npm install 后就天生 package-lock 文件,而且发起你提交到 git/svn 代码库中。

package-lock.json 文件在最初 npm 5.0 默许引入时也引起了相当大的争议。在 npm 5.0 中,如果已有 package-lock 文件存在,若手动在 package.json 文件新增一条依靠,再实行 npm install, 新增的依靠并不会被装置到 node_modules 中, package-lock.json 也不会做响应的更新。如许的表现与运用者的天然希冀表现不符。在 npm 5.1 的首个 Release 版本中这个题目得以修复。这个事变通知我们,要晋级,不要运用 5.0。

——但依旧有阻挡的声响以为 package-lock 太庞杂,对此 npm 也供应了禁用设置:

npm config set package-lock false

4. 依靠包版本治理

依靠包装置完并不意味着就高枕无忧了,版本的保护和更新也很主要。这一章引见依靠包晋级治理相干学问,太长不看版本请直接跳到 4.3 最好实践

4.1 semver

npm 依靠治理的一个主要特征是采纳了语义化版本 (semver) 范例,作为依靠版本治理计划。

semver 商定一个包的版本号必需包括3个数字,花样必需为 MAJOR.MINOR.PATCH, 意为 主版本号.小版本号.订正版本号.

  • MAJOR 对应大的版本号迭代,做了不兼容旧版的修正时要更新 MAJOR 版本号
  • MINOR 对应小版本迭代,发作兼容旧版API的修正或功用更新时,更新MINOR版本号
  • PATCH 对应订正版本号,平常针对修复 BUG 的版本号

关于包作者(宣布者),npm 请求在 publish 之前,必需更新版本号。npm 供应了 npm version 东西,实行 npm version major|minor|patch 能够简朴地将版本号中响应的数字加1.

如果包是一个 git 堆栈,
npm version 还会自动建立一条解释为更新后版本号的 git commit 和名为该版本号的 tag

关于包的援用者来讲,我们须要在 dependencies 中运用 semver 商定的 semver range 指定所需依靠包的版本号或版本局限。npm 供应了网站 https://semver.npmjs.com 可轻易地计算所输入的表达式的婚配局限。经常使用的划定规矩示比方下表:

range寄义
^2.2.1指定的 MAJOR 版本号下, 一切更新的版本婚配 2.2.3, 2.3.0; 不婚配 1.0.3, 3.0.1
~2.2.1指定 MAJOR.MINOR 版本号下,一切更新的版本婚配 2.2.3, 2.2.9 ; 不婚配 2.3.0, 2.4.5
>=2.1版本号大于或即是 2.1.0婚配 2.1.2, 3.1
<=2.2版本号小于或即是 2.2婚配 1.0.0, 2.2.1, 2.2.11
1.0.0 - 2.0.0版本号从 1.0.0 (含) 到 2.0.0 (含)婚配 1.0.0, 1.3.4, 2.0.0

恣意两条划定规矩,经过历程 || 连接起来,则示意两条划定规矩的并集:

^2 >=2.3.1 || ^3 >3.2 能够婚配:

* `2.3.1`, `2,8.1`, `3.3.1`
* 但不婚配 `1.0.0`, `2.2.0`, `3.1.0`, `4.0.0`

PS: 除了这几种,另有以下更直观的示意版本号局限的写法:

  • *x 婚配一切主版本
  • 11.x 婚配 主版本号为 1 的一切版本
  • 1.21.2.x 婚配 版本号为 1.2 开首的一切版本

PPS: 在通例仅包括数字的版本号以外,semver 还许可在 MAJOR.MINOR.PATCH 后追加 - 后跟点号分开的标签,作为预宣布版本标签 – Prerelese Tags,平常被视为不稳定、不发起临盆运用的版本。比方:

  • 1.0.0-alpha
  • 1.0.0-beta.1
  • 1.0.0-rc.3

上表中我们最罕见的是 ^1.8.11 这类花样的 range, 因为我们在运用 npm install <package name> 装置包时,npm 默许装置当前最新版本,比方 1.8.11, 然后在所装置的版本号前加^号, 将 ^1.8.11 写入 package.json 依靠设置,意味着能够婚配 1.8.11 以上,2.0.0 以下的一切版本。

4.2 依靠版本晋级

题目来了,在装置完一个依靠包以后有新版本宣布了,怎样运用 npm 举行版本晋级呢?——答案是简朴的 npm installnpm update,但在差别的 npm 版本,差别的 package.json, package-lock.json 文件,装置/晋级的表现也差别。

我们无妨还以 webpack 举例,做以下的条件假定:

  • 我们的工程项目 app 依靠 webpack
  • 项目最初初始化时,装置了当时最新的包 webpack@1.8.0,而且 package.json 中的依靠设置为: "webpack": "^1.8.0"
  • 当前(2018年3月) webpack 最新版本为 4.2.0, webpack 1.x 最新子版本为 1.15.0

如果我们运用的是 npm 3, 而且项目不含 package-lock.json, 那末依据 node_modules 是不是为空,实行 install/update 的结果以下 (node 6.13.1, npm 3.10.10 环境下实验):

#package.json (BEFORE)node_modules (BEFORE)command (npm 3)package.json (AFTER)node_modules (AFTER)
a)webpack: ^1.8.0webpack@1.8.0installwebpack: ^1.8.0webpack@1.8.0
b)webpack: ^1.8.0installwebpack: ^1.8.0webpack@1.15.0
c)webpack: ^1.8.0webpack@1.8.0updatewebpack: ^1.8.0webpack@1.15.0
d)webpack: ^1.8.0updatewebpack: ^1.8.0webpack@1.15.0

依据这个表我们能够对 npm 3 得出以下结论:

  • 如果当地 node_modules 已装置,再次实行 install 不会更新包版本, 实行 update 才会更新; 而如果当地 node_modules 为空时,实行 install/update 都邑直接装置更新包;
  • npm update 老是会把包更新到相符 package.json 中指定的 semver 的最新版本号——本例中相符 ^1.8.0 的最新版本为 1.15.0
  • 一旦给定 package.json, 不管背面实行 npm install 照样 update, package.json 中的 webpack 版本一向固执地坚持 一最先的 ^1.8.0 纹丝不动

这里不合理的处所在于,如果最最先团队中第一个人装置了 webpack@1.8.0, 而新到场项目标成员, checkout 工程代码后实行 npm install 会装置取得不太一样的 1.15.0 版本。虽然 semver 商定了小版本号应该坚持向下兼容(雷同大版本号下的小版本号)兼容,但万一有不熟悉不遵照此商定的包宣布者,宣布了不兼容的包,此时就能够出现因依靠环境差别致使的 bug。

下面由 npm 5 带着 package-lock.json 闪亮上台,实行 install/update 的结果是如许的 (node 9.8.0, npm 5.7.1 环境下实验):

下表为表述简朴,省略了包名 webpack, install 简写 i, update 简写为 up

#package.json (BEFORE)node_modules (BEFORE)package-lock (BEFORE)commandpackage.json (AFTER)node_modules (AFTER)
a)^1.8.0@1.8.0@1.8.0i^1.8.0@1.8.0
b)^1.8.0@1.8.0i^1.8.0@1.8.0
c)^1.8.0@1.8.0@1.8.0up^1.15.0@1.15.0
d)^1.8.0@1.8.0up^1.8.0@1.15.0
e)^1.15.0@1.8.0 (旧)@1.15.0i^1.15.0@1.15.0
f)^1.15.0@1.8.0 (旧)@1.15.0up^1.15.0@1.15.0

与 npm 3 比拟,在装置和更新依靠版本上主要的区别为:

  • 不管什么时候实行 install, npm 都邑优先根据 package-lock 中指定的版本来装置 webpack; 避免了 npm 3 表中情况 b) 的状态;
  • 不管什么时候完成装置/更新, package-lock 文件总会随着 node_modules 更新 —— (因而能够视 package-lock 文件为 node_modules 的 JSON 表述)
  • 已装置 node_modules 后若实行 npm update,package.json 中的版本号也会随之变动成 ^1.15.0

因而可知 npm 5.1 使得 package.json 和 package-lock.json 中所保留的版本号越发一致,处理了 npm 之前的种种题目。只需遵照好的实践习气,团队成员能够很轻易地保护一套运用代码和 node_modules 依靠都一致的环境。

大快人心。

4.3 最好实践

总结起来,在 2018 年 (node 9.8.0, npm 5.7.1) 时期,我以为的依靠版本治理应该是:

  • 运用 npm: >=5.1 版本, 坚持 package-lock.json 文件默许开启设置
  • 初始化:第一作者初始化项目时运用 npm install <package> 装置依靠包, 默许保留 ^X.Y.Z 依靠 range 到 package.json中; 提交 package.json, package-lock.json, 不要提交 node_modules 目次
  • 初始化:项目成员初次 checkout/clone 项目代码后,实行一次 npm install 装置依靠包
  • 不要手动修正 package-lock.json
  • 晋级依靠包:

    • 晋级小版本: 当地实行 npm update 晋级到新的小版本
    • 晋级大版本: 当地实行 npm install <package-name>@<version> 晋级到新的大版本
    • 也可手动修正 package.json 中版本号为要晋级的版本(大于现有版本号)并指定所需的 semver, 然后实行 npm install
    • 当地考证晋级后新版本无题目后,提交新的 package.json, package-lock.json 文件
  • 降级依靠包:

    • 准确: npm install <package-name>@<old-version> 考证无题目后,提交 package.json 和 package-lock.json 文件
    • 毛病: 手动修正 package.json 中的版本号为更低版本的 semver, 如许修正并不会见效,因为再次实行 npm install 依旧会装置 package-lock.json 中的锁定版本
  • 删除依靠包:

    • Plan A: npm uninstall <package> 并提交 package.jsonpackage-lock.json
    • Plan B: 把要卸载的包从 package.json 中 dependencies 字段删除, 然后实行 npm install 并提交 package.jsonpackage-lock.json
  • 任什么时候刻有人提交了 package.json, package-lock.json 更新后,团队其他成员应在 svn update/git pull 拉取更新后实行 npm install 剧本装置更新后的依靠包

祝贺你终究能够跟 rm -rf node_modules && npm install 这波操纵说拜拜了(实在并不会)

5. npm scripts

5.1 基础运用

npm scripts 是 npm 另一个很主要的特征。经过历程在 package.json 中 scripts 字段定义一个剧本,比方:

{
    "scripts": {
        "echo": "echo HELLO WORLD"
    }
}

我们就能够经过历程 npm run echo 敕令来实行这段剧本,像在 shell 中实行该敕令 echo HELLO WORLD 一样,看到终端输出 HELLO WORLD.

—— npm scripts 的基础运用就是这么简朴,它供应了一个简朴的接口用来挪用工程相干的剧本。关于更细致的相干信息,能够参考阮一峰先生的文章 npm script 运用指南 (2016年10月).

扼要总结阮先生文章内容:

  1. npm run 敕令实行时,会把 ./node_modules/.bin/ 目次增加到实行环境的 PATH 变量中,因而如果某个敕令行包未全局装置,而只装置在了当前项目标 node_modules 中,经过历程 npm run 一样能够挪用该敕令。
  2. 实行 npm 剧本时要传入参数,须要在敕令后加 -- 标明, 如 npm run test -- --grep="pattern" 能够将 --grep="pattern" 参数传给 test 敕令
  3. npm 供应了 pre 和 post 两种钩子机制,能够定义某个剧本前后的实行剧本
  4. 运转时变量:在 npm run 的剧本实行环境内,能够经过历程环境变量的体式格局猎取很多运转时相干信息,以下都能够经过历程 process.env 对象接见取得:

    • npm_lifecycle_event – 正在运转的剧本称号
    • npm_package_<key> – 猎取当前包 package.json 中某个字段的设置值:如 npm_package_name 猎取包名
    • npm_package_<key>_<sub-key> – package.json 中嵌套字段属性:如 npm_pacakge_dependencies_webpack 能够猎取到 package.json 中的 dependencies.webpack 字段的值,即 webpack 的版本号

5.2 node_modules/.bin 目次

上面所说的 node_modules/.bin 目次,保留了依靠目次中所装置的可供挪用的敕令行包。

何谓敕令行包?比方 webpack 就属于一个敕令行包。如果我们在装置 webpack 时增加 --global 参数,就能够在终端直接输入 webpack 举行挪用。但如果不加 --global 参数,我们会在 node_modules/.bin 目次里看到名为 webpack 的文件,如果在终端直接输入 ./node_modules/.bin/webpack 敕令,一样能够实行。

这是因为 webpackpackage.json 文件中定义了 bin 字段为:

{
    "bin": {
        "webpack": "./bin/webpack.js"
    }
}

bin 字段的设置花样为: <command>: <file>, 即 敕令名: 可实行文件. npm 实行 install 时,会剖析每一个依靠包的 package.json 中的 bin 字段,并将其包括的条目装置到 ./node_modules/.bin 目次中,文件名为 <command>。而如果是全局情势装置,则会在 npm 全局装置途径的 bin 目次下建立指向 <file> 名为 <command> 的软链。因而,./node_modules/.bin/webpack 文件在经过历程敕令行挪用时,现实上就是在实行 node ./node_modules/.bin/webpack.js 敕令。

正如上一节所说,npm run 敕令在实行时会把 ./node_modules/.bin 到场到 PATH 中,使我们可直接挪用一切供应了敕令行挪用接口的依靠包。所以这里就引出了一个最好实践:

将项目依靠的敕令行东西装置到项目依靠文件夹中,然后经过历程 npm scripts 挪用;而非全局装置

举例而言 webpack 作为前端工程标配的构建东西,虽然我们都习气了全局装置并直接运用敕令行挪用,但差别的项目依靠的 webpack 版本能够差别,响应的 webpack.config.js 设置文件也能够只兼容了特定版本的 webpack. 如果我们仅全局装置了最新的 webpack 4.x 并运用 webpack 敕令挪用,在一个依靠 webpack 3.x 的工程中就会没法胜利实行构建。

但如果这类东西老是当地装置,我们要挪用一个敕令,要手动增加 ./node_modules/.bin 这个长长的前缀,难免也太麻烦了,我们 nodejs 开辟者都很懒的。因而 npm 从5.2 最先自带了一个新的东西 npx.

5.3 npx

npx 的运用很简朴,就是实行 npx <command> 即可,这里的 <command> 默许就是 ./node_modules 目次中装置的可实行剧本名。比方上面当地装置好的 webpack 包,我们能够直接运用 npx webpack 实行即可。

除了这类最简朴的场景, npm cli 团队开辟者 Kat Marchán 还在这篇文章中引见了其他几种 npx 的奇异用法: Introducing npx: an npm package runner, 国内有位开辟者 robin.law 将原文翻译为中文 npx是什么,为何须要npx?.

有兴致的能够戳链接相识,懒得点链接的,看总结:

场景a) 一键实行长途 npm 源的二进制包

除了在 package 中实行 ./node_modules/.bin 中已装置的敕令, 还能够直接指定未装置的二进制包名实行。比方我们在一个没有 package.json 也没有 node_modules 的目次下,实行:

npx cowsay hello

npx 将会从 npm 源下载 cowsay 这个包(但并不装置)并实行:

 _______ 
< hello >
 ------- 
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

这类用处异常合适 1. 在当地简朴测试或调试 npm 源上这些二进制包的功用;2. 挪用 create-react-app 或 yeoman 这类每每每一个项目只须要运用一次的脚手架东西

PS: 此处有彩蛋,实行这条敕令尝尝:

npx workin-hard

场景b) 一键实行 GitHub Gist

还记得前面提到的 2.1 package定义 么,npm install <package> 能够是包括了有用 package.json 的 git url.

恰好 GitHub Gist 也是 git 堆栈 的一种,鸠合 npx 就能够轻易地将简朴的剧本同享给其他人,具有该链接的人无需将剧本装置到当地事变目次即可实行。将 package.json 和 需实行的二进制剧本上传至 gist, 在运转 npx <gist url> 就能够轻易地实行该 gist 定义的敕令。

原文作者 Kat Marchán 供应了这个示例 gist, 实行:

npx https://gist.github.com/zkat/4bc19503fe9e9309e2bfaa2c58074d32

可取得一个来自 GitHubGist 的 hello world 问候。

场景c) 运用差别版本 node 实行敕令

将 npx 与 Aria Stewart 建立的 node 包 (https://www.npmjs.com/package… 连系,能够实如今一行敕令中运用指定版本的 node 实行敕令。

比方前后实行:

npx node@4 -e "console.log(process.version)"
npx node@6 -e "console.log(process.version)"

将离别输出 v4.8.7v6.13.0.

平常这类事变是由 nvm 这类 node 版本治理东西来做的,但 npx node@4 这类体式格局免除 nvm 手动切换设置的步骤,越发简约简朴。

6. npm 设置

6.1 npm config

npm cli 供应了 npm config 敕令举行 npm 相干设置,经过历程 npm config ls -l 可检察 npm 的一切设置,包括默许设置。npm 文档页为每一个设置项供应了细致的申明 https://docs.npmjs.com/misc/c… .

修正设置的敕令为 npm config set <key> <value>, 我们运用相干的罕见主要设置:

  • proxy, https-proxy: 指定 npm 运用的代办
  • registry 指定 npm 下载装置包时的源,默以为 https://registry.npmjs.org/ 能够指定为私有 Registry 源
  • package-lock 指定是不是默许天生 package-lock 文件,发起坚持默许 true
  • save true/false 指定是不是在 npm install 后保留包为 dependencies, npm 5 起默以为 true

删除指定的设置项敕令为 npm config delete <key>.

6.2 npmrc 文件

除了运用 CLI 的 npm config 敕令显现变动 npm 设置,还能够经过历程 npmrc 文件直接修正设置。

如许的 npmrc 文件优先级由高到低包括:

  • 工程内设置文件: /path/to/my/project/.npmrc
  • 用户级设置文件: ~/.npmrc
  • 全局设置文件: $PREFIX/etc/npmrc (即npm config get globalconfig 输出的途径)
  • npm内置设置文件: /path/to/npm/npmrc

经过历程这个机制,我们能够轻易地在工程跟目次建立一个 .npmrc 文件来同享须要在团队间同享的 npm 运转相干设置。比方如果我们在公司内网环境下需经过历程代办才可接见 registry.npmjs.org 源,或需接见内网的 registry, 就能够在事变项面前目今新增 .npmrc 文件并提交代码库。

proxy = http://proxy.example.com/
https-proxy = http://proxy.example.com/
registry = http://registry.example.com/

因为项目级 .npmrc 文件的作用域只在本项面前目今,所以在非本目次下,这些设置并不见效。关于运用笔记本事变的开辟者,能够很好地断绝公司的事变项目、在家进修研讨项目两种差别的环境。

将这个功用与 ~/.npm-init.js 设置相连系,能够将特定设置的 .npmrc 跟 .gitignore, README 之类文件一同做到 npm init 脚手架中,进一步削减手动设置。

6.3 node 版本束缚

虽然一个项目标团队都同享了雷同的代码,但每一个人的开辟机械能够装置了差别的 node 版本,另外服务器端的也能够与当地开辟机不一致。

这又是一个能够带来不一致性的要素 —— 但也不是很难处理,声明式束缚+剧本限定即可。

声明:经过历程 package.jsonengines 属性声明运用运转所需的版本运转时请求。比方我们的项目中运用了 async, await 特征,查阅兼容性表格得知最低支撑版本为 7.6.0,因而指定 engines 设置为:

{
    "engines": { "node": ">=7.6.0"}
}

强束缚(可选):在 npm 中以上字段内容仅作为发起字段运用,若要在私有项目中增增强束缚,须要本身写剧本钩子,读取并剖析 engines 字段的 semver range 并与运转时环境做对照校验并恰当提示。

7. 小结 npm 最好实践

  • 运用 npm-init 初始化新项目
  • 一致项目设置: 需团队同享的 npm config 设置项,固化到 .npmrc 文件中
  • 一致运转环境,一致 package.json,一致 package-lock 文件
  • 合理运用多样化的源装置依靠包: npm install <git url>|<local file>
  • 运用 npm: >=5.2 版本
  • 运用 npm scripts 与 npx (npm: >=5.2) 剧本治理运用相干剧本

8. 更多材料

参考

文档

延长浏览

    原文作者:rianma
    原文地址: https://segmentfault.com/a/1190000013962514
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞