package.json必知必会

715 阅读27分钟

下面是一个更完整的package.json文件,详细解释一下每个字段的真实含义。

{
    "name": "yindong",
    "version":"0.0.1",
    "description": "antd-theme",
    "keywords":["node.js","antd", "theme"],
    "homepage": "https://zhiqianduan.com",
    "bugs":{"url":"http://path/to/bug","email":"yindong@xxxx.com"},
    "license": "ISC",
    "author": "yindong",
    "contributors":[{"name":"yindong","email":"yindong@xxxx.com"}],
    "files": "",
    "main": "./dist/default.js",
    "bin": "",
    "man": "",
    "directories": "",
    "repository": {
     "type": "git",
      "url": "https://path/to/url"
    },
    "scripts": {
      "start": "webpack serve --config webpack.config.dev.js --progress"
    },
    "config": { "port" : "8080" },
    "dependencies": {},
    "devDependencies": {
        "@babel/core": "^7.14.3",
        "@babel/preset-env": "^7.14.4",
        "@babel/preset-react": "^7.13.13",
        "babel-loader": "^8.2.2",
        "babel-plugin-import": "^1.13.3",
        "glob": "^7.1.7",
        "less": "^3.9.0",
        "less-loader": "^9.0.0",
        "style-loader": "^2.0.0",
        "webpack": "^5.38.1",
        "webpack-cli": "^4.7.0",
        "webpack-dev-server": "^3.11.2"
    },
    "peerDependencies": {
        "tea": "2.x"
    },
    "bundledDependencies": [
        "renderized", "super-streams"
    ],
    "engines": {"node": "0.10.x"},
	  "os" : [ "win32", "darwin", "linux" ],
    "cpu" : [ "x64", "ia32" ],
    "private": false,
    "publishConfig": {}
  }

package.json必要属性

package.json中最重要的两个字段就是name和version,它们都是必须的,名称和版本一起构成一个标识符,该标识符被认为是完全唯一的。对包的更改应该与对版本的更改一起进行。如果没有,就无法正常执行npm install命令。npm规定package.json文件是由名称和版本号作为唯一标识符的。

name

name很容易理解,就是项目的名称,它是一个字符串。在给name字段命名时,需要注意以下几点:

  • 名称的长度必须小于或等于214个字符,不能以“.”和“_”开头,不能包含大写字母(这是因为当软件包在npm上发布时,会基于此属性获得自己的URL,所以不能包含非URL安全字符(non-url-safe));
  • 名称可以作为参数被传入require(""),用来导入模块,所以应当尽可能的简短、语义化;
  • npm官方建议我们不要使用与核心节点模块相同的名称。不要在名称中加js或node。如果需要可以使用engines来指定运行环境。
  • 名称不能和其他模块的名称重复。可以使用npm view antd命令查询模块明是否重复,如果不重复就会提示404;如果npm包上有对应的包,则会显示包的详细信息。

version

version一般的格式是x.x.x, 该字段表示该项目包的版本号,它是一个字符串。在每次项目改动后,即将发布时,都要同步的去更改项目的版本号,每次发布时version不能与已存在的一致。版本号的使用规范如下:

  • 版本号的命名遵循语义化版本2.0.0规范,格式为:主版本号.次版本号.修订号,通常情况下,修改主版本号是做了大的功能性的改动,修改次版本号是新增了新功能,修改修订号就是修复了一些bug;
  • 如果某个版本的改动较大,并且不稳定,可能如法满足预期的兼容性需求,就需要发布先行版本,先行版本通过会加在版本号的后面,通过“-”号连接以点分隔的标识符和版本编译信息:内部版本(alpha)、公测版本(beta)和候选版本(rc,即release candiate)。
  • 可以通过以下命令npm view antd versions?来查看npm包的版本信息,以react为例:
// 查看最新版本
npm view react version
// 查看所有版本
npm view react versions

package.json描述信息

package.jaon中有五个和项目包描述信息相关的配置字段,下面就分别来看看这些字段的含义。

description

