认识 package.json 
package.json存在于每个基于node的前端工程化项目中,所以正确的认识该文件中的字段,对理清项目流程、提升工程化能力格外重要
介绍 
中文文档中翻译的介绍,理解起来仍有难度。简单来说package.json就是一个包(package)描述文件,在node对包内文件进行处理(比如模块类型识别等)时,会按照最近的平级或上级package.json中描述的规则处理,另外package.json默认不对node_modules目录生效
生成 
package.json可以手动生成也可以通过命令生成:
# 根据提示输入字段内容
npm init
# 全部使用默认值生成
npm init -y
# yarn或pnpm也有同样的命令,区别是yarn和pnpm都是默认生成,不需要加-y
# pnpm生成内容与npm默认内容一致,yarn生成只有name和packageManager字段
yarn init
pnpm init2
3
4
5
6
7
8
Node 使用的字段 
type 
目前JS有两种主流模块化方案,分别是Node采用的CommonJS,和最新的ESModule,推荐先阅读JS 模块化原理熟悉这两种模块化规则。对于不同的模块类型,引擎会采用不同的模块解析器进行处理,所以需要正确的识别模块类型
type默认值为commonjs,所以项目中的文件默认会采用CommonJS模块规则解析。如果指定为module则会采用ESModule模块规则解析
官方还扩展了.mjs和.cjs扩展名,分别对应ESModule和CommonJS。设置这两种扩展名的文件将始终按对应模块解析器解析,不受type字段限制
也就是说如果没指定type或指定为commonjs时,项目内要使用ESModule语法,则需要将对应的文件改为.mjs扩展名。反之type指定为module时,应该将CommonJS模块改为.cjs扩展名
main 
定义包的入口文件
比如在项目中下载了包A,通过import A from 'A'方式引入时,实际查找的就是node_modules/A/main定义的路径:
module 
module字段最早由rollup提出,目前在中文官网中还没有提到。目的是提供ESModule入口,这样打包器能够利用ESModule的静态分析能力实现TreeShaking功能
如果打包器支持module字段,则在查找包时,首先会尝试以ESModule规则读取module对应文件
browser 
在例如Axios这种同时支持node端和浏览器端的包中,对于不同环境可能依赖于不同的模块(Axios中node环境使用http请求,浏览器环境使用xhr请求),browser字段就是为了更好的实现这一功能
当使用了例如webpack等打包器时,如果指定环境target为web,则会使用该字段指定的文件规则
支持单个属性,代表入口文件。也支持定义多个键值对,代表替换对于路径
// 单个字段,代表作为浏览器环境的入口文件
"browser": "./lib/browser/main.js"
// 指定键值对时,解析对应的键时,会替换到对应值的路径
"browser": {
  "module-a": "./browser/module-a.js",
  "./server/module-b.js": "./browser/module-b.js",
  // 还支持配置false,防止模块被加载
  "module-c": false
}2
3
4
5
6
7
8
9
exports 
作为main、module的替代方案,多个字段同时存在时,会优先使用exports
允许定义多个入口点,对于暴露入口较少的情况,官方建议明确指定每个入口点;暴露入口过多时,可以直接导出整个文件夹
{
  "exports": {
    // 明确指定每个入口点
    ".": "./lib/index.js",
    "./lib": "./lib/index.js",
    // 官方建议同时提供有扩展名和无扩展名的子路径
    "./lib/index": "./lib/index.js",
    "./lib/index.js": "./lib/index.js",
    // 导出整个文件夹
    "./feature/*": "./lib/feature/*.js",
    // 导出文件夹中有不想暴露的入口点时,可以明确指定不暴露
    "./feature/internal/*": null
  }
}2
3
4
5
6
7
8
9
10
11
12
13
14
每个路径都应该相当于包根目录。仅有根目录暴露时,可以简写为:"exports": "./index.js"
支持条件导出,常用条件包括import、require、default
{
  "exports": {
    // 使用import导入时,加载mjs文件
    "import": "./index.mjs",
    // 使用require导入时 ,加载cjs文件
    "require": "./index.cjs",
    // default作为通用后备选项,应该始终放在最后
    "default": "./index.js"
  },
  // 也可以用在导出子路径
  "exports": {
    ".": "./index.js",
    "./feature": {
      "import": "./feature/index.mjs",
      "require": "./feature/index.cjs"
    }
  }
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
定义exports后,导入非入口点的路径时会抛出ERR_PACKAGE_PATH_NOT_EXPORTED错误
name 
定义包名,也就是发布到npm仓库的名字,需要符合命名规则
TIP
@a/b格式的包名含义是a范围中名字为b的包,里面的@和/属于命名规范。范围的作用是将一系列相关的包组织在一起,更重要的是不用担心包名重复的问题。如何发布范围,参考官方文档
packageManager 
截至发文仍处于实验性阶段,需要NodeJS版本大于等于16.9.0。基于新版node中附带的corepack包,用于统一项目包管理器,具体可以参考这篇文章
如果使用 NVM、Volta 等工具管理 Node 版本时,corepack 命令可能不可用,可以在官方 issue 中找到解决办法
包管理器常用字段 
scripts 
包自身的运行脚本,例如本文档的启动方式:
{
  "scripts": {
    "dev": "vitepress dev"
  }
}2
3
4
5
可以通过npm run dev的方式调用对应的vitepress dev命令
npm还提供了前后脚本关键字pre、post,例如
{
  "scripts": {
    "preecho": "",
    "echo": "",
    "postecho": ""
  }
}2
3
4
5
6
7
执行npm run echo后会分别调用preecho、echo、postecho
除此之外还提供了一些特定的脚本,可以自行查看官方文档
config 
config字段用于添加命令行环境变量,添加后scripts字段中指定运行的脚本中便可以使用添加的环境变量
{
  "config": { "port": "8888" },
  "scripts": { "start": "node server.js" }
}2
3
4
// server.js
console.log(process.env.npm_package_config_port); // 88882
dependencies 
# i是全称install的缩写命令
# 常见的--save或-S可以不用添加
# @version可以省略,默认最新稳定版本
npm i lib-name@version
pnpm i lib-name@version
yarn add lib-name@version2
3
4
5
6
运行时依赖(运行相关的核心文件),包含包名以及版本的映射,通过包管理器安装时会自动将包名以及版本添加到dependencies中
标准的项目版本规则通常为:主版本号.副版本号.修订版本号-预发布标签.预发布版本号,例如vitepress当前版本1.0.0-alpha.49。详情可以查看semver
版本号前可以指定范围符,包括:
- <:小于指定版本号
- <=:小于对于指定版本号
- >、- >=同理
- =:与指定版本号系统,等号可以省略
- 范围标识符可以混用,例如>=1.2.0<2.0.0,也可以使用1.2.0-2.0.0表示
- ||:指定多个版本范围
- x:可以作为通配符,例如- 1.x表示主版本为 1,副版本,修订版本任意
- ~:指定版本到副版本+1 的范围
- ^:指定版本到首位非 0 版本号+1 的范围
- *:匹配任何范围
- tag标记:匹配发布为- tag的特定版本,例如- latest标记
预发布标签通常由 alpha 与 beta。alpha 版本表示工作正在开发中,可以看作技术预览版,beta 版本表示产品已做好发布的准备,可以看作测试版。存在预发布标签时,即使使用范围符,也需要满足主版本号、副版本号、修订版本号相同
devDependencies 
# -D也可以写作--save-dev
npm i lib-name@version -D
pnpm i lib-name@version -D
yarn add lib-name@version -D2
3
4
开发时依赖(例如代码格式化、测试等,与主题功能无关),内容同dependencies。通过包管理器安装时会自动将xxx添加到devDependencies中
在不作为包发布的项目中,dependencies和devDependencies没有区别,因为打包器是按照导入读取的文件,与依赖项无关
但如果项目要作为包发布,用户引入你的包时,则只会下载dependencies中的依赖
peerDependencies 
npm i lib-name@version --save-peer
pnpm i lib-name@version --save-peer
yarn add lib-name@version --save-peer2
3
用于指定当前包需要的宿主环境,内容同dependencies,通常在插件中使用
例如开发vue的插件,如果将vue放在dependencies中,则用户在本身已安装vue的项目中引用你的插件时,还会再安装一次vue,造成浪费。如果定义在peerDependencies中,npm install默认不会重复安装vue,只要定义的版本号与用户已安装版本号匹配,就能直接使用。而如果用户还没有安装对应版本的vue,则会提示用户有peerDependencies未安装
files 
定义包作为依赖被安装时,只包含哪些文件,默认["*"]也就是全部文件
无论如何设置,package.json、README、LICENSE/LICENCE、main字段文件始终会包含
相反,.git等文件会被忽略
bin 
定义可执行命令添加到系统环境变量中,例如本文档使用到的zx:
{
  "bin": {
    "zx": "./build/cli.js"
  },
  // 也可以简写,此时命令名称为包名
  "bin": "./build/cli.js"
}2
3
4
5
6
7
engines 
指定适用的node版本,版本规则同依赖项
{
  "engines": {
    "node": ">=14.0.0"
  }
}2
3
4
5
version 
发布到npm的版本号
description 
包介绍
keywords 
包关键字,用于在npm中发现你的包
homepage 
项目主页地址
bugs 
项目问题追踪地址或邮件地址
{
  "url": "例如github issue页地址",
  "email": "email@xxx.com"
}2
3
4
license 
项目使用的许可证,表示别人能如何利用你的开源项目
author、contributors 
项目开发人员
{
  "author": {
    "name": "",
    "email": "",
    "url": ""
  },
  // 或者精简为一行
  "author": "name <email> (url)"
  // contributors格式同author
  "contributors": [
    {...},
    {...}
  ]
}2
3
4
5
6
7
8
9
10
11
12
13
14
repository 
包代码所在位置,如github仓库
{
  "repository": {
    "type": "git",
    "url": "https://github.com/Xaviw/XaviDocs.git",
    // 如果包是monorepo(多个包在一个项目下)的一部分,可以指定该包所在的目录
    "directory": "packages/react-dom"
  }
}2
3
4
5
6
7
8
private 
如果设置为true,则npm会拒绝发布该包
preferGlobal 
设置为true时,如果用户安装该包未使用--global参数,则会显示警告