package.json 是前端每个项目都有的 json 文件,位于项目的根目录。package.json 里面有许许多多的配置,与项目息息相关,了解它们有助于了解项目,提效开发,规范代码。
今天主要介绍一些常见配置,我把它们分为了 7 大类:
- 描述配置
- 文件配置
- 脚本配置
- 依赖配置
- 发布配置
- 系统配置
- 第三方配置
1. 描述配置
主要是项目的基本信息,包括名称,版本,描述,仓库,作者等,部分会展示在 npm 官网上。
name
项目的名称,如果是第三方包的话,其他人可以通过该名称使用 npm install 进行安装。
version
项目的版本号,开源项目的版本号通常遵循 semver 语义化规范,具体规则如下图所示:

简单介绍一下:
1
代表主版本号 Major,通常在涉及重大功能更新,产生了破坏性变更时会更新此版本号
2
代表次版本号 Minor,在引入了新功能,但未产生破坏性变更,依然向下兼容时会更新此版本号
3
代表修订号 Patch,在修复了一些问题,也未产生破坏性变更时会更新此版本号
除了 X.Y.Z 这样的标准版本号,还有 Pre-release 和 Metadata 来描述项目的测试版本
回到 package.json 的 version 字段,name + version 能共同构成一个完全唯一的项目标识符,所以它两是最重要的两个字段。
最优版本会在版本前多了一个
^
符号,这个符号其实是有特殊意义的。同理这个符号还有可能是~
符号。那这两个符号有什么区别呢?
^ 兼容某个大版本 (^锁主要版本)
^
意味着下载的包可能会是更高的次版本号或者修订版本号。(也就是主版本不能变,次版本、修订版本可以随意变)。~ 兼容某个次版本
而
~
意味着有可能会有更高的修订版本号。(也就是主版本、次版本不能变,修订版本可以随意变)。看了上面版本号的指定后,我们可以知道,当我们使用了
^
或者 ~
来控制依赖包版本号的时候 ,多人开发,就有可能存在大家安装的依赖包版本不一样的情况,就会存在项目运行的结果不一样。repository
项目的仓库地址以及版本控制信息。
description
项目的描述,会展示在 npm 官网,让别人能快速了解该项目。
keywords
一组项目的技术关键词,比如 Ant Design 组件库的 keywords 如下:
好的关键词可以帮助别人在 npm 官网上更好地检索到此项目,增加曝光率。
homepage
项目主页的链接,通常是项目 github 链接,项目官网或文档首页。
bugs
项目 bug 反馈地址,通常是 github issue 页面的链接。
license
项目的开源许可证。项目的版权拥有人可以使用开源许可证来限制源码的使用、复制、修改和再发布等行为。常见的开源许可证有 BSD、MIT、Apache 等,它们的区别可以参考:如何选择开源许可证?
author
项目作者。
2. 文件配置
包括项目所包含的文件,以及入口等信息。
files
项目在进行 npm 发布时,可以通过 files 指定需要跟随一起发布的内容来控制 npm 包的大小,避免安装时间太长。
发布时默认会包括 package.json,license,README 和
main
字段里指定的文件。忽略 node_modules,lockfile 等文件。在此基础上,我们可以指定更多需要一起发布的内容。可以是单独的文件,整个文件夹,或者使用通配符匹配到的文件。
一般情况下,files 里会指定构建出来的产物以及类型文件,而 src,test 等目录下的文件不需要跟随发布。
type
在 node 支持 ES 模块后,要求 ES 模块采用
.mjs
后缀文件名。只要遇到 .mjs
文件,就认为它是 ES 模块。如果不想修改文件后缀,就可以在 package.json文件中,指定 type 字段为 module。这样所有
.js
后缀的文件,node 都会用 ES 模块解释。如果还要使用 CommonJS 模块规范,那么需要将 CommonJS 脚本的后缀名都改成
.cjs
,不过两种模块规范最好不要混用,会产生异常报错。main
项目发布时,默认会包括 package.json,license,README 和
main
字段里指定的文件,因为 main 字段里指定的是项目的入口文件,在 browser 和 Node 环境中都可以使用。如果不设置
main
字段,那么入口文件就是根目录下的 index.js
。比如 packageA 的
main
字段指定为 index.js。我们引入 packageA 时,实际上引入的就是
node_modules/packageA/index.js
。这是早期只有 CommonJS 模块规范时,指定项目入口的唯一属性。
browser
main 字段里指定的入口文件在 browser 和 Node 环境中都可以使用。如果只想在 web 端使用,不允许在 server 端使用,可以通过 browser 字段指定入口。
module
同样,项目也可以指定 ES 模块的入口文件,这就是 module 字段的作用。
当一个项目同时定义了 main,browser 和 module,像 webpack,rollup 等构建工具会感知这些字段,并会根据环境以及不同的模块规范来进行不同的入口文件查找。
比如 webpack 构建项目时默认的 target 为
'web'
,也就是 Web 构建。它的 resolve.mainFeilds 字段默认为 ['browser', 'module', 'main']。此时会按照 browser -> module -> main 的顺序来查找入口文件。
exports
node 在 14.13 支持在 package.json 里定义 exports 字段,拥有了条件导出的功能。
exports 字段可以配置不同环境对应的模块入口文件,并且当它存在时,它的优先级最高。
比如使用
require
和 import
字段根据模块规范分别定义入口:这样的配置在使用
import 'xxx'
和 require('xxx')
时会从不同的入口引入文件,exports 也支持使用 browser
和 node
字段定义 browser 和 Node 环境中的入口。上方的写法其实等同于:
为什么要加一个层级,把 require 和 import 放在
"."
下面呢?因为 exports 除了支持配置包的默认导出,还支持配置包的子路径。
比如一些第三方 UI 包需要引入对应的样式文件才能正常使用。
我们可以使用 exports 来封装文件路径:
用户引入时只需:
除了对导出的文件路径进行封装,exports 还限制了使用者不可以访问未在 "exports" 中定义的任何其他路径。
比如发布的 dist 文件里有一些内部模块
dist/internal/module
,被用户单独引入使用的话可能会导致主模块不可用。为了限制外部的使用,我们可以不在 exports 定义这些模块的路径,这样外部引入 packageA/dist/internal/module
模块的话就会报错。结合上面入口文件配置的知识,再来看看下方 vite 官网推荐的第三方库入口文件的定义,就很容易理解了。
workspaces
项目的工作区配置,用于在本地的根目录下管理多个子项目。可以自动地在 npm install 时将 workspaces 下面的包,软链到根目录的 node_modules 中,不用手动执行
npm link
操作。workspaces 字段接收一个数组,数组里可以是文件夹名称或者通配符。比如:
表示在 workspace-a 目录下还有一个项目,它也有自己的 package.json。
通常子项目都会平铺管理在 packages 目录下,所以根目录下 workspaces 通常配置为:
3. 脚本配置
scripts
指定项目的一些内置脚本命令,这些命令可以通过 npm run 来执行。通常包含项目开发,构建 等 CI 命令,比如:
我们可以使用命令
npm run build
/ yarn build
来执行项目构建。除了指定基础命令,还可以配合 pre 和 post 完成命令的前置和后续操作,比如:
当执行
npm run build
命令时,会按照 prebuild -> build -> postbuild 的顺序依次执行上方的命令。但是这样的隐式逻辑很可能会造成执行工作流的混乱,所以 pnpm 和 yarn2 都已经废弃掉了这种 pre/post 自动执行的逻辑,参考 pnpm issue 讨论 github.com/pnpm/pnpm/i…
如果需要手动开启,pnpm 项目可以设置
.npmrc
enable-pre-post-scripts=true。config
config 用于设置 scripts 里的脚本在运行时的参数。比如设置 port 为 3001:
在执行脚本时,我们可以通过
npm_package_config_port
这个变量访问到 3001。4. 依赖配置
项目可能会依赖其他包,需要在 package.json 里配置这些依赖的信息。
dependencies
dependencies表示项目依赖,这些依赖都会成为你的线上生产环境中的代码组成的部分。当它关联到 npm包被下载的时候,
dependencies下的模块也会作为依赖,一起被下载。
运行依赖,也就是项目生产环境下需要用到的依赖。比如 react,vue,状态管理库以及组件库等。
使用
npm install xxx
或则 npm install xxx --save
时,会被自动插入到该字段中。devDependencies
devDependencies表示开发依赖, 不会被自动下载的。项目开发环境需要用到而运行时不需要的依赖,用于辅助开发,通常包括项目工程化工具。比如说我们用到的
Webpack
,vite
,eslint
,预处理器 babel-loader
、scss-loader
,测试工具E2E
等, 这些都相当于是辅助的工具包, 无需在生产环境被使用到的。使用
npm install xxx -D
或者 npm install xxx --save-dev
时,会被自动插入到该字段中。peerDependencies
同版本的依赖,同伴依赖,一种特殊的依赖,不会被自动安装,通常用于表示与另一个包的依赖与兼容性关系来警示使用者。
比如我们安装 A,A 的正常使用依赖 B@2.x 版本,那么 B@2.x 就应该被列在 A 的 peerDependencies 下,表示“如果你使用我,那么你也需要安装 B,并且至少是 2.x 版本”。
比如 React 组件库 Ant Design,它的 package.json 里 peerDependencies 为
表示如果你使用 Ant Design,那么你的项目也应该安装 react 和 react-dom,并且版本需要大于等于 16.9.0。
optionalDependencies 可选依赖
依赖是可选的,它不会阻塞主功能的使用,安装或者引入失败也无妨。这类依赖如果安装失败,那么 npm 的整个安装过程也是成功的。不建议大家去使用, 可能会增加项目的不确定性和复杂性。
了解即可。
比如我们使用 colors 这个包来对 console.log 打印的信息进行着色来增强和区分提示,但它并不是必需的,所以可以将其加入到 optionalDependencies,并且在运行时处理引入失败的逻辑。
使用
npm install xxx -O
或者 npm install xxx --save-optional
时,依赖会被自动插入到该字段中。bundledDependencies 捆绑依赖
它是一个数组,里面定义了捆绑依赖。
当我们此时执行
npm pack
的时候, 就会生成一个 test-1.0.0.tgz
的压缩包, 在该压缩包中还包含了 bundleD1
和 bundleD2
两个安装包。实际使用到这个压缩包的时候,
npm install test-1.0.0.tgz
的命令时, bundleD1
和 bundleD2
也会被安装的。这样做的意义是什么呢?
如果
bundleD1
和bundleD2
包在npm
上有,能下载到固然没意义。但是当bundleD1
和bundleD2
是自己临时开发的,并且当前项目又依赖这两个包,并且这两个包在npm
上没有,下载不到,这下就有意义了。相当于跟主包一起捆绑发布。这里需要注意的是:
在 bundledDependencies 中指定的依赖包, 必须先在dependencies 和 devDependencies 声明过, 否则 npm pack 阶段是会报错的。
peerDependenciesMeta
同伴依赖也可以使用 peerDependenciesMeta 将其指定为可选的。
bundleDependencies
打包依赖。它的值是一个数组,在发布包时,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 的版本号:
当然这样会更改整个依赖树里的 foo,我们可以只对 A 下的 foo 进行版本号重写:
overrides 支持任意深度的嵌套。
如果在 yarn 里也想复写依赖版本号,需要使用 resolution 字段,而在 pnpm 里复写版本号需要使用 pnpm.overrides 字段。
5. 发布配置
主要是和项目发布相关的配置。
private
如果是私有项目,不希望发布到公共 npm 仓库上,可以将 private 设为 true。
publishConfig
顾名思义,publishConfig 就是 npm 包发布时使用的配置。
比如在安装依赖时指定了 registry 为 taobao 镜像源,但发布时希望在公网发布,就可以指定 publishConfig.registry。
6. 系统配置
和项目关联的系统配置,比如 node 版本或操作系统兼容性之类。这些要求只会起到提示警告的作用,即使用户的环境不符合要求,也不影响安装依赖包。
engines
一些项目由于兼容性问题会对 node 或者包管理器有特定的版本号要求,比如:
要求 node 版本大于等于 14 且小于 16,同时 pnpm 版本号需要大于 7。需要注意,
engines
只是起一个说明的作用,即使用户安装的版本不符合要求,也不影响依赖包的安装。os
在 linux 上能正常运行的项目可能在 windows 上会出现异常,使用 os 字段可以指定项目对操作系统的兼容性要求。
cpu
指定项目只能在特定的 CPU 体系上运行。
7. 第三方配置
一些第三方库或应用在进行某些内部处理时会依赖这些字段,使用它们时需要安装对应的第三方库。
types 或者 typings
指定 TypeScript 的类型定义的入口文件
unpkg
可以让
npm
上所有的文件都开启 CDN 服务。比如 vue package.json 的 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 通过如下的配置
访问
https://cdn.jsdelivr.net/npm/vue
实际上获取到的是 jsdelivr 字段里配置的文件地址。browserslist
设置项目的浏览器兼容情况。babel 和 autoprefixer 等工具会使用该配置对代码进行转换。当然你也可以使用
.browserslistrc
单文件配置。sideEffects
显示设置某些模块具有副作用,用于 webpack 的 tree-shaking 优化。
比如在项目中整体引入 Ant Design 组件库的 css 文件。
如果 Ant Design 的 package.json 里不设置 sideEffects,那么 webapck 构建打包时会认为这段代码只是引入了但并没有使用,可以 tree-shaking 剔除掉,最终导致产物缺少样式。
所以 Ant Design 在 package.json 里设置了如下的 sideEffects,来告知 webpack,这些文件具有副作用,引入后不能被删除。
lint-staged
lint-staged 是用于对 git 的暂存区的文件进行操作的工具,比如可以在代码提交前执行 lint 校验,类型检查,图片优化等操作。
lint-staged 通常配合 husky 这样的 git-hooks 工具一起使用。git-hooks 用来定义一个钩子,这些钩子方法会在 git 工作流程中比如 pre-commit,commit-msg 时触发,可以把 lint-staged 放到这些钩子方法中。
package-lock.json 详解
前面说了
package.json
里面的包版本不是一个具体的版本,而是一个最优版本。而package-lock.json
里面定义的是某个包的具体版本,以及包之间的层叠关系。一个 package-lock.json 里面的每个依赖主要是有以下的几部分组成的:
- Version: 依赖包的版本号
- Resolved: 依赖包的安装源(其实就是可以理解为下载地址)
- Intergrity: 表明完整性的 Hash 值
- Dev: 表示该模块是否为顶级模块的开发依赖或者是一个的传递依赖关系
- requires: 依赖包所需要的所有依赖项,对应依赖包 package.json 里
dependencices
中的依赖项
- dependencices: 依赖包
node_modeles
中依赖的包(特殊情况下才存在)。并不是所有的子依赖都有dependencies
属性,只有子依赖的依赖和当前已安装在根目录的node_modules
中的依赖冲突之后, 才会有这个属性。 这可能涉及嵌套情况的依赖管理。
假设我们的项目依赖
A、B、C
三个包,并且B
包又依赖特定版本的C
包,所以B
包就会有dependencices
,那么我们项目的整个node_modules
的目录结构如下那什么情况下package.json和package-lock.json里面的版本号一致呢?
当
package.json
里面不再使用最优版本,而是一个特定有效版本,也就是版本号前不带修饰符,这样package.json
和package-lock.json
里面的版本号才是一致的。或者我们
npm install xxx@1.1.2
带上版本号,安装特定版本的包package.json
和package-lock.json
里面的版本号也是一致的。或者我们
package.json
里面定义的包版本就是最新的,我们安装的时候package.json
和package-lock.json
里面的版本号也会是一致的。package-lock.json什么时候会变
1、
package-lock.json
在npm install
的时候会自动生成。2、当我们修改依赖位置,比如将部分包的位置从
dependencies
移动到 devDependencies
这种操作,虽然包未变,但是也会影响 package-lock.json
,会将部分包的 dev
字段设置为 true
。3、还有如果我们安装源
registry
不同,执行 npm install
时也会修改 package-lock.json
。因为他是会记录我们的依赖包地址的。
4、当我们使用
npm install
添加或npm uninstall
移除包的时候,同时也会修改 package-lock.json
。5、当我们更新某个包的版本的时候,同时也会修改
package-lock.json
。如何查看安装的包版本
前面说了,
package.json
里面存储的包版本信息不是一个具体的版本,而是一个最优版本,也就是带^/~
符号的,那我们如何知道我们安装的包具体是什么版本呢?常用方法有三种
- 查看
package-lock.json
,这里面锁定的包就是具体的版本,但是文件内容多不好查找。
- 打开
node_modules
文件夹,找到需要看版本信息的包,然后找到它自身的package.json
,里面的version
就是它的版本号。当然这种方式也不太理想。
- 使用命令,
npm list --depth 0
查看本地安装的所有包信息,这种方式笔者感觉是最好的。
如果要查看全局安装的包版本我们带上
-g
参数就可以了。npm list -g --depth 0
怎么安装指定版本的包
我们知道,使用
npm install xxx
会安装某最新版本的包。如果在根目录下直接运行
npm install
,那么npm
会根据package.json
下载所有的最新版本的包。那么怎么安装指定版本的包呢?
我们只需要安装的时候带上版本号就可以啦。比如笔者上面
element-ui
默认安装的最新版本是2.15.9
。我们来安装一个2.15.7
的特定版本。我们最新版本的
2.15.9
的版本包就会被替换。并且它会同步修改我们的package.json
和package-lock.json
。怎么更新包的版本
我们知道,安装完生成
package-lock.json
之后包的版本就会被固定下来,后续不管你怎么npm install
都不会再变化。npm install指定版本
我们知道最新包的版本,直接按上面的安装指定包并带上版本号即可。原理和安装特定版本的包一致。
它会同步修改我们的
package.json
和package-lock.json
。npm update
不知道最新包的版本,我们运行
npm update xxx
即可。它会将指定包更新成最新版本,并且同步修改我们的
package.json
和package-lock.json
。如果想更新项目下所有的包,直接运行
npm update
即可。请注意,更新包它是会遵循我们的最优版本号控制的。也就是说当我们的版本是被
^
修饰时,再怎么更新它只会更新次版本和修订版本,并不会更新主版本。当我们出现了需要升级依赖的需求的时候:
- 升级小版本的时候, 依靠 npm update
- 升级大版本的时候, 依靠 npm install xxx
- 当然我们也有一种方法, 直接去修改 package.json 中的版本号, 并去执行 npm install 去升级版本
- 当我们本地升级新版本后确认没有问题之后, 去提交新的 package.json 和 package-lock.json文件。
当然,如果你不想盲目更新,想确切知道那些包过期了,最新版本是什么,你可以先使用
npm outdated
命令查看过期的包有哪些,并且需要更新到什么版本。current
表示当前项目安装的包版本,wanted
表示遵循版本控制需要更新的包版本,latest
表示该包目前最新的版本。当我们使用npm update
的时候,会将包都更新成wanted
的版本。同理查看/更新全局过期的包使用npm outdated -g
和npm update -g
命令。当我们出现了需要降级依赖的需求的时候:
执行npm install @x.x.x 命令后,验证没有问题之后, 是需要提交新的 package.json 和 package-lock.json 文件。
当我们出现了需要删除依赖的需求的时候:
- 当我们执行 npm uninstall 命令后,需要去验证,提交新的
package.json
和package-lock.json
文件。
- 或者是更加暴力一点, 直接操作
package.json
, 删除对应的依赖, 执行 npm install 命令, 需要去验证,提交新的package.json 和 package-lock.json 文件。
当你把更新后的package.json 和 package-lock.json提交到代码仓库的时候, 需要通知你的团队成员, 保证其他的团队成员拉取代码之后,更新依赖可以有一个更友好的开发环境保障持续性的开发工作。
如果你的 package-lock.json 出现冲突或问题, 我的建议是将本地的 package-lock.json文件删掉, 然后去找远端没有冲突的 package.json 和 package-lock.json, 再去执行
npm install
命令。删除 package-lock.json
如果想更新项目下所有的包我们还可以将
package-lock.json
删除,然后运行npm install
,它会遵循最优版本号控制,安装所有的最新版本的包,并且会重新生成package-lock.json
。package-lock.json 需要提交到仓库吗
至于我们要不要提交 lockfiles 到仓库中?这个就需要看我们具体的项目的定位了。
- 如果是开发一个应用, 我的理解是
package-lock.json
文件提交到代码版本仓库.这样可以保证项目中成员、运维部署成员或者是 CI 系统, 在执行npm install
后, 保证在不同的节点能得到完全一致的依赖安装的内容,减少bug
的出现。
- 如果你的目标是开发一个给外部环境用的库,那么就需要认真考虑一下了, 因为库文件一般都是被其他项目依赖的,在不使用 package-lock.json的情况下,就可以复用主项目已经加载过的包,减少依赖重复和体积
- 如果说我们开发的库依赖了一个精确版本号的模块,那么在我们去提交
lockfiles
到仓库中可能就会出现, 同一个依赖被不同版本都被下载的情况,这样加大了node_modules
的体积。如果我们作为一个库的开发者, 其实如果真的使用到某个特定的版本依赖的需求, 那么定义peerDependencies 是一个更好的选择。
所以, 我个人比较推荐的一个做法是:
把 package-lock.json
一起提交到仓库中去, 不需要 ignore
。但是在执行 npm publish
命令的时候,也就是发布一个库的时候, 它其实应该是被忽略的不应该被发布出去的。可以不使用package-lock.json吗
在项目中如果没有锁版本的必要,可以不使用
package-lock.json
,在安装模块时指定--no-lock
即可。