淺析nodejs專案中的package.json的常見設定屬性

2022-05-20 22:00:28
本篇文章帶大家瞭解一下專案中的package.json組態檔,聊聊package.json中一些常見設定屬性、環境相關屬性、依賴相關屬性和三方屬性,希望對大家有所幫助!

npm是前端開發人員廣泛使用的包管理工具,專案中通過package.json來管理專案中所依賴的npm包的設定。package.json就是一個json檔案,除了能夠描述專案的包依賴外,允許我們使用「語意化版本規則」指明你專案依賴包的版本,讓你的構建更好地與其他開發者分享,便於重複使用。 本文主要從最近的實踐出發,結合最新的npm和node的版本,介紹一下package.json中一些常見的設定以及如何寫一個規範的package.json


一、package.json

1. package.json簡介

在nodejs專案中,package.json是管理其依賴的組態檔,通常我們在初始化一個nodejs專案的時候會通過:

npm init

然後在你的目錄下會生成3個目錄/檔案, node_modules, package.json和 package.lock.json。其中package.json的內容為:

{
    "name": "Your project name",
    "version": "1.0.0",
    "description": "Your project description",
    "main": "app.js",
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
    },
    "author": "Author name",
    "license": "ISC",
    "dependencies": {
        "dependency1": "^1.4.0",
        "dependency2": "^1.5.2"
    }
}

上述可以看出,package.json中包含了專案本身的後設資料,以及專案的子依賴資訊(比如dependicies等)。

2. package-lock.json

我們發現在npm init的時候,不僅生成了package.json檔案,還生成了package-lock.json檔案。那麼為什麼存在package.json的清空下,還需要生成package-lock.json檔案呢。本質上package-lock.json檔案是為了鎖版本,在package.json中指定的子npm包比如:react: "^16.0.0",在實際安裝中,只要高於react的版本都滿足package.json的要求。這樣就使得根據同一個package.json檔案,兩次安裝的子依賴版本不能保證一致。

而package-lock檔案如下所示,子依賴dependency1就詳細的指定了其版本。起到lock版本的作用。

{
    "name": "Your project name",
    "version": "1.0.0",
    "lockfileVersion": 1,
    "requires": true,
    "dependencies": {
        "dependency1": {
            "version": "1.4.0",
            "resolved": 
"https://registry.npmjs.org/dependency1/-/dependency1-1.4.0.tgz",
            "integrity": 
"sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA=="
        },
        "dependency2": {
            "version": "1.5.2",
            "resolved": 
"https://registry.npmjs.org/dependency2/-/dependency2-1.5.2.tgz",
            "integrity": 
"sha512-WOn21V8AhyE1QqVfPIVxe3tupJacq1xGkPTB4iagT6o+P2cAgEOOwIxMftr4+ZCTI6d551ij9j61DFr0nsP2uQ=="
        }
    }
}

二、package.json常用屬性

本章來聊聊package.json中常用的設定屬性,形如name,version等屬性太過簡單,不一一介紹。本章主要介紹一下script、bin和workspaces屬性。

2.1 script

在npm中使用script標籤來定義指令碼,每當制定npm run的時候,就會自動建立一個shell指令碼,這裡需要注意的是,npm run新建的這個 Shell,會將本地目錄的node_modules/.bin子目錄加入PATH變數。

這意味著,當前目錄的node_modules/.bin子目錄裡面的所有指令碼,都可以直接用指令碼名呼叫,而不必加上路徑。比如,當前專案的依賴裡面有 esbuild,只要直接寫esbuild xxx 就可以了。

{
  // ...
  "scripts": {
    "build": "esbuild index.js",
  }
}
{
  // ...
  "scripts": {
    "build": "./node_modules/.bin/esbuild index.js" 
  }
}

上面兩種寫法是等價的。

2.2 bin

bin屬性用來將可執行檔案載入到全域性環境中,指定了bin欄位的npm包,一旦在全域性安裝,就會被載入到全域性環境中,可以通過別名來執行該檔案。

比如@bytepack/cli的npm包:

"bin": {
    "bytepack": "./bin/index.js"
 },

一旦在全域性安裝了@bytepack/cli,就可以直接通過bytepack來執行相應的命令,比如

bytepack -v
//顯示1.11.0

