npm 的原理

npm 據稱成爲世界最大的包管理器?原因真的只是用戶友好?

1、npm init

用來初始化一個簡單的 package.json 文件。package.json 文件用來定義一個 package 的描述文件。

1、npm init 的執行的默認行爲

執行npm init --yes,全部使用默認的值。

2、 自定義 npm init 行爲

npm init 命令的原理是:調用腳本,輸出一個初始化的 package.json 文件。

獲取用戶輸入使用 prompt() 方法。

2、依賴包安裝

npm 的核心功能:依賴管理。執行 npm i 從 package.json 中 dependencies 和 devDependencies 將依賴包安裝到當前目錄的 node_modules 文件夾中。

1、package 定義

npm i 就可以安裝一個包。通常 package 就是我們需要安裝的包名,默認配置下 npm 會從默認的源(Registry)中查找該包名的對應的包地址,並且下載安裝。 還可以是一個指向有效包名的 http url/git url / 文件夾路徑。

package 的準確定義,符合以下 a) 到 g) 其中一個條件,他就是一個 package:

package 的準確定義

2、安裝本地包 / 遠程 git 倉庫包

共享依賴包,並非非要把包發佈到 npm 源上才能使用。

(1)場景 1:本地模塊引用

開發中避免不了模塊之間調用,開發中,我們把頻繁調用的配置模塊放在根目錄,然後如果有很多層級目錄,後來引用

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

這樣的路徑引用不利於代碼重構。這時候我們需要考慮把這個模塊分離出來供其他模塊共享。比如 config.js 可以封裝成一個 package 放到 node_modules 目錄下。

不需要手動拷貝或者創建軟連接到 node_modules 目錄,npm 有自己的解決方案:

方案:

1、新增 config 文件夾,將 config.js 移入文件夾,名字修改爲 index.js,創建 package.json 定義 config 包

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

2、在項目的 package.json 新增依賴項,然後執行 npm i。

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

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

(2)場景 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)場景 3:開源 package 問題修復

此時我們可以手動進入 node_modules 目錄下修改相應的包內容,也許修改了一行代碼就修復了問題。但是這種做法非常不明智!

方案:

fork 原作者的 git 庫,在自己的 repo 修復問題,然後將 dependencies 中的相應依賴改爲自己修復後版本的 git url 就可以解決問題。

3、npm install 如何工作

npm i 執行完畢,node_modules 中看到所有的依賴包。開發人員無關注 node_modules 文件夾的結構細節,關注業務代碼中引用依賴包。

理解 node_modules 結構幫助我們更好理解 npm 如何工作。npm2 到 npm5 變化和改進。

3.1 npm2

npm2 在安裝依賴包,採用的是簡單的遞歸安裝方法。每一個包都有自己的依賴包,每一個包的依賴都安裝在自己的 node_modules 中,依賴關係層層遞進,構成整個依賴樹,這個依賴樹與文件系統中的文件結構樹一一對應。

最方便的依賴樹的方式在根目錄下執行 npm ls。

優點:

•1、層級結構明顯,便於傻瓜式管理。

缺點:

•1、複雜工程,目錄結構可能太深,深層的文件路徑過長觸發 window 文件系統中文件路徑不能超過 260 個字符長。•2、部分被多個包依賴的包在很多地方重複安裝,造成大量的冗餘。

3.2 npm3

npm3 的 node_modules 目錄改成更加扁平狀層級結構。npm3 在安裝的時候遍歷整個依賴樹,計算最合理的文件夾安裝方式,所有被重複依賴的包都可以去重安裝。

npm 來說,同名不同版本的包是兩個獨立的包。

npm3 的依賴樹結構不再與文件夾層級一一對應。

3.3 npm5

沿用 npm3 的扁平化依賴包安裝方式。最大的變化時增加 package-lock.json 文件。

package-lock.json 作用:**鎖定依賴安裝結構,**發現 node_modules 目錄文件層級結構是與 json 的結構一一對應。

npm5 默認會在執行 npm i 後生成 package-lock.json 文件,提交到 git/svn 代碼庫。

