package-json 配置完全解讀

package.json 是前端每個項目都有的 json 文件,位於項目的根目錄。許多腳手架在搭建項目時也會自動幫我們自動初始化好 package.json。

package.json 裏面有許許多多的配置,與項目息息相關,瞭解它們有助於瞭解項目,提效開發,規範代碼。

今天主要介紹一些常見配置,我把它們分爲了 7 大類:

  • 描述配置

  • 文件配置

  • 腳本配置

  • 依賴配置

  • 發佈配置

  • 系統配置

  • 第三方配置

1. 描述配置

主要是項目的基本信息,包括名稱,版本,描述,倉庫,作者等,部分會展示在 npm 官網上。

name

項目的名稱,如果是第三方包的話,其他人可以通過該名稱使用 npm install 進行安裝。

"name": "react"

version

項目的版本號,開源項目的版本號通常遵循 semver 語義化規範,具體規則如下圖所示:

簡單介紹一下:

除了 X.Y.Z 這樣的標準版本號,還有 Pre-release 和 Metadata 來描述項目的測試版本,關於 semver 規範更多的內容,可以參考 https://juejin.cn/post/7122240572491825160 。

回到 package.json 的 version 字段,name + version 能共同構成一個完全唯一的項目標識符,所以它兩是最重要的兩個字段。

"version": "18.2.0"

repository

項目的倉庫地址以及版本控制信息。

"repository": {
  "type": "git",
  "url": "https://github.com/facebook/react.git",
  "directory": "packages/react"
}

description

項目的描述,會展示在 npm 官網,讓別人能快速瞭解該項目。

"description": "React is a JavaScript library for building user interfaces."

keywords

一組項目的技術關鍵詞,比如 Ant Design 組件庫的 keywords 如下:

"keywords": [
  "ant",
  "component",
  "components",
  "design",
  "framework",
  "frontend",
  "react",
  "react-component",
  "ui"
 ],

好的關鍵詞可以幫助別人在 npm 官網上更好地檢索到此項目,增加曝光率。

homepage

項目主頁的鏈接,通常是項目 github 鏈接,項目官網或文檔首頁。

"homepage": "https://reactjs.org/"

bugs

項目 bug 反饋地址,通常是 github issue 頁面的鏈接。

"bugs": "https://github.com/vuejs/core/issues"

license

項目的開源許可證。項目的版權擁有人可以使用開源許可證來限制源碼的使用、複製、修改和再發布等行爲。常見的開源許可證有 BSD、MIT、Apache 等,它們的區別可以參考:如何選擇開源許可證?https://www.ruanyifeng.com/blog/2011/05/how_to_choose_free_software_licenses.html

"license": "MIT"

author

項目作者。

"author": "Li jiaxun",

2. 文件配置

包括項目所包含的文件,以及入口等信息。

files

項目在進行 npm 發佈時,可以通過 files 指定需要跟隨一起發佈的內容來控制 npm 包的大小,避免安裝時間太長。

發佈時默認會包括 package.json,license,README 和main 字段裏指定的文件。忽略 node_modules,lockfile 等文件。

在此基礎上,我們可以指定更多需要一起發佈的內容。可以是單獨的文件,整個文件夾,或者使用通配符匹配到的文件。

"files": [
  "filename.js",
  "directory/",
  "glob/*.{js,json}"
 ]

一般情況下,files 裏會指定構建出來的產物以及類型文件,而 src,test 等目錄下的文件不需要跟隨發佈。

type

在 node 支持 ES 模塊後,要求 ES 模塊採用 .mjs 後綴文件名。只要遇到 .mjs 文件,就認爲它是 ES 模塊。如果不想修改文件後綴,就可以在 package.json 文件中,指定 type 字段爲 module。

"type": "module"

這樣所有 .js 後綴的文件,node 都會用 ES 模塊解釋。

# 使用 ES 模塊規範
$ node index.js

如果還要使用 CommonJS 模塊規範,那麼需要將 CommonJS 腳本的後綴名都改成.cjs,不過兩種模塊規範最好不要混用,會產生異常報錯。

main

項目發佈時,默認會包括 package.json,license,README 和main 字段裏指定的文件,因爲 main 字段裏指定的是項目的入口文件,在 browser 和 Node 環境中都可以使用。

如果不設置 main 字段,那麼入口文件就是根目錄下的 index.js

比如 packageA 的 main 字段指定爲 index.js。

"main": "./index.js"

我們引入 packageA 時,實際上引入的就是 node_modules/packageA/index.js