如果非全域性安裝,那麼會自動連線到專案的node_module/.bin目錄中。與前面介紹的script標籤中所說的一致,可以直接用別名來使用。

2.3 workspaces

在專案過大的時候,最近越來越流行monorepo。提到monorepo就繞不看workspaces,早期我們會用yarn workspaces,現在npm官方也支援了workspaces. workspaces解決了本地檔案系統中如何在一個頂層root package下管理多個子packages的問題,在workspaces宣告目錄下的package會軟鏈到最上層root package的node_modules中。

直接以官網的例子來說明:

{
  "name": "my-project",
  "workspaces": [
    "packages/a"
  ]
}

在一個npm包名為my-project的npm包中,存在workspaces設定的目錄。

.
+-- package.json
+-- index.js
`-- packages
   +-- a
   |  `-- package.json

並且該最上層的名為my-project的root包,有packages/a子包。此時,我們如果npm install,那麼在root package中node_modules中安裝的npm包a,指向的是原生的package/a.

.
+-- node_modules
|  `-- packages/a -> ../packages/a
+-- package-lock.json
+-- package.json
`-- packages
   +-- a
   |   `-- package.json

上述的

-- packages/a -> ../packages/a

指的就是從node_modules中a連結到本地npm包的軟鏈

三、package.json環境相關屬性

常見的環境,基本上分為瀏覽器browser和node環境兩大類,接下來我們來看看package.json中,跟環境相關的設定屬性。環境的定義可以簡單理解如下:

  • browser環境:比如存在一些只有在瀏覽器中才會存在的全域性變數等,比如window,Document等
  • node環境: npm包的原始檔中存在只有在node環境中才會有的一些變數和內建包,內建函數等。

3.1 type

js的模組化規範包含了commonjs、CMD、UMD、AMD和ES module等,最早先在node中支援的僅僅是commonjs欄位,但是從node13.2.0開始後,node正式支援了ES module規範,在package.json中可以通過type欄位來宣告npm包遵循的模組化規範。

//package.json
{
   name: "some package",
   type: "module"||"commonjs" 
}

需要注意的是:

  • 不指定type的時候,type的預設值是commonjs,不過建議npm包都指定一下type

  • 當type欄位指定值為module則採用ESModule規範

  • 當type欄位指定時,目錄下的所有.js字尾結尾的檔案,都遵循type所指定的模組化規範

  • 除了type可以指定模組化規範外,通過檔案的字尾來指定檔案所遵循的模組化規範,以.mjs結尾的檔案就是使用的ESModule規範,以.cjs結尾的遵循的是commonjs規範

3.2 main & module & browser

除了type外,package.json中還有main,module和browser 3個欄位來定義npm包的入口檔案。

  • main : 定義了 npm 包的入口檔案,browser 環境和 node 環境均可使用
  • module : 定義 npm 包的 ESM 規範的入口檔案,browser 環境和 node - 環境均可使用
  • browser : 定義 npm 包在 browser 環境下的入口檔案

我們來看一下這3個欄位的使用場景,以及同時存在這3個欄位時的優先順序。我們假設有一個npm包為demo1,

----- dist
   |-- index.browser.js
   |-- index.browser.mjs
   |-- index.js
   |-- index.mjs

其package.json中同時指定了main,module和browser這3個欄位,

  "main": "dist/index.js",  // main 
  "module": "dist/index.mjs", // module

  // browser 可定義成和 main/module 欄位一一對應的對映物件,也可以直接定義為字串
  "browser": {
    "./dist/index.js": "./dist/index.browser.js", // browser+cjs
    "./dist/index.mjs": "./dist/index.browser.mjs"  // browser+mjs
  },

  // "browser": "./dist/index.browser.js" // browser

預設構建和使用,比如我們在專案中參照這個npm包:

import demo from 'demo'

通過構建工具構建上述程式碼後,模組的載入循序為:

browser+mjs > module > browser+cjs > main

這個載入順序是大部分構建工具預設的載入順序,比如webapck、esbuild等等。可以通過相應的設定修改這個載入順序,不過大部分場景,我們還是會遵循預設的載入順序。

3.3 exports

如果在package.json中定義了exports欄位,那麼這個欄位所定義的內容就是該npm包的真實和全部的匯出,優先順序會高於main和file等欄位。

舉例來說:

{
  "name": "pkg",
  "exports": {
    ".": "./main.mjs",
    "./foo": "./foo.js"
  }
}
import { something } from "pkg"; // from "pkg/main.mjs"
const { something } = require("pkg/foo"); // require("pkg/foo.js")

從上述的例子來看,exports可以定義不同path的匯出。如果存在exports後,以前正常生效的file目錄到處會失效,比如require('pkg/package.json'),因為在exports中沒有指定,就會報錯。

exports還有一個最大的特點,就是條件參照,比如我們可以根據不同的參照方式或者模組化型別,來指定npm包參照不同的入口檔案。

// package.json
{ 
  "name":"pkg",
  "main": "./main-require.cjs",
  "exports": {
    "import": "./main-module.js",
    "require": "./main-require.cjs"
  },
  "type": "module"
}

上述的例子中,如果我們通過

const p = require('pkg')

參照的就是"./main-require.cjs"。

如果通過:

import p from 'pkg'

參照的就是"./main-module.js"

最後需要注意的是 :如果存在exports屬性,exports屬性不僅優先順序高於main,同時也高於module和browser欄位。

三、package.json依賴相關屬性

package.json中跟依賴相關的設定屬性包含了dependencies、devDependencies、peerDependencies和peerDependenciesMeta等。

dependencies是專案的依賴,而devDependencies是開發所需要的模組,所以我們可以在開發過程中需要的安裝上去,來提高我們的開發效率。這裡需要注意的時,在自己的專案中儘量的規範使用,形如webpack、babel等是開發依賴,而不是專案本身的依賴,不要放在dependencies中。

dependencies除了dependencies和devDependencies,本文重點介紹的是peerDependencies和peerDependenciesMeta。

3.1 peerDependencies

peerDependencies是package.json中的依賴項,可以解決核心庫被下載多次,以及統一核心庫版本的問題。

//package/pkg
----- node_modules
   |-- npm-a -> 依賴了react,react-dom
   |-- npm-b -> 依賴了react,react-dom
   |-- index.js

比如上述的例子中如果子npm包a,b都以來了react和react-dom,此時如果我們在子npm包a,b的package.json中宣告了PeerDependicies後,相應的依賴就不會重新安裝。

需要注意的有兩點:

  • 對於子npm包a,在npm7中,如果單獨安裝子npm a,其peerDependicies中的包,會被安裝下來。但是npm7之前是不會的。
  • 請規範和詳細的指定PeerDependicies的設定,筆者在看到有些react元件庫,不在PeerDependicies中指定react和react-dom,或者將react和react-dom放到了dependicies中,這兩種不規範的指定都會存在一些問題。
  • 其二,正確的指定PeerDependicies中npm包的版本,[email protected],peerDependicies指定的是:"react": "^16.8.0 || ^17.0.0 || ^18.0.0",但實際上,這個react-focus-lock並不支援18.x的react

3.2 peerDependenciesMeta

看到「Meta」就有後設資料的意思,這裡的peerDependenciesMeta就是詳細修飾了peerDependicies,比如在react-redux這個npm包中的package.json中有這麼一段:

 "peerDependencies": {
    "react": "^16.8.3 || ^17 || ^18"
  },
 "peerDependenciesMeta": {
    "react-dom": {
      "optional": true
    },
    "react-native": {
      "optional": true
    }
  }

這裡指定了"react-dom","react-native"在peerDependenciesMeta中,且為可選項,因此如果專案中檢測沒有安裝"react-dom"和"react-native"都不會報錯。

值得注意的是,通過peerDependenciesMeta我們確實是取消了限制,但是這裡經常存在非A即B的場景,比如上述例子中,我們需要的是「react-dom」和"react-native"需要安裝一個,但是實際上通過上述的宣告,我們實現不了這種提示。

四、package.json三方屬性

package.json中也存在很多三方屬性,比如tsc中使用的types、構建工具中使用的sideEffects,git中使用的husky,eslint使用的eslintIgnore,這些擴充套件的設定,針對特定的開發工具是有意義的這裡不一一舉例。

更多node相關知識,請存取:!

以上就是淺析nodejs專案中的package.json的常見設定屬性的詳細內容,更多請關注TW511.COM其它相關文章!