要升級,不要使用 5.0 版本。

注意:在 npm 5.0 中,如果已有 package-lock 文件存在,若手動在 package.json 文件新增一條依賴,再執行 npm install, 新增的依賴並不會被安裝到 node_modules 中, package-lock.json 也不會做相應的更新。

4、依賴包版本管理

介紹依賴包升級管理相關知識。

4.1 語義化版本 semver

npm 依賴管理的一個重要特性採用語義化版本(semver)規範,作爲版本管理方案。

語義化版本號必須包含三個數字,格式:major.minor.patch。意思是:主版本號. 小版本號. 修改版本號。

我們需要在 dependencies 中使用 semver 約定的指定所需依賴包的版本號或者範圍。

常用的規則如下圖:

semver 語義化版本

1、任意兩條規則,用空格連接起來,表示 “與” 邏輯,即爲兩個規則的交集。

如 >=2.3.1 <=2.8.0 可以解讀爲:>=2.3.1 且 <=2.8.0

• 可以匹配 2.3.1, 2.4.5, 2.8.0• 但不匹配 1.0.0, 2.3.0, 2.8.1, 3.0.0

2、任意兩條規則,用 || 連接起來,表示 “或” 邏輯,即爲兩條規則的並集。

如 ^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

3、更直觀的表示版本號範圍的寫法

•• 或 x 匹配所有主版本 •1 或 1.x 匹配 主版本號爲 1 的所有版本 •1.2 或 1.2.x 匹配 版本號爲 1.2 開頭的所有版本

4、在 MAJOR.MINOR.PATCH 後追加 - 後跟點號分隔的標籤,作爲預發佈版本標籤 通常被視爲不穩定、不建議生產使用的版本。

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

4.2 依賴版本升級

在安裝完一個依賴包之後有新的版本發佈了,如何使用 npm 進行版本升級呢?

•npm i 或者 npm update,但是不同的 npm 版本,不同的 package.json 和 package-lock.json 文件,安裝和升級表現是不同的。

使用 npm3 的結論:

使用 npm5 的結論:

4.3 最佳實踐

我常用的 node 是 8.11.x,npm 是 5.6.0。

5、npm 的 sctipts

5.1 基本使用

npm scripts 是 npm 的一個重要的特性。在 package.json 中 scripts 字段定義一個腳本。

比如:

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

我們可以通過 npm run echo 命令執行這段腳本,就像 shell 中執行 echo HELLO WOLRD,終端是可以看到輸出的。

總結如下:

5.2 node_modules/.bin 目錄

保存了依賴目錄中所安裝的可供調用的命令行包。本質是一個可執行文件到指定文件源的映射。

例如 webpack 就屬於一個命令行包。如果我們在安裝 webpack 時添加 --global 參數,就可以在終端直接輸入 webpack 進行調用。

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

• 將項目依賴的命令行工具安裝到項目依賴文件夾中,然後通過 npm scripts 調用;而非全局安裝

於是 npm 從 5.2 開始自帶了一個新的工具 npx.

5.3 npx

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

5.4 用法

•1、傳入參數

"scripts": {
  "serve": "vue-cli-service serve",
  "serve1": "vue-cli-service --serve1",
  "serve2": "vue-cli-service -serve2",
  "serve3": "vue-cli-service serve --mode=dev --mobile -config build/example.js"
}

除了第一個可執行的命令,以空格分割的任何字符串都是參數,並且都能通過 process.argv 屬性訪問。

比如執行 npm run serve3 命令,process.argv 的具體內容爲:

[ '/usr/local/Cellar/node/7.7.1_1/bin/node',
  '/Users/mac/Vue-projects/hao-cli/node_modules/.bin/vue-cli-service',
  'serve',
  '--mode=dev',
  '--mobile',
  '-config',
  'build/example.js'
]

•2、多命令運行 在啓動時可能需要同時執行多個任務,多個任務的執行順序決定了項目的表現。

(1)串行執行

