[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