這是早期只有 CommonJS 模塊規範時,指定項目入口的唯一屬性。

browser

main 字段裏指定的入口文件在 browser 和 Node 環境中都可以使用。如果只想在 web 端使用,不允許在 server 端使用,可以通過 browser 字段指定入口。

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

module

同樣,項目也可以指定 ES 模塊的入口文件,這就是 module 字段的作用。

"module": "./index.mjs"

當一個項目同時定義了 main,browser 和 module,像 webpack,rollup 等構建工具會感知這些字段,並會根據環境以及不同的模塊規範來進行不同的入口文件查找。

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

比如 webpack 構建項目時默認的 target 爲 'web',也就是 Web 構建。它的 resolve.mainFeilds 字段默認爲 ['browser', 'module', 'main']。

module.exports = {
  //...
  resolve: {
    mainFields: ['browser', 'module', 'main'],
  },
};

此時會按照 browser -> module -> main 的順序來查找入口文件。

exports

node 在 14.13 支持在 package.json 裏定義 exports 字段,擁有了條件導出的功能。

exports 字段可以配置不同環境對應的模塊入口文件,並且當它存在時,它的優先級最高。

比如使用 requireimport 字段根據模塊規範分別定義入口:

"exports": {
  "require": "./index.js",
  "import": "./index.mjs"
 }
}

這樣的配置在使用 import 'xxx'require('xxx') 時會從不同的入口引入文件,exports 也支持使用 browsernode 字段定義 browser 和 Node 環境中的入口。

上方的寫法其實等同於:

"exports": {
  ".": {
    "require": "./index.js",
    "import": "./index.mjs"
  }
 }
}

爲什麼要加一個層級,把 require 和 import 放在 "." 下面呢?

因爲 exports 除了支持配置包的默認導出,還支持配置包的子路徑。

比如一些第三方 UI 包需要引入對應的樣式文件才能正常使用。

import `packageA/dist/css/index.css`;

我們可以使用 exports 來封裝文件路徑:

"exports": {
  "./style": "./dist/css/index.css'
},

用戶引入時只需:

import `packageA/style`;

除了對導出的文件路徑進行封裝,exports 還限制了使用者不可以訪問未在 "exports" 中定義的任何其他路徑。

比如發佈的 dist 文件裏有一些內部模塊 dist/internal/module ,被用戶單獨引入使用的話可能會導致主模塊不可用。爲了限制外部的使用,我們可以不在 exports 定義這些模塊的路徑,這樣外部引入 packageA/dist/internal/module 模塊的話就會報錯。

結合上面入口文件配置的知識,再來看看下方 vite 官網推薦的第三方庫入口文件的定義,就很容易理解了。

workspaces

項目的工作區配置,用於在本地的根目錄下管理多個子項目。可以自動地在 npm install 時將 workspaces 下面的包,軟鏈到根目錄的 node_modules 中,不用手動執行 npm link 操作。

workspaces 字段接收一個數組,數組裏可以是文件夾名稱或者通配符。比如:

"workspaces": [
  "workspace-a"
]

表示在 workspace-a 目錄下還有一個項目,它也有自己的 package.json。

package.json
workspace-a
  └── package.json

通常子項目都會平鋪管理在 packages 目錄下,所以根目錄下 workspaces 通常配置爲:

"workspaces": [
  "packages/*"
]

3. 腳本配置

scripts

指定項目的一些內置腳本命令,這些命令可以通過 npm run 來執行。通常包含項目開發,構建 等 CI 命令,比如:

"scripts": {
  "build": "webpack"
}

我們可以使用命令 npm run build / yarn build 來執行項目構建。

除了指定基礎命令,還可以配合 pre 和 post 完成命令的前置和後續操作,比如:

"scripts": {
  "build": "webpack",
  "prebuild": "xxx", // build 執行之前的鉤子
  "postbuild": "xxx" // build 執行之後的鉤子
}

當執行 npm run build 命令時,會按照 prebuild -> build -> postbuild 的順序依次執行上方的命令。

但是這樣的隱式邏輯很可能會造成執行工作流的混亂,所以 pnpm 和 yarn2 都已經廢棄掉了這種 pre/post 自動執行的邏輯,參考 pnpm issue 討論 https://github.com/pnpm/pnpm/issues/2891

如果需要手動開啓,pnpm 項目可以設置 .npmrc enable-pre-post-scripts=true。

enable-pre-post-scripts=true

config

config 用於設置 scripts 裏的腳本在運行時的參數。比如設置 port 爲 3001:

"config"{
  "port""3001"
}

在執行腳本時,我們可以通過 npm_package_config_port 這個變量訪問到 3001。

console.log(process.env.npm_package_config_port); // 3001

4. 依賴配置

項目可能會依賴其他包,需要在 package.json 裏配置這些依賴的信息。

dependencies

運行依賴,也就是項目生產環境下需要用到的依賴。比如 react,vue,狀態管理庫以及組件庫等。

使用 npm install xxx 或則 npm install xxx --save 時,會被自動插入到該字段中。

"dependencies": {
  "react": "^18.2.0",
  "react-dom": "^18.2.0"
}

devDependencies

開發依賴,項目開發環境需要用到而運行時不需要的依賴,用於輔助開發,通常包括項目工程化工具比如 webpack,vite,eslint 等。

使用 npm install xxx -D 或者 npm install xxx --save-dev 時,會被自動插入到該字段中。

"devDependencies": {
  "webpack": "^5.69.0"
}

peerDependencies

同伴依賴,一種特殊的依賴,不會被自動安裝,通常用於表示與另一個包的依賴與兼容性關係來警示使用者。

比如我們安裝 A,A 的正常使用依賴 B@2.x 版本,那麼 B@2.x 就應該被列在 A 的 peerDependencies 下,表示 “如果你使用我,那麼你也需要安裝 B,並且至少是 2.x 版本”。

比如 React 組件庫 Ant Design,它的 package.json 裏 peerDependencies 爲

"peerDependencies": {
  "react": ">=16.9.0",
  "react-dom": ">=16.9.0"
}

表示如果你使用 Ant Design,那麼你的項目也應該安裝 react 和 react-dom,並且版本需要大於等於 16.9.0。

optionalDependencies

可選依賴,顧名思義,表示依賴是可選的,它不會阻塞主功能的使用,安裝或者引入失敗也無妨。這類依賴如果安裝失敗,那麼 npm 的整個安裝過程也是成功的。

比如我們使用 colors 這個包來對 console.log 打印的信息進行着色來增強和區分提示,但它並不是必需的,所以可以將其加入到 optionalDependencies,並且在運行時處理引入失敗的邏輯。

使用 npm install xxx -O 或者 npm install xxx --save-optional 時,依賴會被自動插入到該字段中。

"optionalDependencies": {
  "colors": "^1.4.0"
}

peerDependenciesMeta

同伴依賴也可以使用 peerDependenciesMeta 將其指定爲可選的。

"peerDependencies": {
  "colors": "^1.4.0"
},
"peerDependenciesMeta": {
  "colors": {
    "optional": true
   }
 }

bundleDependencies

打包依賴。它的值是一個數組,在發佈包時,bundleDependencies 裏面的依賴都會被一起打包。

比如指定 react 和 react-dom 爲打包依賴:

"bundleDependencies": [
  "react",
  "react-dom"
]

在執行 npm pack 打包生成 tgz 壓縮包中,將出現 node_modules 幷包含 react 和 react-dom。

需要注意的是,這個字段數組中的值必須是在 dependencies,devDependencies 兩個裏面聲明過的依賴纔行。

普通依賴通常從 npm registry 安裝,但當你想用一個不在 npm registry 裏的包,或者一個被修改過的第三方包時,打包依賴會比普通依賴更好用。

overrides

overrides 可以重寫項目依賴的依賴,及其依賴樹下某個依賴的版本號,進行包的替換。

比如某個依賴 A,由於一些原因它依賴的包 foo@1.0.0 需要替換,我們可以使用 overrides 修改 foo 的版本號:

"overrides": {
  "foo": "1.1.0-patch"
}

當然這樣會更改整個依賴樹裏的 foo,我們可以只對 A 下的 foo 進行版本號重寫:

"overrides": {
  "A": {
    "foo": "1.1.0-patch",
  }
}

overrides 支持任意深度的嵌套。

如果在 yarn 裏也想複寫依賴版本號,需要使用 resolution 字段,而在 pnpm 裏複寫版本號需要使用 pnpm.overrides 字段。

5. 發佈配置

主要是和項目發佈相關的配置。

private

如果是私有項目,不希望發佈到公共 npm 倉庫上,可以將 private 設爲 true。

"private": true

publishConfig

顧名思義,publishConfig 就是 npm 包發佈時使用的配置。

比如在安裝依賴時指定了 registry 爲 taobao 鏡像源,但發佈時希望在公網發佈,就可以指定 publishConfig.registry。