description字段用来描述这个项目包,它是一个字符串,可以让其他开发者在 npm 的搜索中发现我们的项目包。

keywords

keywords字段是一个字符串数组,表示这个项目包的关键词。和description一样,都是用来增加项目包的曝光率的。下面是eslint包的描述和关键词:

author

author顾名思义就是作者,表示该项目包的作者。它有两种形式,一种是字符串格式:

"author": "CUGGZ <xxxxx@xx.com> (https://juejin.cn/user/3544481220801815)"

另一种是对象形式:

"author": {
  "name" : "CUGGZ",
  "email" : "xxxxx@xx.com",
  "url" : "https://juejin.cn/user/3544481220801815"
}

contributors

contributors表示该项目包的贡献者,和author不同的是,该字段是一个数组,包含所有的贡献者,它同样有两种写法:

"contributors": [
  "CUGGZ0 <xxxxx@xx.com> (https://juejin.cn/user/3544481220801815)",
  "CUGGZ1 <xxxxx@xx.com> (https://juejin.cn/user/3544481220801815)"
 ]
"contributors": [
  {
  	"name" : "CUGGZ0",
  	"email" : "xxxxx@xx.com",
  	"url" : "https://juejin.cn/user/3544481220801815"
	},
  {
  	"name" : "CUGGZ1",
  	"email" : "xxxxx@xx.com",
  	"url" : "https://juejin.cn/user/3544481220801815"
	}
 ]

homepage

homepage就是项目的主页地址了,它是一个字符串。

repository

repository表示代码的存放仓库地址,通常有两种书写形式。第一种是字符串形式:

"repository": "https://github.com/facebook/react.git"

除此之外,还可以显式地设置版本控制系统,这时就是对象的形式:

"repository": {
  "type": "git",
  "url": "https://github.com/facebook/react.git"
}

bugs

bugs表示项目提交问题的地址,该字段是一个对象,可以添加一个提交问题的地址和反馈的邮箱:

"bugs": { 
  "url" : "https://github.com/facebook/react/issues",
  "email" : "xxxxx@xx.com"
}

最常见的bugs就是Github中的issues页面,如上就是react的issues页面地址。

package.json脚本配置

scripts

scripts 是 package.json中内置的脚本入口,是key-value键值对配置,key为可运行的命令,可以通过 npm run 来执行命令。除了运行基本的scripts命令,还可以结合pre和post完成前置和后续操作。先来看一组scripts:

"scripts": {
	"dev": "node index.js",
  "predev": "node beforeIndex.js",
  "postdev": "node afterIndex.js"
}

这三个js文件中都有一句console:

// index.js
console.log("scripts: index.js")
// beforeIndex.js
console.log("scripts: before index.js")
// afterIndex.js
console.log("scripts: after index.js")

当我们执行npm run dev命令时,输出结果如下:

scripts: before index.js
scripts: index.js
scripts: after index.js

可以看到,三个命令都执行了,执行顺序是predev→dev→postdev。如果scripts命令存在一定的先后关系,则可以使用这三个配置项,分别配置执行命令。

通过配置scripts属性,可以定义一些常见的操作命令:

"scripts": {
  "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
  "start": "npm run dev",
  "unit": "jest --config test/unit/jest.conf.js --coverage",
  "test": "npm run unit",
  "lint": "eslint --ext .js,.vue src test/unit",
  "build": "node build/build.js"
}

这些脚本是命令行应用程序。可以通过调用 npm run XXX 或 yarn XXX 来运行它们,其中 XXX 是命令的名称。 例如:npm run dev。我们可以为命令使用任何的名称,脚本也可以是任何操作。

原理:在npm中使用script标签来定义脚本,每当制定npm run的时候,就会自动创建一个shell脚本,这里需要注意的是,npm run新建的这个 Shell,会将本地目录的node_modules/.bin子目录加入PATH变量。

这意味着,当前目录的node_modules/.bin子目录里面的所有脚本,都可以直接用脚本名调用,而不必加上路径。比如,当前项目的依赖里面有 esbuild,只要直接写esbuild xxx 就可以了。