串行執行,要求前一個任務執行成功之後才能執行下一個任務。使用 && 服務來連接。

npm run scipt1 && npm run script2

串行執行命令,只要一個命令執行失敗,整個腳本會中止的。

(2)並行執行

並行執行,就是多個命令同時平行執行,使用 & 符號來連接。

npm run script1 & npm run script2

•3、env 環境變量 在執行 npm run 腳本時,npm 會設置一些特殊的 env 環境變量。其中 package.json 中的所有字段,都會被設置爲以 npm_package_ 開頭的環境變量。

•4、指令鉤子 在執行 npm scripts 命令(無論是自定義還是內置)時,都經歷了 pre 和 post 兩個鉤子,在這兩個鉤子中可以定義某個命令執行前後的命令。比如在執行 npm run serve 命令時,會依次執行 npm run preserve、npm run serve、npm run postserve,所以可以在這兩個鉤子中自定義一些動作:

"scripts": {
  "preserve": "xxxxx",
  "serve": "cross-env NODE_ENV=production webpack",
  "postserve": "xxxxxx"
}

•5、常用腳本示例

// 刪除目錄
"clean": "rimraf dist/*",
// 本地搭建一個http服務
"server": "http-server -p 9090 dist/",
// 打開瀏覽器
"open:dev": "opener http://localhost:9090",
// 實時刷新
"livereload": "live-reload --port 9091 dist/",
// 構建 HTML 文件
"build:html": "jade index.jade > dist/index.html",
// 只要 CSS 文件有變動,就重新執行構建
"watch:css": "watch 'npm run build:css' assets/styles/",
// 只要 HTML 文件有變動,就重新執行構建
"watch:html": "watch 'npm run build:html' assets/html",
// 部署到 Amazon S3
"deploy:prod": "s3-cli sync ./dist/ s3://example-com/prod-site/",
// 構建 favicon
"build:favicon": "node scripts/favicon.js",

6 npm 配置

6.1 npm config

6.2 npmrc 文件

可以通過刪除 npm config 命令修改配置,還可以通過 npmrc 文件直接修改配置。

npmrc 文件優先級由高到低,包括:

比如:我們在公司內網下需要代理才能訪問默認源:https://registry.npmjs.org 源;或者訪問內網的 registry,就可以在工作項目下新增. npmrc 文件並提交代碼庫。

示例配置:

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

這種在工程內配置文件的優先級最高,作用域在這個項目下,可以很好的隔離公司項目和學習研究的項目兩種不同環境。

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

6.3 node 版本約束

一個團隊中共享了相同的代碼,但是每個人開發機器不一致,使用的 node 版本也不一致,服務端可能與開發環境不一致。

• 這就帶來了不一致的因素 ---- 方案:聲明式約束 + 腳本限制。• 聲明:通過 package.json 的 engines 屬性聲明應用運行所需的版本要求。例如我呢項目中使用了 async,await 特性,得知 node 查閱兼容表格 [1] 得知最低支持版本是 7.6.0. 因此指定 engines 配置爲:

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

• 強約束 (可選):需要添加強約束,需要自己寫腳本鉤子,讀取並解析 engines 字段的 semver range 並與運行環境做比對校驗並適當提醒。

總結

引用鏈接

[1] node 查閱兼容表格: https://node.green/

感謝支持

「松寶寫代碼」公衆號作者,暱稱:saucxs,github 上 dom 水印工具 700+Star,juejin 年度人氣作者 No.81,國家發明專利 4 項,5GWN 國際會議最佳論文獎 1 次,曾經的 ACM 校隊和數學建模隊。現字節社招面試官,校招編程題出題,一個愛好折騰,致力於全棧,喜歡挑戰自己,正在努力成長的字節跳動工程師,星辰大海,未來可期。內推字節跳動各個部門各個崗位,有精選文章,進階學習,思考職業,每日一題,項目實驗室,AB 實驗等模塊,涉及到 JavaScript,Node,Vue,React,瀏覽器,http,算法,端相關,小程序等方向。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/PSlUfdX3KGqvXdkC0xQ97w