成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

2018 年了,你還是只會 npm install 嗎

libxd / 2009人閱讀

摘要:無需手動拷貝文件或者創(chuàng)建軟鏈接到目錄,有更優(yōu)雅的解決方案。這是因為識別協(xié)議的,得知這個包需要直接從文件系統(tǒng)中獲取,會自動創(chuàng)建軟鏈接到中,完成安裝過程。

nodejs 社區(qū)乃至 Web 前端工程化領(lǐng)域發(fā)展到今天,作為 node 自帶的包管理工具的 npm 已經(jīng)成為每個前端開發(fā)者必備的工具。但是現(xiàn)實狀況是,我們很多人對這個nodejs基礎(chǔ)設(shè)施的使用和了解還停留在: 會用 npm install 這里(一言不合就刪除整個 node_modules 目錄然后重新 install 這種事你沒做過嗎?)

當(dāng)然 npm 能成為現(xiàn)在世界上最大規(guī)模的包管理系統(tǒng),很大程度上確實歸功于它足夠用戶友好,你看即使我只會執(zhí)行 install 也不必太擔(dān)心出什么大岔子. 但是 npm 的功能遠(yuǎn)不止于 install 一下那么簡單,這篇文章幫你扒一扒那些你可能不知道的 npm 原理、特性、技巧,以及(我認(rèn)為的)最佳實踐。

你懶得讀的 npm 文檔,我?guī)湍惴g然后試驗整理過來了 ???

1. npm init

我們都知道 package.json 文件是用來定義一個 package 的描述文件, 也知道npm init 命令用來初始化一個簡單的 package.json 文件,執(zhí)行該命令后終端會依次詢問 name, version, description 等字段。

1.1 npm init 執(zhí)行默認(rèn)行為


而如果想要偷懶步免去一直按 enter,在命令后追加 --yes 參數(shù)即可,其作用與一路下一步相同。

npm init --yes

1.2 自定義 npm init 行為

npm init 命令的原理并不復(fù)雜,調(diào)用腳本,輸出一個初始化的 package.json 文件就是了。所以相應(yīng)地,定制 npm init 命令的實現(xiàn)方式也很簡單,在 Home 目錄創(chuàng)建一個 .npm-init.js 即可,該文件的 module.exports 即為 package.json 配置內(nèi)容,需要獲取用戶輸入時候,使用 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 目錄下執(zhí)行 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 是一個常規(guī)的模塊,意味著我們可以執(zhí)行隨便什么 node 腳本可以執(zhí)行的任務(wù)。例如通過 fs 創(chuàng)建 README, .eslintrc 等項目必需文件,實現(xiàn)項目腳手架的作用。

2. 依賴包安裝

依賴管理是 npm 的核心功能,原理就是執(zhí)行 npm install 從 package.json 中的 dependencies, devDependencies 將依賴包安裝到當(dāng)前目錄的 ./node_modules 文件夾中。

2.1 package定義

我們都知道要手動安裝一個包時,執(zhí)行 npm install 命令即可。這里的第三個參數(shù) package 通常就是我們所要安裝的包名,默認(rèn)配置下 npm 會從默認(rèn)的源 (Registry) 中查找該包名對應(yīng)的包地址,并下載安裝。但在 npm 的世界里,除了簡單的指定包名, package 還可以是一個指向有效包名的 http url/git url/文件夾路徑。

閱讀 npm的文檔, 我們會發(fā)現(xiàn)package 準(zhǔn)確的定義,只要符合以下 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) 一個格式為 @ 的字符串,可指向 npm 源(通常是官方源 npmjs.org)上已發(fā)布的可訪問 url,且該 url 滿足條件 (c) webpack@4.1.0
e) 一個格式為 @ 的字符串,在 npm 源上該指向某 得到 @,后者滿足條件 (d) webpack@latest
f) 一個格式為 的字符串,默認(rèn)添加 latest 標(biāo)簽所得到的 @latest 滿足條件 (e) webpack
g) 一個 git url, 該 url 所指向的代碼庫滿足條件 (a) git@github.com:webpack/webpack.git
2.2 安裝本地包/遠(yuǎn)程git倉庫包

上面表格的定義意味著,我們在共享依賴包時,并不是非要將包發(fā)表到 npm 源上才可以提供給使用者來安裝。這對于私有的不方便 publish 到遠(yuǎn)程源(即使是私有源),或者需要對某官方源進(jìn)行改造,但依然需要把包共享出去的場景來說非常實用。

場景1: 本地模塊引用

nodejs 應(yīng)用開發(fā)中不可避免有模塊間調(diào)用,例如在實踐中經(jīng)常會把需要被頻繁引用的配置模塊放到應(yīng)用根目錄;于是在創(chuàng)建了很多層級的目錄、文件后,很可能會遇到這樣的代碼:

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

除了看上去很丑以外,這樣的路徑引用也不利于代碼的重構(gòu)。并且身為程序員的自我修養(yǎng)告訴我們,這樣重復(fù)的代碼多了也就意味著是時候把這個模塊分離出來供應(yīng)用內(nèi)其他模塊共享了。例如這個例子里的 config.js 非常適合封裝為 package 放到 node_modules 目錄下,共享給同應(yīng)用內(nèi)其他模塊。