{
  // ...
  "scripts": {
    "build": "esbuild index.js",
  }
}
{
  // ...
  "scripts": {
    "build": "./node_modules/.bin/esbuild index.js" 
  }
}

上面两种写法是等价的。

DEFAULT VALUES

npm设置了一些默认参数,如:·"scripts": {"start": "node server.js"} 如果模块根目录下有一个server.js文件,那么npm start会默认运行这个文件。 "scripts":{"preinstall": "node-gyp rebuild"} 如果模块根目录下有binding.gyp, npm将默认用node-gyp来编译preinstall的脚本 "contributors": [...] 若模块根目录下有AUTHORS 文件,则npm会按Name (url)格式解析每一行的数据添加到contributors中,可以用#添加行注释.

bin

bin 列出了可执行文件,表示你这个包要对外提供哪些脚本。

在这个包被 install 安装时,如果是全局安装 -g,bin 列出的可执行文件会被添加到 PATH 变量(全局可执行),可以通过别名来执行该文件;如果是局部安装,则会进入到 node_modules/.bin/ 目录下。

在开发 npm 包时,要求发布的可执行脚本要以#!/usr/bin/env node开头,这是为什么呢?是为了用于指明该脚本文件要使用 node 来执行。

比如@bytepack/cli的npm包:

"bin": {
    "bytepack": "./bin/index.js"
 },

一旦在全局安装了@bytepack/cli,就可以直接通过bytepack来执行相应的命令,比如

bytepack -v
//显示1.11.0

如果非全局安装,那么会自动连接到项目的node_module/.bin目录中。与前面介绍的script标签中所说的一致,可以直接用别名来使用。

files

files定义了哪些文件应该被包括在 npm install 后的 node_modules中。当npm包发布时,files指定的文件会被推送到npm服务器中。

files配置是一个数组,内容是模块下文件名或者文件夹名,用来描述当把npm包作为依赖包安装时需要说明的文件列表。

  • 如果是文件夹名,则文件夹下所有的文件也会被包含进来(除非文件被另一些配置排除了) 你也可以在模块根目录下创建一个.npmignore文件,写在这个文件里边的文件即便被写在files属性里边也会被排除在外,这个文件的写法".gitignore"类似。
"files": [
    "LICENSE",
    "Readme.md",
    "index.js",
    "lib/"
 ]

.npmignore文件

node_modules
.vscode

build

.DS_Store
  • 当然,有些文件是自动暴露出来的,不管你是不是配置了files,比如:
package.json
README / CHANGELOG / LICENSE
...

很多库都定义了 files,避免一些不必要的文件暴露到 node_modules 中。

workspaces

在项目过大的时候,最近越来越流行monorepo。提到monorepo就绕不开workspaces,早期我们会用yarn workspaces,现在npm官方也支持了workspaces.

workspaces解决了本地文件系统中如何在一个顶层root package下管理多个子packages的问题,在workspaces声明目录下的package会软链到最上层root package的node_modules中。

直接以官网的例子来说明:

{
  "name": "my-project",
  "workspaces": [
    "packages/a"
  ]
}

在一个npm包名为my-project的npm包中,存在workspaces配置的目录。

.my-project
+-- package.json
+-- index.js
`-- packages
   +-- a
   |  `-- package.json

并且该最上层的名为my-project的root包,有packages/a子包。此时,我们如果npm install,那么在root package中node_modules中安装的npm包a,指向的是本地的package/a.

