package.json 配置详解
简介
package.json 是前端每个项目都有的 json 文件,位于项目的根目录。许多脚手架在搭建项目时也会自动帮我们自动初始化好 package.json。
package.json 里面有许许多多的配置,与项目息息相关,了解它们有助于了解项目,提效开发,规范代码。
使用教程
当我们手动创建项目时,可以在根目录使用 npm init,然后根据提示一步步输入相应的内容完成后即可自动创建,当然你也可以使用 npm init -y 跳过交互直接生成。
npm init
前提条件:已配置好
node.js与npm环境
自动生成 package.json 文件:
{
"name": "project_name",
"version": "1.0.0",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": ""
}
配置选项
描述配置
name
它表示项目名称,该字段决定了你发布的包在 npm 的名字,也就是平时安装依赖的包名。
如果你希望发布的包属于某个组织或团队,可以使用 scoped 作用域,名称以 @scope/ 开头,例如 @your-org/package-name。
其中 Scoped 允许你创建一个命名空间,从而避免命名冲突。例如,如果你有一个名为 utils 的包,可以将其命名为 @your-org/utils,这样即使其他开发者也有一个名为 utils 的包,也不会产生冲突。
一些规则:
- 名称必须小于或等于 214 个字符。这包括范围包的范围。
- 作用域包的名称可以以点或下划线开头。如果没有范围,这是不允许的。
- 新包的名称中不得包含大写字母。
- 该名称最终成为 URL、命令行参数和文件夹名称的一部分。因此,名称不能包含任何非 URL 安全字符。
{
"name": "@your_org/package_name"
}
version
它表示项目的版本号,该 version 属性必须采用 major.minor.patch 格式的形式。它还必须遵循 语义版本控制准则。
{
"version": "1.0.0"
}
一般来讲如果你计划发布包,则 package.json 中最重要的内容就是 name 和 version ,因为它们是必需的。名称和版本一起形成一个标识符,假定该标识符是完全唯一的。对包的更改应该伴随着对版本的更改。如果你不打算发布包,则 name 和 version 字段是可选的。
description
它表示项目的描述信息,该内容会直接展示在 npm 官网,它主要是为了让其他人快速了解的项目
{
"description": ""
}
keywords
它表示项目的关键字,它是一个数组,它可以方便其他人更好的搜索
{
"keywords": ["vue", "vue-cli", "cli"]
}
好的关键词可以帮助别人在 npm 官网上更好地检索到此项目,增加曝光率。
homepage
项目主页的链接,通常是项目 github 链接,项目官网或文档首页。
{
"homepage": "https://github.org/"
}
bugs
它表示项目问题的提交地址,这个一般在开源项目中见的多
- 简略
{
"bugs": "https://github.com/vuejs/core/issues"
}
- 完整
{
"bugs": {
"email": "xxx@xx.com",
"url": "http://github.com/***/issues"
}
}
author
它表示项目的作者信息,它既可以是一个字符串,又可以是一个对象
- 简写
{
"author": "xxx"
}
- 完整
{
"author": {
"name": "xxx",
"email": "xxx@163.com",
"url": "https://xxx.github.io/xxx/"
}
}
其中 email、url 是可选的
repository
它表示项目的仓库地址
- 简写
{
"repository": "https://github.com/***"
}
- 完整
{
"repository": {
"type": "git",
"url": "http://github.com/<author>/<repo_name>.git",
"directory": "<author>" // Monorepo:packages/<sub_package_name>
}
}
contributors
它表示项目的贡献者,该字段是一个数组
{
"contributors": [
{
"name" : "author_name",
"email" : "xxx@xx.com",
"url" : "https://xxx.github.io/xxx/"
},
]
}
license
项目的开源许可证。项目的版权拥有人可以使用开源许可证来限制源码的使用、复制、修改和再发布等行为。常见的开源许可证有 BSD、MIT、Apache 等。
{
"license": "MIT"
}
它们的区别可以参考:如何选择开源许可证?
文件配置
bin
定义了一系列可执行命令, 在全局安装的命令行包里尤其多见;带有 bin 信息的包, 在局部安装后, 可执行文件会在 ./node_modules/.bin 下,;如果是全局安装, 可执行文件会在 $PATH 里对应 npm 那个目录下.
带有 bin 信息的包, 在局部安装后, 可执行文件会在 ./node_modules/.bin 下, 如果是全局安装, 可执行文件会在 $PATH 里对应 npm 那个目录下.
{
"bin": {
"pm2": "./bin/pm2",
"pm2-dev": "./bin/pm2-dev",
"pm2-docker": "./bin/pm2-docker",
}
}
files
项目在进行 npm 发布时,可以通过 files 指定需要跟随一起发布的内容来控制 npm 包的大小,避免安装时间太长。有点类似 .gitignore,但功能与之相反。
默认包含某些文件:
package.jsonREADMELICENSE/LICENCEmain字段中的文件
忽略 node_modules,lockfile 等文件。
在此基础上,我们可以指定更多需要一起发布的内容。可以是单独的文件,整个文件夹,或者使用通配符匹配到的文件。
{
"files": [
"index.js",
"dist",
"bin/*.{js,json}"
]
}
一般情况下,files 里会指定构建出来的产物以及类型文件,而 src,test 等目录下的文件不需要跟随发布。
type(非官方字段)
它表示指定使用那种模块方式,默认值为 commonjs
在 node 支持 ES 模块后,要求 ES 模块采用 .mjs 后缀文件名。只要遇到 .mjs 文件,就认为它是 ES 模块。如果不想修改文件后缀,就可以在 package.json 文件中,指定 type 字段为 module。
{
"type": "module"
}
这样所有 .js 后缀的文件,node 都会用 ES 模块解释。
node index.js
如果还要使用 CommonJS 模块规范,那么需要将 CommonJS 脚本的后缀名都改成 .cjs,不过两种模块规范最好不要混用,会产生异常报错。
main
它表示项目的入口文件,如果不指定该字段,默认是根目录下的 index.js
{
"main": "dist/index.js"
}
这是早期只有 CommonJS 模块规范时,指定项目入口的唯一属性。从 Node.js 12.20.0 版本开始,main 字段也可以指定 ES 模块的入口文件。
browser
它表示 UMD 模块的入口文件
UMD:兼容 CommonJS 和 AMD 的模块,既可以在 node 环境中被 require 引用,也可以在浏览器中直接用 CDN 被 script 标签 引入
main 字段里指定的入口文件在 browser 和 node 环境中都可以使用。如果只想在 web 端使用,禁止在 server 端使用,可以通过 browser 字段指定入口。
{
"browser": "./dist/index.js"
}
module(非官方字段)
它表示 ES 模块入口文件,浏览器环境和 node 环境均可使用
{
"module": "./dist/index.js"
}
当一个项目同时定义了 main,browser 和 module,像 webpack,rollup 等构建工具会感知这些字段,并会根据环境以及不同的模块规范来进行不同的入口文件查找。
在 web 环境中,如果使用 loader 加载 ESM(ES module),那么这三个配置的加载顺序是 browser -> module -> main,如果使用 require 加载 CommonJS 模块,则加载的顺序为 main -> module -> browser。
main、browser、module 三个字段都是用于 npm 包的,如果项目不是作为 npm 包发布,这三个字段不需要写。
- 导出包只在 web 端使用,并且禁止在 server 端使用,使用
browser - 导出包只在 server 端使用,使用
main - 导出 ESM 规范的包,使用
module - 导出包在 web 端和 server 端都允许使用,使用
module和main
exports(非官方字段)
node 在 14.13 支持在 package.json 里定义 exports 字段,拥有了条件导出的功能。
exports 字段可以配置不同环境对应的模块入口文件,并且当它存在时,它的优先级最高。它表示当打包工具支持 exports 字段时, main/browser/module 的配置将被忽略,而使用该字段。
- 路径封装
{
// 表示该包仅存在默认导出,默认导出为 ./index.js
"exports": "./index.js"
}
上述的写法相当于:
{
"exports": {
".": "./index.js"
}
}
{
"exports": {
"require": "./index.js",
"import": "./index.mjs"
}
}
定义了该字段后,该 Npm 包仅支持引入包自身,禁止引入其他子路径,相当于对于子路径的封装。
换句话说,仅仅只能引入 index.js。比如我们引入了未在 exports 中定义的模块,会报错。如果想要该包引入其他模块,可以增加其他模块配置,如下:
{
"exports": {
// . 表示引入包默认的导出文件路径, 比如 import lsx from 'lsx'
// 这里的 . 即表示未携带任何路径的 lsx,相当于默认导出 ./index.js 文件的内容
".": "./index.js",
// 同时额外定义一个可以被引入的子路径,下面的代码相当于导出./src/submodule.js文件的内容
// 可以通过 import lsxSub from 'lsx/submodule.js' 进行引入 /src/submodule.js 的文件
"./submodule.js": "./src/submodule.js"
}
}
- 条件导出
通常我们编写的 NPM 包支持被 ESM 和 CJS 两种方式同时引入,需要根据不同的引入方式来寻找不同的入口文件。
{
"exports": {
// ESM 引入时的入口文件
"import": "./index-module.js",
// CJS 方式引入时寻找的路径
"require": "./index-require.cjs"
}
}
上述的写法相当于:
{
"exports": {
"import": {
".": "./index-module.js"
},
"require": {
".": "./index-require.cjs"
}
}
}
可以看到 exports 关键字中定义的 key 为 import 和 require 分别表示两种不同的模块引入方式使用该包时引入的不同文件路径。
关于条件判断的 Key 值,除了上述的 import 和 require 分别代表的 ESM 引入和 CJS 引入的方式,NodeJS 同样提供了以下的条件匹配:
"import"- 当包通过ESM或加载时匹配import(),或者通过 ECMAScript 模块加载器的任何顶级导入或解析操作。"require"- 当包通过CJS加载时,匹配require()。"default"- 始终匹配的默认选项。可以是 CommonJS 或 ES 模块文件。这种情况应始终排在最后。 (他会匹配任意模块引入方式)
需要注意的是 exports 中的 key/value 顺序很重要,在发生条件匹配时较早的具有更高的优先级并优先于后面的配置。
当然上边我们提到的条件导出不仅仅适用于包的默认导出路径,同样也适用于子路径。比如:
{
"exports": {
".": "./index.js",
"./feature.js": {
"import": "./feature-node.js",
"default": "./feature.js"
}
}
}
注意:
如果引入的 Npm 包中定义了exports关键字来定义对应的入口文件导出,package.json 中的module、main字段都是无效。
此时自然webpack中的resolve.mainFields字段也会失去它的效果,需要通过resolve.conditionNames字段来定义对应的环境。
也就是说,在引入的 Npm 包的 package.json 中如果存在exports关键字时,构建配置的resolve.mainFields是无效的。
如果未设置resolve.conditionNames字段,那么默认webpack会按照你当前的运行环境以及引入方式从而去 npm 包中的exports字段查找对应匹配的文件。
workspaces
项目的工作区配置,用于在本地的根目录下管理多个子项目。可以自动地在 npm install 时将 workspaces 下面的包,软链到根目录的 node_modules 中,不用手动执行 npm link 操作。
workspaces 字段接收一个数组,数组里可以是文件夹名称或者通配符。比如:
"workspaces": [
"packages/*"
]
表示在 packages 目录下还有一个或多个项目,它们都有自己的 package.json。
脚本配置
scripts
指定项目的一些内置脚本命令,这些命令可以通过 npm run 来执行。通常包含项目开发,构建 等 CI 命令,比如:
{
"scripts": {
"start": "node ./build/cli.js start",
"dev": "vue-cli-service serve --mode dev",
"build": "vue-cli-service build",
}
}
执行时使用 npm run 对应的 key 就可以,比如 npm run start。
除了指定基础命令,还可以配合 pre 和 post 完成命令的前置和后续操作,比如:
"scripts": {
"build": "webpack",
"prebuild": "xxx", // build 执行之前的钩子
"postbuild": "xxx" // build 执行之后的钩子
}
每个 npm script 有 pre 和 post 两个钩子, pre 钩子在脚本执行前将被触发,post 则是在脚本执行后触发。即当执行 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 为 3000:
{
"config" : {
"port" : "3000"
}
}
可以引用 config 字段的中 port 的值:npm_package_config_xxx,如:npm_package_config_port
console.log(process.env.npm_package_config_port); // 3001
依赖配置
dependencies
它表示项目生产环境中所需的依赖,当使用 npm 安装时,依赖会默认插入该字段中
{
"dependencies": {
"@nestjs/common": "^10.0.0",
"@nestjs/core": "^10.0.0",
"@nestjs/mapped-types": "*",
"@nestjs/platform-express": "^10.0.0",
"@types/cors": "^2.8.13",
"cors": "^2.8.5"
}
}
在安装依赖时也可以使用 --save 参数,表示依赖是生产环境所需
npm install <package_name> --save
或者使用 -S 简写
npm i <package_name> -S
devDependencies
它表示项目开发环境所需的依赖,比如 webpack、vite、babel 等工程化依赖包。这些依赖表示只需要在开发环境安装,无需在生产环境安装。
{
"devDependencies": {
"@nestjs/cli": "^10.0.0",
"@nestjs/schematics": "^10.0.0",
"@nestjs/testing": "^10.0.0",
}
}
想要将依赖制定安装在 devDependencies 下,可以使用如下命令:
npm i <package_name> --save-dev
或者使用 -D 简写
npm i <package_name> -D
peerDependencies
它表示项目对等依赖,使用 npm install --save-peer 安装
这个其实在我们工作中用的并不多,一般用于开发插件,防止项目在安装该插件时,多次安装相同的依赖
{
"peerDependencies": {
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
}
需要注意的是在 npm 2 中,当我们下载 ant-design@3.x 时,peerDependencies 中指定的依赖会随着 ant-design@3.x 一起被强制安装,所以我们不需要在宿主项目的 package.json 文件中指定 peerDependencies 中的依赖,但是在 npm 3 中,不会再强制安装 peerDependencies 中所指定的包,而是通过警告的方式来提示我们,此时就需要手动在 package.json 文件中手动添加依赖。
optionalDependencies
可选依赖,顾名思义,表示依赖是可选的,它不会阻塞主功能的使用,安装或者引入失败也无妨。这类依赖如果安装失败,那么 npm 的整个安装过程也是成功的。
比如我们使用 colors 这个包来对 console.log 打印的信息进行着色来增强和区分提示,但它并不是必需的,所以可以将其加入到 optionalDependencies,并且在运行时处理引入失败的逻辑。
使用 npm install xxx -O 或者 npm install xxx --save-optional 时,依赖会被自动插入到该字段中。
{
"optionalDependencies": {
"colors": "^1.4.0"
}
}
当你希望某些依赖即使下载失败或者没有找到时,项目依然可以正常运行或者 npm 继续运行的时,就可以把这些依赖放在 optionalDependencies 对象中,但是 optionalDependencies 会覆盖 dependencies 中的同名依赖包,所以不要把一个包同时写进两个对象中。
需要注意,由于 optionalDependencies 中的依赖可能并为安装成功,所以一定要做异常处理,否则当获取这个依赖时,如果获取不到就会报错。
try {
var axios = require('axios')
var fooVersion = require('axios/package.json').version
} catch (er) {
// 报错
}
peerDependenciesMeta
同伴依赖也可以使用 peerDependenciesMeta 将其指定为可选的。
{
"peerDependencies": {
"colors": "^1.4.0"
},
"peerDependenciesMeta": {
"colors": {
"optional": true
}
}
}
bundledDependencies
它表示捆绑依赖项,它的值是一个数组,在发布包时,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字段。
发布配置
private
它用于指示该包是否为私有包。如果将 private 字段设置为 true,则该包不会被发布到 npm 公共注册表。这对于确保某些包仅用于内部开发或特定项目非常有用。
{
"private": true
}
publishConfig
publishConfig 字段在 package.json 文件中用于指定发布包时的一些配置选项。这个字段可以帮助你自定义发布行为,例如指定发布到哪个注册表、设置访问权限等。
例如(这样就会发布到指定服务器上(私服)):
{
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
}
}
但是需要注意的时,即使设置了 private: false,且设置了 publishConfig 的 registry 为私服地址。发包后,仍会发布到私服,而不会发布到 npm 上。
系统配置
engines
它表示声明 node 环境
与依赖关系一样,如果不指定版本 (或者指定 “ * ”作为版本) ,那么任何版本的节点都可以。
{
"engines": {
"node": ">=14 <16",
"pnpm": ">7"
}
}
要求 node 版本大于等于 14 且小于 16,同时 pnpm 版本号需要大于 7。
os
它表示该模块只能在那个操作系统上运行
在 linux 上能正常运行的项目可能在 windows 上会出现异常,使用 os 字段可以指定项目对操作系统的兼容性要求。
{
"os": [ "darwin", "linux", "win32" ]
}
还可以阻止而不是允许操作系统,只需在被阻止的操作系统前面加上 !
{
"os": [
"!win32"
]
}
cpu
它表示指定项目只在某些 CPU 架构上运行
{
"cpu": [
"x64",
"ia32"
]
}
与 os 类似,它同样可以使用 !
{
"cpu": [
"!arm",
"!mips"
]
}
第三方配置
typings & 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、autoperfixer 会使用该字段进行配置。当然你也可以使用 .browserslistrc 单文件配置。
{
"browserslist": [
"> 1%",
"last 2 versions",
"iOS >= 9.3",
"Android >= 6.0"
],
}
sideEffects
如果需要 treeShaking,这是一个极为重要的配置。该配置用于告诉 webpack,这个 npm 是不是有副作用的,这在减小业务包体积极为有用。比如开发组件库时,通常会把样式文件配置到这里,说明样式是存在副作用的时候。那么 wenpack 在打包时就不会剔除掉这些代码。
显示设置某些模块具有副作用,用于 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 放到这些钩子方法中。
gitHooks
它表示 git 定制化脚本程序,可以用来配置代码提交检测等
{
"scripts": {
"lint:diff": "node ./models/pre_commit.js"
},
"gitHooks": {
"pre-commit": "npm run lint:diff"
},
}
hook 类型:
- commit-msg:钩子在启动提交信息编辑器之前,默认信息被创建之后运行。
- post-update:仅在所有的 ref 被 push 之后执行一次。它与 post-receive 很像,但是不接收旧值与新值。主要用于通知。
- pre-applypatch: 实际上的调用时机是应用补丁之后、变更 commit 之前。
- pre-commit: 钩子在键入提交信息前运行。
- prepare-commit-msg: 钩子在启动提交信息编辑器之前,默认信息被创建之后运行。
- pre-push: 钩子会在 git push 运行期间, 更新了远程引用但尚未传送对象时被调用。
- pre-rebase: 钩子运行于变基之前,以非零值退出可以中止变基的过程。
- post-checkout: 更新工作树后调用 checkout 时调用,或者执行 git clone 后调用。主要用于验证环境、显示变更、配置环境。
- …
babel
它表示 babel 的配置项,用来指定 babel 的编译配置
{
"babel": {
"presets": ["@babel/preset-env"]
}
}
不过推荐做法还是使用 babel 单独配置文件:babel.config.js 等
preferGlobal
当设置 preferGlobal 字段为 true 时,它表示你的包更适合以全局方式安装,而不是作为项目的依赖项进行本地安装。
{
"preferGlobal": true
}