package.json
为什么需要package.json?npm官网给出的解答为:
- 列出项目中依赖的包
- 语义化的方式指明项目中可以使用的包版本
- 使构建可重复化,便于分享给其他开发者
创建
npm init -y
使用
npm install
yarn install
import name from 'name'
字段含义
name
包名称,可以在npm中搜索包名并下载
{
"name": "my-awesome-package",
"version": "1.0.0",
"author": "Your Name <email@example.com>"
}
version
包的当前版本,为x.x.x的semantic version
author
包的作者信息,格式为 Your Name <email@example.com> (http://example.com)
type
js的模块化规范包含了commonjs、CMD、UMD、AMD和ES module等,最早先在node中支持的仅仅是commonjs字段,但是从node13.2.0开始后,node正式支持了ES module规范,在package.json中可以通过type字段来声明npm包遵循的模块化规范
{
name: "some package",
type: "module"||"commonjs"
}
需要注意的是:
- 不指定type的时候,type的默认值是commonjs,不过建议npm包都指定一下type
- 当type字段指定值为module则采用ESModule规范
- 当type字段指定时,目录下的所有.js后缀结尾的文件,都遵循type所指定的模块化规范
- 除了type可以指定模块化规范外,通过文件的后缀来指定文件所遵循的模块化规范,以.mjs结尾的文件就是使用的ESModule规范,以.cjs结尾的遵循的是commonjs规范
main & module & browser
除了type外,package.json中还有main,module和browser 3个字段来定义npm包的入口文件。
- main : 定义了 npm 包的入口文件,browser 环境和 node 环境均可使用
- module : 定义 npm 包的 ESM 规范的入口文件,browser 环境和 node - 环境均可使用
- browser : 定义 npm 包在 browser 环境下的入口文件
我们来看一下这3个字段的使用场景,以及同时存在这3个字段时的优先级。我们假设有一个npm包为demo1,
----- dist
|-- index.browser.js
|-- index.browser.mjs
|-- index.js
|-- index.mjs
其package.json中同时指定了main,module和browser这3个字段
"main": "dist/index.js", // main
"module": "dist/index.mjs", // module
// browser 可定义成和 main/module 字段一一对应的映射对象,也可以直接定义为字符串
"browser": {
"./dist/index.js": "./dist/index.browser.js", // browser+cjs
"./dist/index.mjs": "./dist/index.browser.mjs" // browser+mjs
},
// "browser": "./dist/index.browser.js" // browser
默认构建和使用,比如我们在项目中引用这个npm包:
import demo from 'demo'
通过构建工具构建上述代码后,模块的加载循序为:
browser+mjs > module > browser+cjs > main
这个加载顺序是大部分构建工具默认的加载顺序,比如webapck、esbuild等等。可以通过相应的配置修改这个加载顺序,不过大部分场景,我们还是会遵循默认的加载顺序。
exports
如果在package.json中定义了exports字段,那么这个字段所定义的内容就是该npm包的真实和全部的导出,优先级会高于main和file等字段。
{
"name": "pkg",
"exports": {
".": "./main.mjs",
"./foo": "./foo.js"
}
}
import { something } from "pkg"; // from "pkg/main.mjs"
const { something } = require("pkg/foo"); // require("pkg/foo.js")
从上述的例子来看,exports可以定义不同path的导出。如果存在exports后,以前正常生效的file目录到处会失效,比如require('pkg/package.json'),因为在exports中没有指定,就会报错。
exports还有一个最大的特点,就是条件引用,比如我们可以根据不同的引用方式或者模块化类型,来指定npm包引用不同的入口文件
{
"name":"pkg",
"main": "./main-require.cjs",
"exports": {
"types": "./types/index.d.ts",
"import": "./main-module.js",
"require": "./main-require.cjs"
},
"type": "module"
}
上述的例子中,如果我们通过
const p = require('pkg')
引用的就是"./main-require.cjs"。
如果通过:
import p from 'pkg'
引用的就是"./main-module.js"
包中的类型定义文件引用的是"./types/index.d.ts"
最后需要注意的是 :如果存在exports属性,exports属性不仅优先级高于main,同时也高于module和browser字段。
scripts
运行脚本命令的缩写配置,比如start指定了运行npm start时,所要执行的命令。
需要注意的是在script中的命令不再需要npx(命令行中直接调用项目中的安装包,需要使用npx)
"scripts": {
"test": "vitest",
"build": "rollup -c rollup.config.js"
},
dependencies 和 devDependencies
dependencies字段指定了项目运行所依赖的模块,devDependencies指定项目开发所需要的模块。
它们的值都是一个对象。该对象的各个成员,分别由模块名和对应的版本要求组成,表示依赖的模块及其版本范围。
当安装依赖的时候使用--save参数表示将该模块写入dependencies属性,--save-dev表示将该模块写入devDependencies属性
"devDependencies": {
"@rollup/plugin-typescript": "^11.0.0",
"rollup": "^3.17.2",
"typescript": "^4.9.5"
},
"dependencies": {
"@rollup/pluginutils": "^5.0.2",
"tslib": "^2.5.0",
"vitest": "^0.28.5"
}
依赖与开发依赖的区别
- 安装一个外部模块时,不会自动安装这个模块下packagejson里边devDependencies的依赖,因为只需要使用模块,而不需要开发模块
- 运行npm install时,加入--production参数不会下载devDependencies的依赖(前提是保证这里边的依赖不影响项目打包部署,也只有eslint、prettier这种才是完全不影响的了)
版本控制
版本控制遵循 major.minor.patch 语法,按照功能的迭代,按需升级版本。
| Code status | Stage | Rule | Example version |
|---|---|---|---|
| First release | New product | Start with 1.0.0 | 1.0.0 |
| Backward compatible bug fixes | Patch release | Increment the third digit | 1.0.1 |
| Backward compatible new features | Minor release | Increment the middle digit and reset last digit to zero | 1.1.0 |
| Changes that break backward compatibility | Major release | Increment the first digit and reset middle and last digits to zero | 2.0.0 |
包版本升级策略
- 指定版本
1.0.1:表示只能安装改版本的包 - 允许升级patch版本
1.01.0.x~1.0.1:表示只能更新patch,比如1.0.1,1.0.2, ... - 允许升级minor版本
11.x^1.0.1:表示只能更新minor和patch,比如1.0.1,1.1.0,1.2.0, ... - 安装最新版本:
*
可以在npm semver calculator进行在线测试
"lodash": "1.0.1",
"lodash": "~1.0.1",
"lodash": "^1.0.1",
"lodash": "*",
peerDependencies
当我们开发项目的时候,如果项目与所依赖的包同时依赖一个第三方包,并且依赖的是两个不兼容的版本时就会出现问题。比如,你的项目依赖A包和B包的1.0版,而A模块本身又依赖B模块的2.0版。大多数情况下,这不构成问题,B模块的两个版本可以并存,同时运行。
但是,有一种情况,会出现问题,就是这种依赖关系将暴露给用户。
最典型的场景就是插件,比如A包是B包的插件。用户安装的B模块是1.0版本,但是A插件只能和2.0版本的B包一起使用。这时,用户要是将1.0版本的B的实例传给A,就会出现问题。因此,需要一种机制,在模板安装的时候提醒用户,如果A和B一起安装,那么B必须是2.0包。
peerDependencies字段,就是用来供插件指定其所需要的主工具的版本。可以通过peerDependencies字段来限制,使用chai-as-promised模块必须依赖chai模块的3.9.x版本。
{
"name": "chai-as-promised",
"peerDependencies": {
"chai": "1.x"
}
}
还有一个场景是避免包的重复安装,如下图所示,此时会存在两个版本的request包。可以将request包放在some-other-library的peerDependencies中,统一使用项目中公共的request包。
├── request@2.12.0
└─┬ some-other-library@1.2.3
└── request@1.9.9
description
一个字符串,用于编写描述信息。有助于人们在npm库中搜索的时候发现你的模块
keywords
一个字符串组成的数组,有助于人们在npm库中搜索的时候发现你的模块
homepage
项目的主页地址
license
是当前项目的协议,让用户知道他们有何权限来使用你的模块,以及使用该模块有哪些限制
engine
engines字段指明了该模块运行的平台,比如Node或者npm的某个版本或者浏览器
{ "engines" : { "node" : ">=0.10.3 <0.12", "npm" : "~1.0.20" } }
private
表示私有模块,如果这个属性被设置为true,npm将拒绝发布它,这是为了防止一个私有模块被无意间发布出去
publishConfig
这个配置是会在模块发布时生效,用于设置发布用到的一些值的集合。如果你不想模块被默认标记为最新的,或者默认发布到公共仓库,可以在这里配置tag或仓库地址。
通常publishConfig会配合private来使用,如果你只想让模块被发布到一个特定的npm仓库,如一个内部的仓库
"private": true,
"publishConfig": {
"tag": "1.0.0",
"registry": "https://registry.npmjs.org/",
"access": "public"
}
files
定义了哪些文件应该被包括在 npm install 后的 node_modules中,避免别人在安装包时增加资源下载量。
files属性的值是一个数组,内容是模块下文件名或者文件夹名,如果是文件夹名,则文件夹下所有的文件也会被包含进来(除非文件被另一些配置排除了)。注意,有些文件是自动暴露的,不管是否配置了files,如:package.json, readme等
可以在模块根目录下创建一个.npmignore文件,写在这个文件里边的文件即便被写在files属性里边也会被排除在外,这个文件的写法与.gitignore类似。
bin
bin项用来指定每个内部命令对应的可执行文件的位置。如果你编写的是一个node工具的时候一定会用到bin字段。
当我们编写一个cli工具的时候,需要指定工具的运行命令,比如常用的webpack模块,他的运行命令就是webpack
"bin": {
"webpack": "bin/index.js",
}
在模块以非全局依赖的方式被安装,如果存在bin选项。在node_modules/.bin/生成对应的文件,npm会寻找这个文件,在node_modules/.bin/目录下建立符号链接。由于node_modules/.bin/目录会在运行时加入系统的PATH变量,因此在运行npm时,就可以不带路径,直接通过命令来调用这些脚本,即scripts中直接调用命令。
所有node_modules/.bin/目录下的命令,都可以用npm run [命令]的格式运行。在命令行下,键入npm run,然后按tab键,就会显示所有可以使用的命令。
模块以全局依赖安装的时候,bin字段会被加载到全局环境中,可以在命令行直接执行相应的命令。
如:nvm ls
man
指定当前模块的man文档的位置
repository
指定一个代码存放地址,对想要为你的项目贡献代码的人有帮助
"repository" : {
"type" : "git",
"url" : "https://github.com/npm/npm.git"
}
types
用于指定ts的类型声明定义
"types": "./types/index.d.ts",