.
+-- node_modules
|  `-- packages/a -> ../packages/a
+-- package-lock.json
+-- package.json
`-- packages
   +-- a
   |   `-- package.json

上述的-- packages/a -> ../packages/a指的就是从node_modules中a链接到本地npm包的软链。

config

config字段用来配置scripts运行时的配置参数,如下所示:

"config": {
	"port": 3000
}

如果运行npm run start,则port字段会映射到npm_package_config_port环境变量中:

console.log(process.env.npm_package_config_port) // 3000

用户可以通过npm config set foo:port 3001 命令来重写port的值。

npm config set yindong:port 8000

man

制定一个或通过数组制定一些文件来让linux下的man命令查找文档地址。

  • 如果只有一个文件被指定的话,安装后直接使用man+模块名称,而不管man指定的文件的实际名称。如下通过man foo命令会得到 ./man/doc.1 文件的内容。
{
    "name" : "foo",
    "version" : "1.2.3", 
    "description" : "A packaged foo fooer for fooing foos", 
    "main" : "foo.js", 
    "man" : "./man/doc.1"
}
  • 如果man文件名称不是以模块名称开头的,安装的时候会给加上模块名称前缀。因此,下面这段配置会创建一些文件来作为man foo和man foo-bar命令的结果。
{ 
    "name" : "foo", 
    "version" : "1.2.3", 
    "description" : "A packaged foo fooer for fooing foos", 
    "main" : "foo.js", 
    "man" : [ "./man/foo.1", "./man/bar.1" ]
}
  • man文件必须以数字结尾,如果经过压缩,还可以使用.gz后缀。数字表示文件将被安装到man的哪个部分。如下会创建 man foo 和 man 2 foo 两条命令。
{ 
    "name" : "foo", 
    "version" : "1.2.3",
    "description" : "A packaged foo fooer for fooing foos", 
    "main" : "foo.js", 
    "man" : [ "./man/foo.1", "./man/foo.2" ]
}

directories

CommonJs通过directories来制定一些方法来描述模块的结构,看看npm的package.json文件registry.npmjs.org/npm/latest

directories字段用来规范项目的目录。node.js 模块是基于 CommonJS 模块化规范实现的,需要严格遵循 CommonJS 规范。模块目录下除了必须包含包项目描述文件 package.json 以外,还需要包含以下目录:

bin :存放可执行二进制文件的目录

lib :存放js代码的目录

doc :存放文档的目录

test :存放单元测试用例代码的目录

...

在实际的项目目录中,我们可能没有按照这个规范进行命名,那么就可以在directories字段指定每个目录对应的文件路径:

"directories": {
    "bin": "./bin",
    "lib": "./lib",
    "doc": "./doc",
    "test" "./test",
    "man": "./man"
},

directories.lib

告诉用户模块中lib目录在哪,这个配置目前没有任何作用,但是对使用模块的人来说是一个很有用的信息。

directories.bin

如果你在这里指定了bin目录,这个配置下面的文件会被加入到bin路径下,如果你已经在package.json中配置了bin目录,那么这里的配置将不起任何作用。

directories.man

指定一个目录,目录里边都是man文件,这是一种配置man文件的语法糖。

directories.doc

在这个目录里边放一些markdown文件,可能最终有一天它们会被友好的展现出来(应该是在npm的网站上)

directories.example

放一些示例脚本,或许某一天会有用 - -!

这个属性实际上没有什么实际的作用,当然不排除未来会有什么比较有意义的用处。

package.json文件&目录

下面来看看package.json中和文件以及目录相关的属性。

type

main

main属性指定了程序的主入口文件。意思是,如果你的模块被命名为foo,用户安装了这个模块并通过require("foo")来使用这个模块,那么require返回的内容就是main属性指定的文件中 module.exports指向的对象。 它应该指向模块根目录下的一个文件。对大对数模块而言,这个属性更多的是让模块有一个主入口文件,然而很多模块并不写这个属性。


main字段指定了加载的入口文件,require导入的时候就会加载这个文件。这个字段的默认值是模块根目录下面的index.js。


main 字段用来指定加载的入口文件,在 browser 和 Node 环境中都可以使用。如果我们将项目发布为npm包,那么当使用 require 导入npm包时,返回的就是main字段所列出的文件的module.exports 属性。如果不指定该字段,默认是项目根目录下的index.js。如果没找到,就会报错。

该字段的值是一个字符串:

"main": "./src/index.js",

browser

browser字段可以定义 npm 包在 browser 环境下的入口文件。如果 npm 包只在 web 端使用,并且严禁在 server 端使用,使用 browser 来定义入口文件。

"browser": "./src/index.js" 

module

module字段可以定义 npm 包的 ESM 规范的入口文件,browser 环境和 node 环境均可使用。如果 npm 包导出的是 ESM 规范的包,使用 module 来定义入口文件。

"module": "./src/index.mjs",

需要注意,.js 文件是使用 commonJS 规范的语法(require('xxx')),.mjs 是用 ESM 规范的语法(import 'xxx')。

上面三个的入口入口文件相关的配置是有差别的,特别是在不同的使用场景下。在Web环境中,如果使用loader加载ESM(ES module),那么这三个配置的加载顺序是browser→module→main,如果使用require加载CommonJS模块,则加载的顺序为main→module→browser。

Webpack在进行项目构建时,有一个target选项,默认为Web,即构建Web应用。如果需要编译一些同构项目,如node项目,则只需将webpack.config.js的target选项设置为node进行构建即可。如果再Node环境中加载CommonJS模块,或者ESM,则只有main字段有效。


常见的环境,基本上分为浏览器browser和node环境两大类,接下来我们来看看package.json中,跟环境相关的配置属性。环境的定义可以简单理解如下:

browser环境:比如存在一些只有在浏览器中才会存在的全局变量等,比如window,Document等

node环境: npm包的源文件中存在只有在node环境中才会有的一些变量和内置包,内置函数等。

type

js的模块化规范包含了commonjs、CMD、UMD、AMD和ES module等,最早先在node中支持的仅仅是commonjs字段,但是从node13.2.0开始后,node正式支持了ES module规范,在package.json中可以通过type字段来声明npm包遵循的模块化规范。

//package.json
{
   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包引用不同的入口文件。

// package.json
{ 
  "name":"pkg",
  "main": "./main-require.cjs",
  "exports": {
    "import": "./main-module.js",
    "require": "./main-require.cjs"
  },
  "type": "module"
}

上述的例子中,如果我们通过

const p = require('pkg')

引用的就是"./main-require.cjs"。

如果通过:

import p from 'pkg'

引用的就是"./main-module.js"

最后需要注意的是 : 如果存在exports属性,exports属性不仅优先级高于main,同时也高于module和browser字段。

package.json依赖配置

package.json中跟依赖相关的配置属性包含了dependencies、devDependencies、peerDependencies和peerDependenciesMeta等。dependencies是项目的依赖,而devDependencies是开发所需要的模块,所以我们可以在开发过程中需要的安装上去,来提高我们的开发效率。这里需要注意的时,在自己的项目中尽量的规范使用,形如webpack、babel等是开发依赖,而不是项目本身的依赖,不要放在dependencies中。

dependencies

dependencies字段中声明的是项目的生产环境中所必须的依赖包。当使用 npm 或 yarn 安装npm包时,该npm包会被自动插入到此配置项中:

npm install <PACKAGENAME>
yarn add <PACKAGENAME>

当在安装依赖时使用--save参数,也会将新安装的npm包写入dependencies属性。

npm install --save <PACKAGENAME>

该字段的值是一个对象,该对象的各个成员,分别由模块名和对应的版本要求组成,表示依赖的模块及其版本范围。

"dependencies": {
   "react": "^17.0.2",
   "react-dom": "^17.0.2",
   "react-scripts": "4.0.3",
},

这里每一项配置都是一个键值对(key-value), key表示模块名称,value表示模块的版本号。版本号遵循主版本号.次版本号.修订号的格式规定:

  • 固定版本: 上面的react-scripts的版本4.0.3就是固定版本,安装时只安装这个指定的版本;
  • 波浪号: 比如~4.0.3,表示安装4.0.x的最新版本(不低于4.0.3),也就是说安装时不会改变主版本号和次版本号;
  • 插入号: 比如上面 react 的版本^17.0.2,表示安装17.x.x的最新版本(不低于17.0.2),也就是说安装时不会改变主版本号。如果主版本号为0,那么插入号和波浪号的行为是一致的;
  • latest:安装最新的版本。

URLs as Dependencies

在版本范围的地方可以写一个url指向一个压缩包,模块安装的时候会把这个压缩包下载下来安装到模块本地。

Git URLs as Dependencies

Git url可以像下面一样:

git://github.com/user/project.git#commit-ish
git+ssh://user@hostname:project.git#commit-ish
git+ssh://user@hostname/project.git#commit-ish
git+http://user@hostname/project/blah.git#commit-ish
git+https://user@hostname/project/blah.git#commit-ish

commit-ish 可以是任意标签,哈希值,或者可以检出的分支,默认是master分支。

GitHub URLs

支持github的 username/modulename 的写法,#后边可以加后缀写明分支hash或标签:

{
  "name": "foo",
  "version": "0.0.0",
  "dependencies": {
    "express": "visionmedia/express",
    "mocha": "visionmedia/mocha#4727d357ea"
  }
}

Local Paths

npm2.0.0版本以上可以提供一个本地路径来安装一个本地的模块,通过npm install xxx --save 来安装,格式如下:

../foo/bar
~/foo/bar
./foo/bar
/foo/bar

package.json 生成的相对路径如下:

{
  "name": "baz",
  "dependencies": {
    "bar": "file:../foo/bar"
  }
}

这种属性在离线开发或者测试需要用npm install的情况,又不想自己搞一个npm server的时候有用,但是发布模块到公共仓库时不应该使用这种属性。

devDependencies

devDependencies中声明的是开发阶段需要的依赖包,如Webpack、Eslint、Babel等,用于辅助开发。它们不同于 dependencies,因为它们只需安装在开发设备上,而无需在生产环境中运行代码。当打包上线时并不需要这些包,所以可以把这些依赖添加到 devDependencies 中,这些依赖依然会在本地指定 npm install 时被安装和管理,但是不会被安装到生产环境中。

当使用 npm 或 yarn 安装软件包时,指定以下参数后,新安装的npm包会被自动插入到此列表中:

npm install --save-dev <PACKAGENAME>
yarn add --dev <PACKAGENAME>
"devDependencies": {
  "autoprefixer": "^7.1.2",
  "babel-core": "^6.22.1"
}

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字段就是用来供插件指定其所需要的主工具的版本。

"name": "chai-as-promised",
"peerDependencies": {
   "chai": "1.x"
}

上面代码指定在安装chai-as-promised模块时,主程序chai必须一起安装,而且chai的版本必须是1.x。如果项目指定的依赖是chai的2.0版本,就会报错。

需要注意,从npm 3.0版开始,peerDependencies不再会默认安装了。


peerDependencies是package.json中的依赖项,可以解决核心库被下载多次,以及统一核心库版本的问题。

//package/pkg
----- node_modules
   |-- npm-a -> 依赖了react,react-dom
   |-- npm-b -> 依赖了react,react-dom
   |-- index.js

比如上述的例子中如果子npm包a,b都以来了react和react-dom,此时如果我们在子npm包a,b的package.json中声明了PeerDependicies后,相应的依赖就不会重新安装。

需要注意的有两点:

对于子npm包a,在npm7中,如果单独安装子npm a,其peerDependicies中的包,会被安装下来。但是npm7之前是不会的。

请规范和详细的指定PeerDependicies的配置,笔者在看到有些react组件库,不在PeerDependicies中指定react和react-dom,或者将react和react-dom放到了dependicies中,这两种不规范的指定都会存在一些问题。

其二,正确的指定PeerDependicies中npm包的版本,react-focus-lock@2.8.1,peerDependicies指定的是:"react": "^16.8.0 || ^17.0.0 || ^18.0.0",但实际上,这个react-focus-lock并不支持18.x的react

peerDependenciesMeta

看到“Meta”就有元数据的意思,这里的peerDependenciesMeta就是详细修饰了peerDependicies,比如在react-redux这个npm包中的package.json中有这么一段:

 "peerDependencies": {
    "react": "^16.8.3 || ^17 || ^18"
  },
 "peerDependenciesMeta": {
    "react-dom": {
      "optional": true
    },
    "react-native": {
      "optional": true
    }
  }

这里指定了"react-dom","react-native"在peerDependenciesMeta中,且为可选项,因此如果项目中检测没有安装"react-dom"和"react-native"都不会报错。

值得注意的是,通过peerDependenciesMeta我们确实是取消了限制,但是这里经常存在非A即B的场景,比如上述例子中,我们需要的是“react-dom”和"react-native"需要安装一个,但是实际上通过上述的声明,我们实现不了这种提示。

optionalDependencies

如果需要在找不到包或者安装包失败时,npm仍然能够继续运行,则可以将该包放在optionalDependencies对象中,optionalDependencies对象中的包会覆盖dependencies中同名的包,所以只需在一个地方进行设置即可。

需要注意,由于optionalDependencies中的依赖可能并为安装成功,所以一定要做异常处理,否则当获取这个依赖时,如果获取不到就会报错。

bundledDependencies

上面的几个依赖相关的配置项都是一个对象,而bundledDependencies配置项是一个数组,数组里可以指定一些模块,这些模块将在这个包发布时被一起打包。

需要注意,这个字段数组中的值必须是在dependencies, devDependencies两个里面声明过的包才行。

engines

当我们维护一些旧项目时,可能对npm包的版本或者Node版本有特殊要求,如果不满足条件就可能无法将项目跑起来。为了让项目开箱即用,可以在engines字段中说明具体的版本号:

"engines": {
	"node": ">=8.10.3 <12.13.0",
  "npm": ">=6.9.0"
}

需要注意,engines只是起一个说明的作用,即使用户安装的版本不符合要求,也不影响依赖包的安装。

package.json发布配置

下面来看看和npm项目包发布相关的配置。

private

private字段可以防止我们意外地将私有库发布到npm服务器。只需要将该字段设置为true:

"private": true

preferGlobal

preferGlobal字段表示当用户不把该模块安装为全局模块时,如果设置为true就会显示警告。它并不会真正的防止用户进行局部的安装,只是对用户进行提示,防止产生误解:

"preferGlobal": true

publishConfig

publishConfig配置会在模块发布时生效,用于设置发布时一些配置项的集合。如果不想模块被默认标记为最新,或者不想发布到公共仓库,可以在这里配置tag或仓库地址。更详细的配置可以参考 npm-config

通常情况下,publishConfig会配合private来使用,如果只想让模块发布到特定npm仓库,如一个内部的仓库就可以这样来配置:

"private": true,
"publishConfig": {
  "tag": "1.1.0",
  "registry": "https://registry.npmjs.org/",
  "access": "public"
}

os

os字段可以让我们设置该npm包可以在什么操作系统使用,不能再什么操作系统使用。如果我们希望开发的npm包只运行在linux,为了避免出现不必要的异常,建议使用Windows系统的用户不要安装它,这时就可以使用os配置:

"os" ["linux"]   // 适用的操作系统
"os" ["!win32"]  // 禁用的操作系统

cpu

该配置和OS配置类似,用CPU可以更准确的限制用户的安装环境:

"cpu" ["x64", "AMD64"]   // 适用的cpu
"cpu" ["!arm", "!mips"]  // 禁用的cpu

可以看到,黑名单和白名单的区别就是,黑名单在前面加了一个“!”。

license

license 字段用于指定软件的开源协议,开源协议表述了其他人获得代码后拥有的权利,可以对代码进行何种操作,何种操作又是被禁止的。常见的协议如下:

  • MIT :只要用户在项目副本中包含了版权声明和许可声明,他们就可以拿你的代码做任何想做的事情,你也无需承担任何责任。
  • Apache :类似于 MIT ,同时还包含了贡献者向用户提供专利授权相关的条款。
  • GPL :修改项目代码的用户再次分发源码或二进制代码时,必须公布他的相关修改。

可以这样来声明该字段:

"license": "MIT"

package.json第三方配置

package.json中也存在很多三方属性,比如tsc中使用的types、构建工具中使用的sideEffects,git中使用的husky,eslint使用的eslintIgnore。它们是命令特有的,可以在相应的命令/项目文档中找到如何使用它们。下面来看几个常用的第三方配置项。

types

打包的最终目的是生成一个Npm模块并发布到Npm公有仓库供其他开发者使用。当一个项目安装一个Npm模块后,引用该模块并编码可能会发现该模块的函数名称与入参都有对应模糊提示,这样更友好地提升编码体验。实现上述效果都是typescript的功劳,VSCode会扫描该模块package.json的types字段,该字段引用一个d.ts声明文件,该文件描述类库代码块的相关声明信息,能为VSCode提供类型检查等特性功能。为了更好地拥有该模糊提示功能,推荐使用typescript编写工具库,在打包代码时顺便生成d.ts声明文件。

typings

typings字段用来指定TypeScript的入口文件:

"typings": "types/index.d.ts",

该字段的作用和main配置相同。

eslintConfig

eslint的配置可以写在单独的配置文件.eslintrc.json 中,也可以写在package.json文件的eslintConfig配置项中。

"eslintConfig": {
      "root": true,
      "env": {
        "node": true
      },
      "extends": [
        "plugin:vue/essential",
        "eslint:recommended"
      ],
      "rules": {},
      "parserOptions": {
        "parser": "babel-eslint"
     },
}

babel

babel用来指定Babel的编译配置,代码如下:

"babel": {
	"presets": ["@babel/preset-env"],
	"plugins": [...]
}

unpkg

使用该字段可以让 npm 上所有的文件都开启 cdn 服务,该CND服务由unpkg提供:

"unpkg": "dist/vue.js"

lint-staged

lint-staged是一个在Git暂存文件上运行linters的工具,配置后每次修改一个文件即可给所有文件执行一次lint检查,通常配合gitHooks一起使用。

"lint-staged": {
	"*.js": [
  	"eslint --fix",
    "git add"
  ]
}

使用lint-staged时,每次提交代码只会检查当前改动的文件。

gitHooks

gitHooks用来定义一个钩子,在提交(commit)之前执行ESlint检查。在执行lint命令后,会自动修复暂存区的文件。修复之后的文件并不会存储在暂存区,所以需要用git add命令将修复后的文件重新加入暂存区。在执行pre-commit命令之后,如果没有错误,就会执行git commit命令:

"gitHooks": {
	"pre-commit": "lint-staged"
}

这里就是配合上面的lint-staged来进行代码的检查操作。

browserslist

browserslist字段用来告知支持哪些浏览器及版本。Babel、Autoprefixer 和其他工具会用到它,以将所需的 polyfill 和 fallback 添加到目标浏览器。比如最上面的例子中的该字段值:

"browserslist": {
  "production": [
    ">0.2%",
    "not dead",
    "not op_mini all"
  ],
  "development": [
    "last 1 chrome version",
    "last 1 firefox version",
    "last 1 safari version"
  ]
}

这里指定了一个对象,里面定义了生产环境和开发环境的浏览器要求。上面的development就是指定开发环境中支持最后一个版本的chrome、Firefox、safari浏览器。这个属性是不同的前端工具之间共用目标浏览器和 node 版本的配置工具,被很多前端工具使用,比如Babel、Autoprefixer等。

nodemonConfig

{
	"nodemonConfig": {
		"env": {
			"NODE_ENV": "dev"
		},
		"execMap": {
			"js": "node --harmony"
		},
		"ext": "js json",
		"ignore": [
			"dist/"
		],
		"watch": [
			"src/"
		]
	}
}

修改src/index.js内容,nodemon就能快速响应改动并重启命令。nodemon配置可查看Nodemon官网,在此不深入讲述了。

相关问题

问题一

问:npm包。如何指定使用我本地文件。或者说,如何调试npm包

答:npm link或yalc

相关文章

  1. docs.npmjs.com/cli/v8/conf…