[TOC]
# 結構
舉例`pinia`包的`package.json`內容如下:
```
{
"name": "pinia",
"version": "2.0.9",
"description": "Intuitive, type safe and flexible Store for Vue",
"main": "index.js",
"module": "dist/pinia.mjs",
"unpkg": "dist/pinia.iife.js",
"jsdelivr": "dist/pinia.iife.js",
"types": "dist/pinia.d.ts",
"exports": {
".": {
"browser": "./dist/pinia.esm-browser.js",
"node": {
"import": {
"production": "./dist/pinia.prod.cjs",
"development": "./dist/pinia.mjs",
"default": "./dist/pinia.mjs"
},
"require": {
"production": "./dist/pinia.prod.cjs",
"development": "./dist/pinia.cjs",
"default": "./index.js"
}
},
"import": "./dist/pinia.mjs"
},
"./package.json": "./package.json",
"./dist/*": "./dist/*"
},
"sideEffects": false,
"author": {
"name": "Eduardo San Martin Morote",
"email": "posva13@gmail.com"
},
"funding": "https://github.com/sponsors/posva",
"scripts": {
"build": "rimraf dist && rollup -c ../../rollup.config.js --environment TARGET:pinia",
"build:dts": "api-extractor run --local --verbose && tail -n +3 ./src/globalExtensions.ts >> dist/pinia.d.ts",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s --commit-path . -l pinia -r 1",
"test:dts": "tsc -p ./test-dts/tsconfig.json",
"test": "yarn run build && yarn run build:dts && yarn test:dts"
},
"files": [
"dist/*.js",
"dist/*.mjs",
"dist/*.cjs",
"dist/pinia.d.ts",
"index.js",
"index.cjs",
"LICENSE",
"README.md"
],
"keywords": [
"vue",
"vuex",
"store",
"pinia",
],
"license": "MIT",
"devDependencies": {
"@microsoft/api-extractor": "7.19.2",
"@vue/compiler-sfc": "^3.2.26",
"@vue/server-renderer": "^3.2.26",
"@vue/test-utils": "^2.0.0-rc.17",
"vue": "^3.2.26",
"vue2": "npm:vue@2"
},
"dependencies": {
"@vue/devtools-api": "^6.0.0-beta.21",
"vue-demi": "*"
},
"peerDependencies": {
"@vue/composition-api": "^1.4.0",
"typescript": ">=4.4.4",
"vue": "^2.6.14 || ^3.2.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
},
"@vue/composition-api": {
"optional": true
}
},
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/pinia.git"
},
"bugs": {
"url": "https://github.com/vuejs/pinia/issues"
},
"homepage": "https://github.com/vuejs/pinia#readme"
}
```
其中 `package.json` 中設置的所有字段,都會被設置為 **`npm_package_`** 開頭的環境變量。
可以得到 `npm_package_name`、`npm_package_version`、`npm_package_scripts_build`、`npm_package_browserslist_production_0` 等變量。
不止 `package.json`,npm 相關的所有配置也會有 **`npm_config_`** 開頭的環境變量。
> [package.json 非官方字段集合](https://github.com/senntyou/blogs/blob/master/web-extend/3.md)
# package-lock.json
加上`--no-save`選項即可防止`npm install`命令對`package.json`和`package-lock.json`的更改:
~~~
npm install --no-save
~~~
> [關于 `package-lock.json` 的一切](https://codertx.github.io/2018/01/09/about-package-json/)
# 設置 nodejs 的 global 和 cache 路徑
設置路徑能夠把通過npm 安裝的模塊集中在一起,便于管理。
在nodejs 的安裝目錄 `D:\nodejs\` 下,新建`node_global`和 `node_cache` 兩個文件夾
執行指令:
```
npm config set prefix "D:\nodejs\node_global"
npm config set cache "D:\nodejs\node_cache"
```
設置成功后,后續用命令 `npm install -g XXX` 安裝的 XXX模塊就在 `D:\nodejs\node_global\node_modules` 里。
查看配置信息指令: `npm config list`
# npm script
可以通過命令行的方式:在`package.json`中新增 prepare scripts:
~~~shell
npm set-script prepare "husky install" && npm run prepare
~~~
`npm run XXX`是執行配置在`package.json`中的腳本,比如:
```
"scripts": {
"dev": "node build/dev-server.js",
"build": "node build/build.js",
"unit": "karma start test/unit/karma.conf.js --single-run",
"e2e": "node test/e2e/runner.js",
"test": "npm run unit && npm run e2e",
"lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs"
},
```
只有這里配置了,你才能 run,所以不是所有的項目都能`npm run dev/build`。要了解這些命令做了什么,就要去scripts中看具體執行的是什么代碼。這里就像是一些命令的快捷方式,免去每次都要輸入很長的的命令(比如unit那行)。
為什么會出現`ERROR`,就是因為在跑這些對應的腳本文件的時候,可能是某些依賴沒有被加載等的。
一般項目都會有 build, dev, unit等,從名字上基本能看出來是干什么的。
比如上面配置的 unit,就是開啟 karma 去跑單元測試,具體測試內容,要去看`karma.conf.js`;
e2e就是 End to End 的端到端測試;
而 test 則會將單元測試和端到端測試都執行。
有些項目中根據需要,還會配置其他命令,例如自動生成文檔,比如這里:
~~~
"build:doc": "node ./scripts/build-doc.js",
~~~
如果你去`build-doc.js`中看的話,會發現,這個腳本在遍歷所有源文件,解析注釋和其他內容,自動生成API文檔
> [用 npm-run 自動化任務](http://blog.csdn.net/yy374864125/article/details/40740073)
## 多 script 運行
因為 npm scripts 在內部實際產生的是一個`shell`進程,所以我們可以使用`shell`語法來實現我們所需要的功能。 具體來說:
* 使用 `;`(不管失敗與否,所有命令都會被執行) (或者 `&&`(**任何一個串聯命令失敗,則停止后續的所有命令執行**)) 來串聯運行
* 使用 `&` 來并行運行。
使用此語法的示例如下所示:
并行:
```
"scripts": {
...,
"lint:bx": "npm run lint:js & npm run lint:jsx & npm run lint:css & npm run lint:json & npm run lint:markdown"
}
```
> 注: `&`語法會創建一個子進程,這會導致無法判斷原始的 npm 進程是否已經完成。這可能是有問題的,特別是長時間運行 scripts 時。并行命令,為了穩定復現一些錯誤,可在命令最后加上 `& wait`。另外,加上 `& wait` 的好處還有,如果我們在子命令啟動長時間運行的進程,可用 `ctrl + c` 來結束進程。
串行:
```
"scripts": {
...,
"build": "babel; jest"
}
```
## 使用 hook scripts (鉤子腳本)
npm 中的每條 script 在引擎內部都會運行三個單獨的 script 。
1. `pre` scripts
2. scripts 本身
3. `post` scripts
這兩個額外運行的 scripts ,正如他們的名字所描述的那樣,就是在該 scripts 運行前 和該 scripts 運行后運行的腳本。
例如,在部署期間,它們可用來做一些設置和清理。
這兩個 scripts 使用與之前`scripts`名稱相同的`pre[scriptname]`和`post[scriptname]`來表示。
假設我們要構建我們的項目,這將是一個非常簡單的例子,只是為了展示這個概念。
我們會做的是這樣的:
* 創建一個新的一個`dist`目錄,如果這個目錄已經存在,那么從中刪除所有內容
* 創建`tmp`目錄
* 將項目構建到`tmp`目錄
* 將我們的包壓縮到到`dist`目錄
* 刪除`tmp`目錄
`package.json` 代碼:
~~~
"scripts": {
...,
"prebuild": "mkdir dist tmp; rm -rf dist/*",
"build": "browserify main.js -o tmp/bundle.js && uglifyjs -o dist/bundle.min.js -- tmp/bundle.js",
"postbuild": "rm -rf tmp"
}
~~~
現在,每當運行`npm run build`時,它會觸發所有命令,并且保證他們以正確的順序被執行。
# 傳入參數
對于上面的腳本`"test": "mocha"`如果希望給 mocha 傳入一些選項,比如希望執行:
~~~shell
mocha --reporter spec
~~~
需要這樣執行 npm test:
~~~shell
npm test -- --reporter spec
~~~
需要使用**兩個短線**將選項隔開,或者將選項直接寫在`package.json`中:
~~~js
"scripts":{
"test": "mocha --reporter spec"
}
~~~
在 shell 中傳入的參數都要使用`--`隔開,這個`--`被視作 npm run 命令參數的結束,`--`后面的內容都會原封不動地傳給運行的命令。
> [NPM 相關知識](https://github.com/wy-ei/notebook/issues/42)
# 設置環境變量
添加用戶變量PATH:`D:\nodejs\node_global`
新增系統變量NODE_PATH:`D:\nodejs\node_global\node_modules`
# `peerDependencies`
它會告訴`npm`:如果某個`package`依賴我,那么這個`package` 也應該對`peer-dependencies-plugin-core`依賴,這個時候,你 `npm install peer-dependencies-plugin` 的時候,將得到下面這樣的目錄:
```
├──package.json
├──src
│ └──index.js
└──node_modules
└──peer-dependencies-plugin-core
└──peer-dependencies-plugin
└──node_modules
└──peer-dependencies-plugin-core
```
這勢必會靠成很多不必要的麻煩,首當齊沖的就是,你的項目依賴的是`1.0.0`,而你依賴的另一個插件卻只能支持到`0.0.8`,這個時候,導致一個項目里面依賴了兩次`peer-dependencies-plugin-core`,而且還不是同一個版本。
npm 3 中不會再要求 `peerDependencies` 所指定的依賴包被強制安裝,相反 npm 3 會在安裝結束后檢查本次安裝是否正確,如果不正確會給用戶打印警告提示。
我們在 `webpack-plugin-a@1.0.0` 的 `package.json` 中添加如下配置:
```
"peerDependencies": {
"webpack": "^2.0.0"
}
```
這樣就指定了 `webpack-plugin-a@1.0.0` 只兼容 `webpack@2.x.x`,當用戶同時安裝 `webpack@3.0.0` 和 `webpack-plugin-a@1.0.0`的時候就會拋出:
```
> UNMET PEER DEPENDENCY webpack@3.0.0
> npm WARN webpack-plugin-a@1.0.0 requires a peer of webpack@^2.0.0 but none was installed
```
> [peerDependencies 的理解](https://xwenliang.cn/p/5af2a97d5a8a996548000003)
## 什么時候使用`peerDependencies`?
通常是在插件開發的場景下,你的插件需要某些依賴的支持,但是你又沒必要去安裝,因為使用該插件的宿主會去安裝這些依賴,你就可以用 `peerDependencies` 去聲明一下需要依賴的插件和版本,如果出問題 npm 就會有警告來提醒使用者去解決版本沖突問題。
`dependencies`及`devDependencies`常見,而`peerDependencies`并不是。`peerDependencies` 不會被自動安裝。
示例:當一個依賴項 c 被列在某個包 b 的 peerDependency 中時,**它就不會被自動安裝**。取而代之的是,包含了 b 包的代碼庫 a 則必須將對應的依賴項 c 包含為其依賴。(npm 3 中只會打印警告提示)
*a/package.json*:
```
{
//...
"dependencies": {
"b": "1.x",
"c": "1.x"
}
}
```
> [探討npm依賴管理之peerDependencies](https://www.cnblogs.com/wonyun/p/9692476.html)
> [package.json 文件中的 peerDependencies](https://pantao.parcmg.com/press/peer-dependencies-in-package.html)
# `package.json` 其他配置字段
`package.json` 中可以配置很多字段。其他程序會去自動讀取其配置或者該文件中的配置字段~!比如:babel、eslint、browserslist等。
# Semantic Versioning
在NPM包依賴項中經常使用的版本是`脫字符號(又叫 插入符號^ )范圍`或`版本`。npm的安裝也使用了`脫字符號范圍`。
脫字符號范圍:
`[major, minor, patch] (主要、次要、補丁)`,這個元祖中請不要修改最左邊的非零位。換言之,這允許**補丁**和**版本次要更新**`1.0.0或以上`,**補丁更新**為`版本0.X> = 0.1.0`,并且**沒有進行版本更新**`0.0.x`。
例如:
```
* ^1.2.3 := >=1.2.3 =1.2.3,并且<2.0.0。)
* ^0.2.3 := >=0.2.3 <0.3.0
* ^0.0.3 := >=0.0.3 <0.0.4
* ^1.2.3-beta.2 := >=1.2.3-beta.2 <2.0.0 Note that prereleases in the 1.2.3 version will be allowed, if they are greater than or equal to beta.2. So, 1.2.3-beta.4 would be allowed, but 1.2.4-beta.2 would not, because it is a prerelease of a different \[major, minor, patch\] tuple.
* ^0.0.3-beta := >=0.0.3-beta <0.0.4 Note that prereleases in the 0.0.3 version only will be allowed, if they are greater than or equal to beta. So, 0.0.3-pr.2 would be allowed.
When parsing caret ranges, a missing patch value desugars to the number 0, but will allow flexibility within that value, even if the major and minor versions are both 0.
* ^1.2.x := >=1.2.0 <2.0.0
* ^0.0.x := >=0.0.0 <0.1.0
* ^0.0 := >=0.0.0 <0.1.0
A missing minor and patch values will desugar to zero, but also allow flexibility within those values, even if the major version is zero.
* ^1.x := >=1.0.0 <2.0.0
* ^0.x := >=0.0.0 <1.0.0
```
大多數情況下,使用插入符號范圍作為依賴版本工作完全正常。但是有時候會出現bug。在一個項目中,使用并安裝了帶有插入符號版本`^3.4.3`的[JS-YAML](https://github.com/nodeca/js-yaml):
```json
{
"dependencies": {
"js-yaml": "^3.4.3"
}
}
```
過了段時間。當在一個新克隆的項目代碼庫再次運行`npm install`時,安裝了`3.5.2`版本。由于js-yaml版本`3.5.0`,當安全加載帶有重復密鑰的規范時,會拋出錯誤。如果在YAML文件中沒有重復的鍵,這是好的。然而,其中一個文件有它。正確的方法是修復重復的密鑰。但這需要額外的工作。你以前聽過這句話:“我當時安裝它的時候,它工作得很好,現在怎么不行了”。
這里的要點是使用精確的版本,而不是讓包管理器通過刪除脫字符來決定:
```json
{
"dependencies": {
"js-yaml": "3.4.3"
}
}
```
這將避免上述問題。我們可以手動[更新過時的NPM包](https://realguess.net/2014/12/13/update-outdated-npm-packages/)。
不要讓機器來決定。自己動手去做!
> [What's the difference between tilde(~) and caret(^) in package.json?](https://stackoverflow.com/questions/22343224/whats-the-difference-between-tilde-and-caret-in-package-json)
> [Semver explained - why is there a caret (^) in my package.json?](https://bytearcher.com/articles/semver-explained-why-theres-a-caret-in-my-package-json/)
> [Versions of dependencies | Yarn](https://classic.yarnpkg.com/en/docs/dependency-versions/)
# 詳細查看安裝過程
**Append the`--loglevel verbose`argument to the command you want to run** and all logs will be shown on STDERR and saved to`npm-debug.log`file in the current working directory.
Example usage:
```
npm install ionic --loglevel verbose
```
Running the`npm`commands like this, shows the logs in realtime and saves the logs to the directory its running within.
```
npm config set loglevel verbose
```
For permanent solution, just edit the global`npm`configuration. To do this, run`npm config edit`command and add`loglevel=verbose`. Now every`npm`command will show detailed logs
# 生成多入口的包
## [Package.json with multiple entrypoints](https://stackoverflow.com/questions/63058081/package-json-with-multiple-entrypoints)
# `exports`字段
`"exports"` 字段算是 `"main"` 的替代品,它既可以定義包的主入口(`main`),又封閉了包,**防止其他未被定義的內容被訪問**。這種封閉允許模塊作者為他們的包定義公共接口。
如果同時定義了 `"exports"` 和 `"main"`,在支持`"exports"`的 Node(>= v12.7.0) 中`"exports"`會覆蓋`"main"`,否則`"main"`生效。因此, `"main"` **不能作為 CommonJS 的降級回落,但它可以作為不支持** `"exports"` **字段的 Node.js 舊版本的降級回落**。
在 `"exports"` 中使用“條件導出”(Conditional exports)可以為每個環境定義不同入口,包括包是通過 `require` 還是 `import` 來引用。
**注意**:使用 `"exports"` 字段可以防止包的使用者使用其他未定義的入口點,包括 `package.json`(例如:`require('your-package/package.json')`。**這很可能是一個重大變更**。
為了使 `"exports"` 的引入不具有破壞性,請確保之前支持的每個入口都被導出。最好明確指定各個入口,這樣包的就有了明確的公共API定義。
```json
{
...
"types": "dist/pinia.d.ts",
"name": "my-package",
"type": "module",
"exports": {
".": {
// Entry-point for `import "my-package"` in ESM
"import": "./esm/index.js",
// Entry-point for `require("my-package") in CJS
"require": "./commonjs/index.cjs",
// Entry-point for TypeScript resolution
"types": "./types/index.d.ts"
},
},
// CJS fall-back for older versions of Node.js
"main": "./commonjs/index.cjs",
// Fall-back for older versions of TypeScript
"types": "./types/index.d.ts",
// 自定義子路徑
"./client": {
"types": "./client.d.ts"
},
"./dist/client/*": "./dist/client/*",
"./package.json": "./package.json"
},
"sideEffects": false,
...
}
```
當使用 `"exports"` 字段時,可以將主入口視為 `"."` 路徑,然后構造自定義路徑:
```
{
"main": "./main.js",
"exports": {
".": "./main.js",
"./submodule": "./src/submodule.js"
}
}
```
目前只有在 `"exports"` 中定義的子路徑才能被導入:
```
import submodule from 'es-module-package/submodule';
// 加載 ./node_modules/es-module-package/src/submodule.js
```
導入其他子路徑就會報錯:
```
import submodule from 'es-module-package/private-module.js';
// 拋錯 ERR_PACKAGE_PATH_NOT_EXPORTED
```
可以參考`vite` 包的`package.json`設置的比較全面。目前 typescript 對該字段的解析還是支持的不夠完善,但是已經納入下個計劃了。
> [Node 最新 Module 導入導出規范 - 掘金 (juejin.cn)](https://juejin.cn/post/6972006652631318564#heading-16)
> [New package.json `exports` field not working with TypeScript](https://stackoverflow.com/questions/58990498/new-package-json-exports-field-not-working-with-typescript)
> [Support for NodeJS 12.7+ package exports · Issue #33079 · microsoft/TypeScript (github.com)](https://github.com/microsoft/TypeScript/issues/33079)
# 工具
[Package Dependency Graph for npm](http://npm-dependencies.com/)
[pastelsky/bundlephobia](https://github.com/pastelsky/bundlephobia) 了解在應用程序包中包含npm包的性能影響。
# 附錄
npm.io
[npm scripts : 每個前端開發都應知道的一些使用提示](https://www.html.cn/archives/8029)
[Docco](http://ashkenas.com/docco/)
- 講解 Markdown
- 示例
- SVN
- Git筆記
- github 相關
- DESIGNER'S GUIDE TO DPI
- JS 模塊化
- CommonJS、AMD、CMD、UMD、ES6
- AMD
- RequrieJS
- r.js
- 模塊化打包
- 學習Chrome DevTools
- chrome://inspect
- Chrome DevTools 之 Elements
- Chrome DevTools 之 Console
- Chrome DevTools 之 Sources
- Chrome DevTools 之 Network
- Chrome DevTools 之 Memory
- Chrome DevTools 之 Performance
- Chrome DevTools 之 Resources
- Chrome DevTools 之 Security
- Chrome DevTools 之 Audits
- 技巧
- Node.js
- 基礎知識
- package.json 詳解
- corepack
- npm
- yarn
- pnpm
- yalc
- 庫處理
- Babel
- 相關庫
- 轉譯基礎
- 插件
- AST
- Rollup
- 基礎
- 插件
- Webpack
- 詳解配置
- 實現 loader
- webpack 進階
- plugin 用法
- 輔助工具
- 解答疑惑
- 開發工具集合
- 花樣百出的打包工具
- 紛雜的構建系統
- monorepo
- 前端工作流
- 爬蟲
- 測試篇
- 綜合
- Jest
- playwright
- Puppeteer
- cypress
- webdriverIO
- TestCafe
- 其他
- 工程開發
- gulp篇
- Building With Gulp
- Sass篇
- PostCSS篇
- combo服務
- 編碼規范檢查
- 前端優化
- 優化策略
- 高性能HTML5
- 瀏覽器端性能
- 前后端分離篇
- 分離部署
- API 文檔框架
- 項目開發環境
- 基于 JWT 的 Token 認證
- 扯皮時間
- 持續集成及后續服務
- 靜態服務器搭建
- mock與調試
- browserslist
- Project Starter
- Docker
- 文檔網站生成
- ddd