"publishConfig": {
  "registry": "https://registry.npmjs.org/"
}

6. 系統配置

和項目關聯的系統配置,比如 node 版本或操作系統兼容性之類。這些要求只會起到提示警告的作用,即使用戶的環境不符合要求,也不影響安裝依賴包。

engines

一些項目由於兼容性問題會對 node 或者包管理器有特定的版本號要求,比如:

"engines": {
  "node": ">=14 <16",
  "pnpm": ">7"
}

要求 node 版本大於等於 14 且小於 16,同時 pnpm 版本號需要大於 7。

os

在 linux 上能正常運行的項目可能在 windows 上會出現異常,使用 os 字段可以指定項目對操作系統的兼容性要求。

"os": ["darwin", "linux"]

cpu

指定項目只能在特定的 CPU 體系上運行。

"cpu": ["x64", "ia32"]

7. 第三方配置

一些第三方庫或應用在進行某些內部處理時會依賴這些字段,使用它們時需要安裝對應的第三方庫。

types 或者 typings

指定 TypeScript 的類型定義的入口文件

"types": "./index.d.ts",

unpkg

可以讓 npm 上所有的文件都開啓 CDN 服務。

比如 vue package.json 的 unpkg 定義爲 dist/vue.global.js

"unpkg": "dist/vue.global.js",

當我們想通過 CDN 的方式使用鏈接引入 vue 時。

訪問 https://unpkg.com/vue 會重定向到 https://unpkg.com/vue@3.2.37/dist/vue.global.js,其中 3.2.27 是 Vue 的最新版本。

jsdelivr

與 unpkg 類似,vue 通過如下的配置

"jsdelivr": "dist/vue.global.js",

訪問 https://cdn.jsdelivr.net/npm/vue 實際上獲取到的是 jsdelivr 字段裏配置的文件地址。

browserslist

設置項目的瀏覽器兼容情況。babel 和 autoprefixer 等工具會使用該配置對代碼進行轉換。當然你也可以使用 .browserslistrc 單文件配置。

"browserslist": [
  "> 1%",
  "last 2 versions"
]

sideEffects

顯示設置某些模塊具有副作用,用於 webpack 的 tree-shaking 優化。

比如在項目中整體引入 Ant Design 組件庫的 css 文件。

import 'antd/dist/antd.css'; // or 'antd/dist/antd.less'

如果 Ant Design 的 package.json 裏不設置 sideEffects,那麼 webapck 構建打包時會認爲這段代碼只是引入了但並沒有使用,可以 tree-shaking 剔除掉,最終導致產物缺少樣式。

所以 Ant Design 在 package.json 裏設置瞭如下的 sideEffects,來告知 webpack,這些文件具有副作用,引入後不能被刪除。

"sideEffects": [
  "dist/*",
  "es/**/style/*",
  "lib/**/style/*",
  "*.less"
]

lint-staged

lint-staged 是用於對 git 的暫存區的文件進行操作的工具,比如可以在代碼提交前執行 lint 校驗,類型檢查,圖片優化等操作。

"lint-staged": {
  "src/**/*.{js,jsx,ts,tsx}": [
    "eslint --fix",
    "git add -A"
  ]
}

lint-staged 通常配合 husky 這樣的 git-hooks 工具一起使用。git-hooks 用來定義一個鉤子,這些鉤子方法會在 git 工作流程中比如 pre-commit,commit-msg 時觸發,可以把 lint-staged 放到這些鉤子方法中。

8. 結語

今天我們簡單瞭解了 package.json 的常見配置。有了這些知識,我敢說絕大多數項目的 package.json 你都能毫無壓力的閱讀。

但 package.json 裏的內容遠不止如此,比如 semver 規範,入口文件,項目依賴等都還有很多值得深入的內容,認識他們只是第一步。

9. 參考

https://docs.npmjs.com/cli/v8/configuring-npm/package-json

https://zhuanlan.zhihu.com/p/548202395 by WangHaoyu

https://juejin.cn/post/7095903278084390948 by 摸魚的春哥

https://juejin.cn/post/7122240572491825160 by 摸魚的春哥

https://zhuanlan.zhihu.com/p/29855253 by 風清洋

https://juejin.cn/post/7023539063424548872 by CUGGZ

https://segmentfault.com/a/1190000016365409 by senntyou

https://github.com/SunshowerC/blog/issues/8 by SunshowerC

https://www.ruanyifeng.com/blog/2020/08/how-nodejs-use-es6-module.html by 阮一峯

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