無需手動拷貝文件或者創(chuàng)建軟鏈接到 node_modules 目錄,npm 有更優(yōu)雅的解決方案。

方案:

創(chuàng)建 config 包:
新增 config 文件夾; 重命名 config.js 為 config/index.js 文件; 創(chuàng)建 package.json 定義 config 包

{
    "name": "config",
    "main": "index.js",
    "version": "0.1.0"
}

在應(yīng)用層 package.json 文件中新增依賴項,然后執(zhí)行 npm install; 或直接執(zhí)行第 3 步

{
    "dependencies": {
        "config": "file:./config"
    }
}

(等價于第 2 步)直接在應(yīng)用目錄執(zhí)行 npm install file:./config

此時,查看 node_modules 目錄我們會發(fā)現(xiàn)多出來一個名為 config,指向上層 config/ 文件夾的軟鏈接。這是因為 npm 識別 file: 協(xié)議的url,得知這個包需要直接從文件系統(tǒng)中獲取,會自動創(chuàng)建軟鏈接到 node_modules 中,完成“安裝”過程。

相比手動軟鏈,我們既不需要關(guān)心 windows 和 linux 命令差異,又可以顯式地將依賴信息固化到 dependencies 字段中,開發(fā)團(tuán)隊其他成員可以執(zhí)行 npm install 后直接使用。

場景2: 私有 git 共享 package

有些時候,我們一個團(tuán)隊內(nèi)會有一些代碼/公用庫需要在團(tuán)隊內(nèi)不同項目間共享,但可能由于包含了敏感內(nèi)容,或者代碼太爛拿不出手等原因,不方便發(fā)布到源。

這種情況下,我們可以簡單地將被依賴的包托管在私有的 git 倉庫中,然后將該 git url 保存到 dependencies 中. npm 會直接調(diào)用系統(tǒng)的 git 命令從 git 倉庫拉取包的內(nèi)容到 node_modules 中。

npm 支持的 git url 格式:

://[[:]@][:][:][/][# | #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 問題修復(fù)

使用某個 npm 包時發(fā)現(xiàn)它有某個嚴(yán)重bug,但也許最初作者已不再維護(hù)代碼了,也許我們工作緊急,沒有足夠的時間提 issue 給作者再慢慢等作者發(fā)布新的修復(fù)版本到 npm 源。

此時我們可以手動進(jìn)入 node_modules 目錄下修改相應(yīng)的包內(nèi)容,也許修改了一行代碼就修復(fù)了問題。但是這種做法非常不明智!

首先 node_modules 本身不應(yīng)該放進(jìn)版本控制系統(tǒng),對 node_modules 文件夾中內(nèi)容的修改不會被記錄進(jìn) git 提交記錄;其次,就算我們非要反模式,把 node_modules 放進(jìn)版本控制中,你的修改內(nèi)容也很容易在下次 team 中某位成員執(zhí)行 npm installnpm update 時被覆蓋,而這樣的一次提交很可能包含了幾十幾百個包的更新,你自己所做的修改很容易就被淹沒在龐大的 diff 文件列表中了。

方案:

最好的辦法應(yīng)當(dāng)是 fork 原作者的 git 庫,在自己所屬的 repo 下修復(fù)問題后,將 dependencies 中相應(yīng)的依賴項更改為自己修復(fù)后版本的 git url 即可解決問題。(Fork 代碼庫后,也便于向原作者提交 PR 修復(fù)問題。上游代碼庫修復(fù)問題后,再次更新我們的依賴配置也不遲。)

3. npm install 如何工作 —— node_modules 目錄結(jié)構(gòu)

npm install 執(zhí)行完畢后,我們可以在 node_modules 中看到所有依賴的包。雖然使用者無需關(guān)注這個目錄里的文件夾結(jié)構(gòu)細(xì)節(jié),只管在業(yè)務(wù)代碼中引用依賴包即可,但了解 node_modules 的內(nèi)容可以幫我們更好理解 npm 如何工作,了解從 npm 2 到 npm 5 有哪些變化和改進(jìn)。

為簡單起見,我們假設(shè)應(yīng)用目錄為 app, 用兩個流行的包 webpack, nconf 作為依賴包做示例說明。并且為了正常安裝,使用了“上古” npm 2 時期的版本 webpack@1.15.0, nconf@0.8.5.

3.1 npm 2

npm 2 在安裝依賴包時,采用簡單的遞歸安裝方法。執(zhí)行 npm install 后,npm 2 依次遞歸安裝 webpacknconf 兩個包到 node_modules 中。執(zhí)行完畢后,我們會看到 ./node_modules 這層目錄只含有這兩個子目錄。

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

進(jìn)入更深一層 nconf 或 webpack 目錄,將看到這兩個包各自的 node_modules 中,已經(jīng)由 npm 遞歸地安裝好自身的依賴包。包括 ./node_modules/webpack/node_modules/webpack-core , ./node_modules/conf/node_modules/async 等等。而每一個包都有自己的依賴包,每個包自己的依賴都安裝在了自己的 node_modules 中。依賴關(guān)系層層遞進(jìn),構(gòu)成了一整個依賴樹,這個依賴樹與文件系統(tǒng)中的文件結(jié)構(gòu)樹剛好層層對應(yīng)。

