Node.js Package Manager Part I - Package.json fields & Sematic Version

Introduction

成熟的编程语言一般都有相应的包管理工具,比如说 Python 中的 pip,Java 中的 Maven,PHP 中的 composer 等等。
对于 Node.js 来说,可选择的包管理工具比较多,比较有名的包括 npm,yarn 和 pnpm。
作为 Node.js 包管理系列文章的第一篇,这里先简单介绍 package 及其相关的周边内容

Package & Module

package 和 module 是相对的概念,官方文档上是这么定义的:

A package is a file or directory that is described by a package.json file. A package must contain a package.json file in order to be published to the npm registry.

A module is any file or directory in the node_modules directory that can be loaded by the Node.js require() function
可以看出来 package 和 module 相互纠缠,相互包含,共同组成了 Node.js 的生态

package.json

说到了 package,不得不提一下 package.json 文件。对于平常的前端仓库,想要快速入手的话基本上都要瞄一下 package.json,找到对应的 dependencies,scripts 等内容,看完之后对仓库整体会有一个大致的了解。

先整体看一下 npm 官网上的整体配置项,对 package.json 有个整体的了解
package.json configs

那么除了平常我们遇到过的 dependencies,scripts 等配置项之外,还有什么需要我们注意的地方呢?

files

files 用来描述哪些文件/文件夹在包作为依赖安装时会被包括进来,打个比方,ant design 的package.json文件中有关于 files 的配置,如下所示:

1
2
3
{
"files": ["dist", "lib", "es"]
}

这样用户在安装包的时候,只会安装对应的 files 配置内所包含的文件,减小了安装包的体积

另外如下的文件如果在 package 内存在,则不管配置如何在安装的时候都会包括在内:

  • package.json
  • README
  • CHANGES / CHANGELOG / HISTORY
  • LICENSE / LICENCE
  • NOTICE
  • The file in the “main” field

main

定义了被用户 require()引入时对应的文件路径,往往是一个单一的文件

browser

很多 package 现在只被用在浏览器环境下,这时应该定义 browser 配置,而不是 main 配置。这一点在 webpack5 中也有所涉及,webpack5 建议使用 browser 配置来增加 package 的前端兼容性

NOTICE

关于 package 的入口定义配置,除了 main,browser 之外,还有一个提案module,现在已经被 webpack 等打包工具所接受,三者之间遵循如下规则:

  1. main 在三者中的优先级最低,出现其他两个配置会以其他的为准
  2. 在浏览器环境下(webpack target 设置为 webworker, web 或者不设置) browser 会替代 main 成为 package 的入口[1]
  3. 在 node 环境下 module 会替代 main 成为 package 的入口[2]
  4. 在 webpack 中,如果 target 为 node,整体优先级为 module->main,其他配置整体优先级为: browser->module->main

    Update: module 是使用 ESM 格式下的首选入口,以上的几点是 webpack 下的默认 resolve 读取顺序,可以通过resolve.mainFields进行调整

bin

bin 配置多用于开发 cli,全局安装时会在 prefix/bin 路径下(例如在 linux 环境的/usr/local/bin 路径)创建软链接,本地安装时则在./node_modules/.bin/路径下

需要注意的是指定的文件需要以#!/usr/bin/env node开头

peerDependencies

peerDependencies 一般用在插件和周边的包中,peerDependencies 在 npm7 之前如果不安装只会给出警告,npm7 会默认安装

peerDependenciesMeta

该选项一般配合 peerDependencies 使用,如果某个包的可选项(optional)配置为 true,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"name": "tea-latte",
"version": "1.3.5",
"peerDependencies": {
"tea": "2.x",
"soy-milk": "1.2"
},
"peerDependenciesMeta": {
"soy-milk": {
"optional": true
}
}
}

那么 peerDependencies 指定的包不会默认安装

optionalDependencies

配置可选的依赖项,这些依赖项会在npm install时同时安装,类似于 dependencies,但是 optionalDependencies 内的同名依赖项会覆盖 dependencies 内的依赖项,所以最好只在一个地方配置依赖项

NOTICE

optionalDependencies 和 peerDependenciesMeta 看起来功能有点类似,但是本质上是有所区别的[3]

  • optionalDependencies 是在安装的时候,如果没有安装可选依赖,那么整个npm install不会失败报错,
  • peerDependenciesMeta 则是对 peerDependencies 的依赖进行进一步的说明

bundledDependencies

指定一组没有版本号的模块,这些模块会在包发布时一起被打包,主要用于离线部署等方面,具体的用法[4]作出了解释

以上就是平常中需要注意的配置项,关于 npm7 和 yarn2 引入的workspaces新概念,会在后续的部分加以介绍和解释

Semantic Versioning & dependencies

关于语义化版本的定义和具体内容,引用官方的描述:

Given a version number MAJOR.MINOR.PATCH, increment the:

  1. MAJOR version when you make incompatible API changes,

  2. MINOR version when you add functionality in a backwards compatible manner, and

  3. PATCH version when you make backwards compatible bug fixes.

Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format.

包管理和发布应当遵循以上规则,这里不再赘述

对于 package.json 内的各种 dependencies,有很多种方式:

  1. URLs:
    指定可下载的压缩包地址
  2. Git URLs:
    <protocol>://[<user>[:<password>]@]<hostname>[:<port>][:][/]<path>[#<commit-ish> | #semver:<semver>]
  3. GitHub URLs:
    形如foo:"user/foo-project"的 github 链接,可以带 commit-ish
  4. Local Paths:
    形如file: ../foo/bar形式的本地路径安装

除此之外,还有我们最为熟悉的包名+版本号的依赖方式,遵循node semver内的规则,这里主要说一下两个比较容易让人困惑的点:

  1. Tilde Ranges: ~1.2.3 ~1.2 ~1
    Allows patch-level changes if a minor version is specified on the comparator. Allows minor-level changes if not.
  2. Caret Range ^1.2.3 ^0.2.5 ^0.0.4
    Allows changes that do not modify the left-most non-zero element in the [major, minor, patch] tuple. In other words, this allows patch and minor updates for versions 1.0.0 and above, patch updates for versions 0.X >=0.1.0, and no updates for versions 0.0.X

对于 MAJOR 和 MINOR 版本确定的版本号,Tilde Range 主要用途在于可以保持 PATCH 版本的稳定性,而对于 Caret Range 则除了接收 PATCH 的 bug 修复之外,还可以更新 MINOR 版本的新功能,
所以npm install --save安装时默认是 Caret Range。但是需要注意的是语义化版本中以 1.0.0 版本为稳定版本的初始版本,那么对于 Caret Range 而言,0.X 则只更新 PATCH 版本,0.0.x 则不进行相应的升级

Next Time

作为 Node.js 环境下的包管理的第一部分,主要对一些前置内容作了补充,以便后续内容的深入,下一章主要讲解 Node.js 自带的包管理工具-NPM

Comments