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.json
README
LICENSE
/LICENCE
main
字段中的文件
忽略 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
}