最方便的查看依賴樹的方式是直接在 app 目錄下執(zhí)行 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

這樣的目錄結(jié)構(gòu)優(yōu)點在于層級結(jié)構(gòu)明顯,便于進(jìn)行傻瓜式的管理:

例如新裝一個依賴包,可以立即在第一層 node_modules 中看到子目錄

在已知所需包名和版本號時,甚至可以從別的文件夾手動拷貝需要的包到 node_modules 文件夾中,再手動修改 package.json 中的依賴配置

要刪除這個包,也可以簡單地手動刪除這個包的子目錄,并刪除 package.json 文件中相應(yīng)的一行即可

實際上,很多人在 npm 2 時代也的確都這么實踐過,的確也都可以安裝和刪除成功,并不會導(dǎo)致什么差錯。

但這樣的文件結(jié)構(gòu)也有很明顯的問題:

對復(fù)雜的工程, node_modules 內(nèi)目錄結(jié)構(gòu)可能會太深,導(dǎo)致深層的文件路徑過長而觸發(fā) windows 文件系統(tǒng)中,文件路徑不能超過 260 個字符長的錯誤

部分被多個包所依賴的包,很可能在應(yīng)用 node_modules 目錄中的很多地方被重復(fù)安裝。隨著工程規(guī)模越來越大,依賴樹越來越復(fù)雜,這樣的包情況會越來越多,造成大量的冗余。

——在我們的示例中就有這個問題,webpacknconf 都依賴 async 這個包,所以在文件系統(tǒng)中,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 - 扁平結(jié)構(gòu)

主要為了解決以上問題,npm 3 的 node_modules 目錄改成了更加扁平狀的層級結(jié)構(gòu)。文件系統(tǒng)中 webpack, nconf, async 的層級關(guān)系變成了平級關(guān)系,處于同一級目錄中。

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

雖然這樣一來 webpack/node_modules 和 nconf/node_modules 中都不再有 async 文件夾,但得益于 node 的模塊加載機(jī)制,他們都可以在上一級 node_modules 目錄中找到 async 庫。所以 webpack 和 nconf 的庫代碼中 require("async") 語句的執(zhí)行都不會有任何問題。

這只是最簡單的例子,實際的工程項目中,依賴樹不可避免地會有很多層級,很多依賴包,其中會有很多同名但版本不同的包存在于不同的依賴層級,對這些復(fù)雜的情況, npm 3 都會在安裝時遍歷整個依賴樹,計算出最合理的文件夾安裝方式,使得所有被重復(fù)依賴的包都可以去重安裝。

npm 文檔提供了更直觀的例子解釋這種情況:

假如 package{dep} 寫法代表包和包的依賴,那么  A{B,C}, B{C}, C{D} 的依賴結(jié)構(gòu)在安裝之后的 node_modules 是這樣的結(jié)構(gòu):
A
+-- B
+-- C
+-- D

這里之所以 D 也安裝到了與 B C 同一級目錄,是因為 npm 會默認(rèn)會在無沖突的前提下,盡可能將包安裝到較高的層級。

如果是 A{B,C}, B{C,D@1}, C{D@2} 的依賴關(guān)系,得到的安裝后結(jié)構(gòu)是:
A
+-- B
+-- C
   `-- D@2
+-- D@1

這里是因為,對于 npm 來說同名但不同版本的包是兩個獨立的包,而同層不能有兩個同名子目錄,所以其中的 D@2 放到了 C 的子目錄而另一個 D@1 被放到了再上一層目錄。

很明顯在 npm 3 之后 npm 的依賴樹結(jié)構(gòu)不再與文件夾層級一一對應(yīng)了。想要查看 app 的直接依賴項,要通過 npm ls 命令指定 --depth 參數(shù)來查看:

npm ls --depth 1
PS: 與本地依賴包不同,如果我們通過 npm install --global 全局安裝包到全局目錄時,得到的目錄依然是“傳統(tǒng)的”目錄結(jié)構(gòu)。而如果使用 npm 3 想要得到“傳統(tǒng)”形式的本地 node_modules 目錄,使用 npm install --global-style 命令即可。
3.3 npm 5 - package-lock 文件

npm 5 發(fā)布于 2017 年也是目前最新的 npm 版本,這一版本依然沿用 npm 3 之后扁平化的依賴包安裝方式,此外最大的變化是增加了 package-lock.json 文件。

package-lock.json 的作用是鎖定依賴安裝結(jié)構(gòu),如果查看這個 json 的結(jié)構(gòu),會發(fā)現(xiàn)與 node_modules 目錄的文件層級結(jié)構(gòu)是一一對應(yīng)的。

以依賴關(guān)系為: 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 文件并不難,其結(jié)構(gòu)是同樣類型的幾個字段嵌套起來的,主要是 version, resolved, integrity, requires, dependencies 這幾個字段而已。

version, resolved, integrity 用來記錄包的準(zhǔn)確版本號、內(nèi)容hash、安裝源的,決定了要安裝的包的準(zhǔn)確“身份”信息

假設(shè)蓋住其他字段,只關(guān)注文件中的 dependencies: {} 我們會發(fā)現(xiàn),整個文件的 JSON 配置里的 dependencies 層次結(jié)構(gòu)與文件系統(tǒng)中 node_modules 的文件夾層次結(jié)構(gòu)是完全對照的

只關(guān)注 requires: {} 字段又會發(fā)現(xiàn),除最外層的 requires 屬性為 true 以外, 其他層的 requires 屬性都對應(yīng)著這個包的 package.json 里記錄的自己的依賴項

因為這個文件記錄了 node_modules 里所有包的結(jié)構(gòu)、層級和版本號甚至安裝源,它也就事實上提供了 “保存” node_modules 狀態(tài)的能力。只要有這樣一個 lock 文件,不管在那一臺機(jī)器上執(zhí)行 npm install 都會得到完全相同的 node_modules 結(jié)果。

這就是 package-lock 文件致力于優(yōu)化的場景:在從前僅僅用 package.json 記錄依賴,由于 semver range 的機(jī)制;一個月前由 A 生成的 package.json 文件,B 在一個月后根據(jù)它執(zhí)行 npm install 所得到的 node_modules 結(jié)果很可能許多包都存在不同的差異,雖然 semver 機(jī)制的限制使得同一份 package.json 不會得到大版本不同的依賴包,但同一份代碼在不同環(huán)境安裝出不同的依賴包,依然是可能導(dǎo)致意外的潛在因素。

相同作用的文件在 npm 5 之前就有,稱為 npm shrinkwrap 文件,二者作用完全相同,不同的是后者需要手動生成,而 npm 5 默認(rèn)會在執(zhí)行 npm install 后就生成 package-lock 文件,并且建議你提交到 git/svn 代碼庫中。

package-lock.json 文件在最初 npm 5.0 默認(rèn)引入時也引起了相當(dāng)大的爭議。在 npm 5.0 中,如果已有 package-lock 文件存在,若手動在 package.json 文件新增一條依賴,再執(zhí)行 npm install, 新增的依賴并不會被安裝到 node_modules 中, package-lock.json 也不會做相應(yīng)的更新。這樣的表現(xiàn)與使用者的自然期望表現(xiàn)不符。在 npm 5.1 的首個 Release 版本中這個問題得以修復(fù)。這個事情告訴我們,要升級,不要使用 5.0。

——但依然有反對的聲音認(rèn)為 package-lock 太復(fù)雜,對此 npm 也提供了禁用配置:

npm config set package-lock false
4. 依賴包版本管理

依賴包安裝完并不意味著就萬事大吉了,版本的維護(hù)和更新也很重要。這一章介紹依賴包升級管理相關(guān)知識,太長不看版本請直接跳到 4.3 最佳實踐

4.1 semver

npm 依賴管理的一個重要特性是采用了語義化版本 (semver) 規(guī)范,作為依賴版本管理方案。

semver 約定一個包的版本號必須包含3個數(shù)字,格式必須為 MAJOR.MINOR.PATCH, 意為 主版本號.小版本號.修訂版本號.

MAJOR 對應(yīng)大的版本號迭代,做了不兼容舊版的修改時要更新 MAJOR 版本號

MINOR 對應(yīng)小版本迭代,發(fā)生兼容舊版API的修改或功能更新時,更新MINOR版本號

PATCH 對應(yīng)修訂版本號,一般針對修復(fù) BUG 的版本號

對于包作者(發(fā)布者),npm 要求在 publish 之前,必須更新版本號。npm 提供了 npm version 工具,執(zhí)行 npm version major|minor|patch 可以簡單地將版本號中相應(yīng)的數(shù)字加1.

如果包是一個 git 倉庫,npm version 還會自動創(chuàng)建一條注釋為更新后版本號的 git commit 和名為該版本號的 tag

對于包的引用者來說,我們需要在 dependencies 中使用 semver 約定的 semver range 指定所需依賴包的版本號或版本范圍。npm 提供了網(wǎng)站 https://semver.npmjs.com 可方便地計算所輸入的表達(dá)式的匹配范圍。常用的規(guī)則示例如下表:

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

任意兩條規(guī)則,通過 || 連接起來,則表示兩條規(guī)則的并集:

^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: 在常規(guī)僅包含數(shù)字的版本號之外,semver 還允許在 MAJOR.MINOR.PATCH 后追加 - 后跟點號分隔的標(biāo)簽,作為預(yù)發(fā)布版本標(biāo)簽 - Prerelese Tags,通常被視為不穩(wěn)定、不建議生產(chǎn)使用的版本。例如:

1.0.0-alpha

1.0.0-beta.1

1.0.0-rc.3

上表中我們最常見的是 ^1.8.11 這種格式的 range, 因為我們在使用 npm install 安裝包時,npm 默認(rèn)安裝當(dāng)前最新版本,例如 1.8.11, 然后在所安裝的版本號前加^號, 將 ^1.8.11 寫入 package.json 依賴配置,意味著可以匹配 1.8.11 以上,2.0.0 以下的所有版本。

4.2 依賴版本升級

問題來了,在安裝完一個依賴包之后有新版本發(fā)布了,如何使用 npm 進(jìn)行版本升級呢?——答案是簡單的 npm installnpm update,但在不同的 npm 版本,不同的 package.json, package-lock.json 文件,安裝/升級的表現(xiàn)也不同。

我們不妨還以 webpack 舉例,做如下的前提假設(shè):

我們的工程項目 app 依賴 webpack

項目最初初始化時,安裝了當(dāng)時最新的包 webpack@1.8.0,并且 package.json 中的依賴配置為: "webpack": "^1.8.0"

當(dāng)前(2018年3月) webpack 最新版本為 4.2.0, webpack 1.x 最新子版本為 1.15.0

如果我們使用的是 npm 3, 并且項目不含 package-lock.json, 那么根據(jù) node_modules 是否為空,執(zhí)行 install/update 的結(jié)果如下 (node 6.13.1, npm 3.10.10 環(huán)境下試驗):

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

根據(jù)這個表我們可以對 npm 3 得出以下結(jié)論:

如果本地 node_modules 已安裝,再次執(zhí)行 install 不會更新包版本, 執(zhí)行 update 才會更新; 而如果本地 node_modules 為空時,執(zhí)行 install/update 都會直接安裝更新包;

npm update 總是會把包更新到符合 package.json 中指定的 semver 的最新版本號——本例中符合 ^1.8.0 的最新版本為 1.15.0

一旦給定 package.json, 無論后面執(zhí)行 npm install 還是 update, package.json 中的 webpack 版本一直頑固地保持 一開始的 ^1.8.0 巋然不動

這里不合理的地方在于,如果最開始團(tuán)隊中第一個人安裝了 webpack@1.8.0, 而新加入項目的成員, checkout 工程代碼后執(zhí)行 npm install 會安裝得到不太一樣的 1.15.0 版本。雖然 semver 約定了小版本號應(yīng)當(dāng)保持向下兼容(相同大版本號下的小版本號)兼容,但萬一有不熟悉不遵循此約定的包發(fā)布者,發(fā)布了不兼容的包,此時就可能出現(xiàn)因依賴環(huán)境不同導(dǎo)致的 bug。

下面由 npm 5 帶著 package-lock.json 閃亮登場,執(zhí)行 install/update 的效果是這樣的 (node 9.8.0, npm 5.7.1 環(huán)境下試驗):

下表為表述簡單,省略了包名 webpack, install 簡寫 i, update 簡寫為 up
# package.json (BEFORE) node_modules (BEFORE) package-lock (BEFORE) command package.json (AFTER) node_modules (AFTER)
a) ^1.8.0 @1.8.0 @1.8.0 i ^1.8.0 @1.8.0
b) ^1.8.0 @1.8.0 i ^1.8.0 @1.8.0
c) ^1.8.0 @1.8.0 @1.8.0 up ^1.15.0 @1.15.0
d) ^1.8.0 @1.8.0 up ^1.8.0 @1.15.0
e) ^1.15.0 @1.8.0 (舊) @1.15.0 i ^1.15.0 @1.15.0
f) ^1.15.0 @1.8.0 (舊) @1.15.0 up ^1.15.0 @1.15.0

與 npm 3 相比,在安裝和更新依賴版本上主要的區(qū)別為:

無論何時執(zhí)行 install, npm 都會優(yōu)先按照 package-lock 中指定的版本來安裝 webpack; 避免了 npm 3 表中情形 b) 的狀況;

無論何時完成安裝/更新, package-lock 文件總會跟著 node_modules 更新 —— (因此可以視 package-lock 文件為 node_modules 的 JSON 表述)

已安裝 node_modules 后若執(zhí)行 npm update,package.json 中的版本號也會隨之更改為 ^1.15.0

由此可見 npm 5.1 使得 package.json 和 package-lock.json 中所保存的版本號更加統(tǒng)一,解決了 npm 之前的各種問題。只要遵循好的實踐習(xí)慣,團(tuán)隊成員可以很方便地維護(hù)一套應(yīng)用代碼和 node_modules 依賴都一致的環(huán)境。

皆大歡喜。

4.3 最佳實踐

總結(jié)起來,在 2018 年 (node 9.8.0, npm 5.7.1) 時代,我認(rèn)為的依賴版本管理應(yīng)當(dāng)是:

使用 npm: >=5.1 版本, 保持 package-lock.json 文件默認(rèn)開啟配置

初始化:第一作者初始化項目時使用 npm install 安裝依賴包, 默認(rèn)保存 ^X.Y.Z 依賴 range 到 package.json中; 提交 package.json, package-lock.json, 不要提交 node_modules 目錄

初始化:項目成員首次 checkout/clone 項目代碼后,執(zhí)行一次 npm install 安裝依賴包

不要手動修改 package-lock.json

升級依賴包:

升級小版本: 本地執(zhí)行 npm update 升級到新的小版本

升級大版本: 本地執(zhí)行 npm install @ 升級到新的大版本

也可手動修改 package.json 中版本號為要升級的版本(大于現(xiàn)有版本號)并指定所需的 semver, 然后執(zhí)行 npm install

本地驗證升級后新版本無問題后,提交新的 package.json, package-lock.json 文件

降級依賴包:

正確: npm install @ 驗證無問題后,提交 package.json 和 package-lock.json 文件

錯誤: 手動修改 package.json 中的版本號為更低版本的 semver, 這樣修改并不會生效,因為再次執(zhí)行 npm install 依然會安裝 package-lock.json 中的鎖定版本

刪除依賴包:

Plan A: npm uninstall 并提交 package.jsonpackage-lock.json

Plan B: 把要卸載的包從 package.json 中 dependencies 字段刪除, 然后執(zhí)行 npm install 并提交 package.jsonpackage-lock.json

任何時候有人提交了 package.json, package-lock.json 更新后,團(tuán)隊其他成員應(yīng)在 svn update/git pull 拉取更新后執(zhí)行 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 命令來執(zhí)行這段腳本,像在 shell 中執(zhí)行該命令 echo HELLO WORLD 一樣,看到終端輸出 HELLO WORLD.

—— npm scripts 的基本使用就是這么簡單,它提供了一個簡單的接口用來調(diào)用工程相關(guān)的腳本。關(guān)于更詳細(xì)的相關(guān)信息,可以參考阮一峰老師的文章 npm script 使用指南 (2016年10月).

簡要總結(jié)阮老師文章內(nèi)容:

npm run 命令執(zhí)行時,會把 ./node_modules/.bin/ 目錄添加到執(zhí)行環(huán)境的 PATH 變量中,因此如果某個命令行包未全局安裝,而只安裝在了當(dāng)前項目的 node_modules 中,通過 npm run 一樣可以調(diào)用該命令。

執(zhí)行 npm 腳本時要傳入?yún)?shù),需要在命令后加 -- 標(biāo)明, 如 npm run test -- --grep="pattern" 可以將 --grep="pattern" 參數(shù)傳給 test 命令

npm 提供了 pre 和 post 兩種鉤子機(jī)制,可以定義某個腳本前后的執(zhí)行腳本

運行時變量:在 npm run 的腳本執(zhí)行環(huán)境內(nèi),可以通過環(huán)境變量的方式獲取許多運行時相關(guān)信息,以下都可以通過 process.env 對象訪問獲得:

npm_lifecycle_event - 正在運行的腳本名稱

npm_package_ - 獲取當(dāng)前包 package.json 中某個字段的配置值:如 npm_package_name 獲取包名

npm_package__ - package.json 中嵌套字段屬性:如 npm_pacakge_dependencies_webpack 可以獲取到 package.json 中的 dependencies.webpack 字段的值,即 webpack 的版本號

5.2 node_modules/.bin 目錄

上面所說的 node_modules/.bin 目錄,保存了依賴目錄中所安裝的可供調(diào)用的命令行包。

何謂命令行包?例如 webpack 就屬于一個命令行包。如果我們在安裝 webpack 時添加 --global 參數(shù),就可以在終端直接輸入 webpack 進(jìn)行調(diào)用。但如果不加 --global 參數(shù),我們會在 node_modules/.bin 目錄里看到名為 webpack 的文件,如果在終端直接輸入 ./node_modules/.bin/webpack 命令,一樣可以執(zhí)行。

這是因為 webpackpackage.json 文件中定義了 bin 字段為:

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

bin 字段的配置格式為: : , 即 命令名: 可執(zhí)行文件. npm 執(zhí)行 install 時,會分析每個依賴包的 package.json 中的 bin 字段,并將其包含的條目安裝到 ./node_modules/.bin 目錄中,文件名為 。而如果是全局模式安裝,則會在 npm 全局安裝路徑的 bin 目錄下創(chuàng)建指向 名為 的軟鏈。因此,./node_modules/.bin/webpack 文件在通過命令行調(diào)用時,實際上就是在執(zhí)行 node ./node_modules/.bin/webpack.js 命令。

正如上一節(jié)所說,npm run 命令在執(zhí)行時會把 ./node_modules/.bin 加入到 PATH 中,使我們可直接調(diào)用所有提供了命令行調(diào)用接口的依賴包。所以這里就引出了一個最佳實踐:

將項目依賴的命令行工具安裝到項目依賴文件夾中,然后通過 npm scripts 調(diào)用;而非全局安裝

舉例而言 webpack 作為前端工程標(biāo)配的構(gòu)建工具,雖然我們都習(xí)慣了全局安裝并直接使用命令行調(diào)用,但不同的項目依賴的 webpack 版本可能不同,相應(yīng)的 webpack.config.js 配置文件也可能只兼容了特定版本的 webpack. 如果我們僅全局安裝了最新的 webpack 4.x 并使用 webpack 命令調(diào)用,在一個依賴 webpack 3.x 的工程中就會無法成功執(zhí)行構(gòu)建。

但如果這類工具總是本地安裝,我們要調(diào)用一個命令,要手動添加 ./node_modules/.bin 這個長長的前綴,未免也太麻煩了,我們 nodejs 開發(fā)者都很懶的。于是 npm 從5.2 開始自帶了一個新的工具 npx.

5.3 npx

npx 的使用很簡單,就是執(zhí)行 npx 即可,這里的 默認(rèn)就是 ./node_modules 目錄中安裝的可執(zhí)行腳本名。例如上面本地安裝好的 webpack 包,我們可以直接使用 npx webpack 執(zhí)行即可。

除了這種最簡單的場景, npm cli 團(tuán)隊開發(fā)者 Kat Marchán 還在這篇文章中介紹了其他幾種 npx 的神奇用法: Introducing npx: an npm package runner, 國內(nèi)有位開發(fā)者 robin.law 將原文翻譯為中文 npx是什么,為什么需要npx?.

有興趣的可以戳鏈接了解,懶得點鏈接的,看總結(jié):

場景a) 一鍵執(zhí)行遠(yuǎn)程 npm 源的二進(jìn)制包

除了在 package 中執(zhí)行 ./node_modules/.bin 中已安裝的命令, 還可以直接指定未安裝的二進(jìn)制包名執(zhí)行。例如我們在一個沒有 package.json 也沒有 node_modules 的目錄下,執(zhí)行:

npx cowsay hello

npx 將會從 npm 源下載 cowsay 這個包(但并不安裝)并執(zhí)行:

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

這種用途非常適合 1. 在本地簡單測試或調(diào)試 npm 源上這些二進(jìn)制包的功能;2. 調(diào)用 create-react-app 或 yeoman 這類往往每個項目只需要使用一次的腳手架工具

PS: 此處有彩蛋,執(zhí)行這條命令試試:

npx workin-hard
場景b) 一鍵執(zhí)行 GitHub Gist

還記得前面提到的 2.1 package定義 么,npm install 可以是包含了有效 package.json 的 git url.

剛好 GitHub Gist 也是 git 倉庫 的一種,集合 npx 就可以方便地將簡單的腳本共享給其他人,擁有該鏈接的人無需將腳本安裝到本地工作目錄即可執(zhí)行。將 package.json 和 需執(zhí)行的二進(jìn)制腳本上傳至 gist, 在運行 npx 就可以方便地執(zhí)行該 gist 定義的命令。

原文作者 Kat Marchán 提供了這個示例 gist, 執(zhí)行:

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

可得到一個來自 GitHubGist 的 hello world 問候。

場景c) 使用不同版本 node 執(zhí)行命令

將 npx 與 Aria Stewart 創(chuàng)建的 node 包 (https://www.npmjs.com/package... 結(jié)合,可以實現(xiàn)在一行命令中使用指定版本的 node 執(zhí)行命令。

例如先后執(zhí)行:

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 命令進(jìn)行 npm 相關(guān)配置,通過 npm config ls -l 可查看 npm 的所有配置,包括默認(rèn)配置。npm 文檔頁為每個配置項提供了詳細(xì)的說明 https://docs.npmjs.com/misc/c... .

修改配置的命令為 npm config set , 我們使用相關(guān)的常見重要配置:

proxy, https-proxy: 指定 npm 使用的代理

registry 指定 npm 下載安裝包時的源,默認(rèn)為 https://registry.npmjs.org/ 可以指定為私有 Registry 源

package-lock 指定是否默認(rèn)生成 package-lock 文件,建議保持默認(rèn) true

save true/false 指定是否在 npm install 后保存包為 dependencies, npm 5 起默認(rèn)為 true

刪除指定的配置項命令為 npm config delete .

6.2 npmrc 文件

除了使用 CLI 的 npm config 命令顯示更改 npm 配置,還可以通過 npmrc 文件直接修改配置。

這樣的 npmrc 文件優(yōu)先級由高到低包括:

工程內(nèi)配置文件: /path/to/my/project/.npmrc

用戶級配置文件: ~/.npmrc

全局配置文件: $PREFIX/etc/npmrc (即npm config get globalconfig 輸出的路徑)

npm內(nèi)置配置文件: /path/to/npm/npmrc

通過這個機(jī)制,我們可以方便地在工程跟目錄創(chuàng)建一個 .npmrc 文件來共享需要在團(tuán)隊間共享的 npm 運行相關(guān)配置。比如如果我們在公司內(nèi)網(wǎng)環(huán)境下需通過代理才可訪問 registry.npmjs.org 源,或需訪問內(nèi)網(wǎng)的 registry, 就可以在工作項目下新增 .npmrc 文件并提交代碼庫。

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

因為項目級 .npmrc 文件的作用域只在本項目下,所以在非本目錄下,這些配置并不生效。對于使用筆記本工作的開發(fā)者,可以很好地隔離公司的工作項目、在家學(xué)習(xí)研究項目兩種不同的環(huán)境。

將這個功能與 ~/.npm-init.js 配置相結(jié)合,可以將特定配置的 .npmrc 跟 .gitignore, README 之類文件一起做到 npm init 腳手架中,進(jìn)一步減少手動配置。

6.3 node 版本約束

雖然一個項目的團(tuán)隊都共享了相同的代碼,但每個人的開發(fā)機(jī)器可能安裝了不同的 node 版本,此外服務(wù)器端的也可能與本地開發(fā)機(jī)不一致。

這又是一個可能帶來不一致性的因素 —— 但也不是很難解決,聲明式約束+腳本限制即可。

聲明:通過 package.jsonengines 屬性聲明應(yīng)用運行所需的版本運行時要求。例如我們的項目中使用了 async, await 特性,查閱兼容性表格得知最低支持版本為 7.6.0,因此指定 engines 配置為:

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

強(qiáng)約束(可選):在 npm 中以上字段內(nèi)容僅作為建議字段使用,若要在私有項目中添加強(qiáng)約束,需要自己寫腳本鉤子,讀取并解析 engines 字段的 semver range 并與運行時環(huán)境做對比校驗并適當(dāng)提醒。

7. 小結(jié) npm 最佳實踐

使用 npm-init 初始化新項目

統(tǒng)一項目配置: 需團(tuán)隊共享的 npm config 配置項,固化到 .npmrc 文件中

統(tǒng)一運行環(huán)境,統(tǒng)一 package.json,統(tǒng)一 package-lock 文件

合理使用多樣化的源安裝依賴包: npm install |

使用 npm: >=5.2 版本

使用 npm scripts 與 npx (npm: >=5.2) 腳本管理應(yīng)用相關(guān)腳本

8. 更多資料

參考

npm team 成員 Ashley Williams 在 2016 年 Node.js Live 上的 talk: You Don"t Know npm, 當(dāng)時還沒有 npm 5

YouTube 視頻鏈接: Node.js Live (Paris) - Ashley Williams, You Don"t Know npm

演講用的 slides: the ag_deck

這篇 2015 年的文章介紹了如何使用把本地模塊打包到 node_modules 依賴中: Build modular application with npm local modules

一篇很好的介紹 package-lock.json 的文章: Everything you wanted to know about package-lock.json

阮一峰 npm scripts 使用指南

Kat Marchán 介紹npx:

原文 Introducing npx: an npm package runner

中文 npx是什么,為什么需要npx?

文檔

npm 官方文檔, 無中文翻譯

package.json 文件

npm config 配置

npm semver 計算器

node_modules 目錄扁平化

yarn 中文文檔,雖然是 npm 競爭者但兼容 package.json 和 node_modules 目錄,因此這兩部分一樣可參考:

package.json - 中文

依賴與版本 - 中文

延伸閱讀

sam boyer 《所以你想開發(fā)一個包管理系統(tǒng)》,從無關(guān)特定語言的角度,介紹一個包管理系統(tǒng)的方面: So you want to write a package manager

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/93656.html

相關(guān)文章

  • [譯]2018年值得關(guān)注的10大JavaScript動畫庫

    摘要:幸運的是,供應(yīng)似乎與需求相匹配,并且有多種選擇。讓我們來看看年值得關(guān)注的十大動畫庫。八年了,仍然是一個強(qiáng)大的動畫工具。接下來在這個令人驚嘆的動畫庫列表上的就是了。,通常被稱為動畫平臺,我們忽略它在列表中的排名,它是列表中最受歡迎的庫之一。 原文鏈接原譯文鏈接 現(xiàn)代網(wǎng)站的客戶端依靠高質(zhì)量的動畫,這就使得JavaScript動畫庫的需求不斷增加。幸運的是,供應(yīng)似乎與需求相匹配,并且有多種選...

    Me_Kun 評論0 收藏0
  • 【總結(jié)】我們2018年的關(guān)鍵詞-堅持學(xué)習(xí)

    摘要:因為涉及業(yè)務(wù)敏感話題,本文只記錄我們學(xué)習(xí)的歷程。我由衷的感謝團(tuán)隊的小伙伴們,感謝你們的堅韌不拔,感謝你們的持續(xù)成長。這個變化只是在每天的堅持和刻意練習(xí)中發(fā)生的,是那么的神奇。 因為涉及業(yè)務(wù)敏感話題,本文只記錄我們學(xué)習(xí)的歷程。 關(guān)于堅持 ??從2016年起,我們團(tuán)隊堅持每天早晨8:50-10:30的100分鐘早晨討論,到現(xiàn)在已經(jīng)兩年了,期間沒有中斷過。我由衷的感謝團(tuán)隊的小伙伴們,感謝你們...

    Imfan 評論0 收藏0
  • 【總結(jié)】我們2018年的關(guān)鍵詞-堅持學(xué)習(xí)

    摘要:因為涉及業(yè)務(wù)敏感話題,本文只記錄我們學(xué)習(xí)的歷程。我由衷的感謝團(tuán)隊的小伙伴們,感謝你們的堅韌不拔,感謝你們的持續(xù)成長。這個變化只是在每天的堅持和刻意練習(xí)中發(fā)生的,是那么的神奇。 因為涉及業(yè)務(wù)敏感話題,本文只記錄我們學(xué)習(xí)的歷程。 關(guān)于堅持 ??從2016年起,我們團(tuán)隊堅持每天早晨8:50-10:30的100分鐘早晨討論,到現(xiàn)在已經(jīng)兩年了,期間沒有中斷過。我由衷的感謝團(tuán)隊的小伙伴們,感謝你們...

    xingpingz 評論0 收藏0
  • 掘金 AMA:聽《Android進(jìn)階解密》作者--劉望舒聊 Android 開發(fā)、進(jìn)階那些事

    摘要:第二十二期掘金團(tuán)隊請來了進(jìn)階解密作者劉望舒做了為期三天的活動活動已結(jié)束。我們在此精選了一些來自用戶的提問及劉望舒的回答。提醒本期分布式微服務(wù)主題的正在進(jìn)行,歡迎前去提問,傳送門關(guān)于劉望舒進(jìn)階之光進(jìn)階解密的作者,安卓巴士等技術(shù)大會特邀講師。第二十二期 AMA 掘金團(tuán)隊請來了《Android進(jìn)階解密》作者-- 劉望舒做了為期三天的 Ask Me Anything (AMA) 活動(活動已結(jié)束)。...

    wenyiweb 評論0 收藏0

發(fā)表評論

0條評論

閱讀需要支付1元查看
<