💡 Question:在项目开发的时候,我们也经常需要安装和升级对应的依赖。虽然 npm 以及语意化的版本号 (semantic versioning, semver) 让开发过程中依赖的获取和升级变得非常容易, 但不严格的版本号限制,也带来了版本号的不确定性。本章我们就来一起回顾一下npm中控制工程安装版本的文件。
package.json
什么是package.json
package-lock由npm 5版本后引入,npm 5版本前由package.json控制工程安装的版本,遵循semver机制。
如何生成package.json
npm init 交互式问答的方式生成 package.json
npm init -y/--yes 自动生成默认的 package.json
package.json配置详解
描述配置
name
项目的名称,如果是第三方包的话,其他人可以通过该名称使用 npm install 进行安装,有些包名会含有 scope
"name": "@icedesign/pro-scaffold",
name 规范:
- 不多于 214 个字符,包括 scope
- 不能以 . 或 _ 开头
- 不能包含大写字母
- 有可能会成为 URL 的一部分、命令行参数或者文件夹名字,因此不能包含任何非 URL 安全字符,如空格、
(、``、"; - 可能会作为 require() 的参数传递
- 最好取简短而语义化的值
- 不能和 NPM 网站中已有的包名字重名
scope:
scope 是一种将相关的模块组织到一起的一种方式,可以防止包重名。
命名遵循 name 的规范,scope 前面是 @ 符号,后面是 /,格式为 @somescope/somepackagename,如:@somescope/somepackagename,会被安装在 node_modules/@somescope/somepackagename下。
举个例子:
version
当前的版本,开源项目的版本号通常遵循 semver 语义化规范。
"version": "3.0.15",
package.json 中最重要的属性是 name 和 version 两个属性,这两个属性是必须要有的,否则模块就无法被安装,这两个属性一起形成了一个 npm 模块的唯一标识。
description
项目的描述,会展示在 npm 官网,让别人能快速了解该项目。
"description": "橙狮体育商家平台",
keywords
一组项目的技术关键词,比如 Ant Design 组件库的 keywords 如下:
"keywords": [
"ant",
"component",
"components",
"design",
"framework",
"frontend",
"react",
"react-component",
"ui"
],
homepage
项目主页的链接,通常是项目 github 链接,项目官网或文档首页。
repository
指定代码存放地址
"repository": {
"type": "git",
"url": "https://github.com/xxx/xxx.git"
}
bugs
项目 bug 反馈地址,通常是 github issue 页面的链接或者邮箱
// 如果提供了 url,则可以被 npm bugs 命令使用
"bugs": {
"url": "",
"email": ""
}
license
项目的开源许可证。项目的版权拥有人可以使用开源许可证来限制源码的使用、复制、修改和再发布等行为。
author
项目作者
"author": {
"name": "zhanbaihe",
"email": "zhanbaihe.zbh@alibaba-inc.com",
"url": "https://xxxx.com"
}
"author": "zhanbaihe zhanbaihe.zbh@alibaba-inc.com"
contributors
贡献者
"contributors": [
{
"name": "zhanbaihe",
"email": "zhanbaihe.zbh@alibaba-inc.com",
"url": "https://xxxx.com"
}
]
文件配置
bin
许多软件包都具有一个或多个要安装到 PATH 中的可执行文件。
bin 字段是命令名到本地文件名的映射。在安装时,npm 会将文件符号链接到 prefix/bin 以进行全局安装或./node_modules/.bin/本地安装。
当我们使用 npm 或者 yarn 命令安装包时,如果该包的 package.json 文件有 bin 字段,就会在 node_modules 文件夹下面的 .bin 目录中复制了 bin 字段链接的执行文件。我们在调用执行文件时,可以不带路径,直接使用命令名来执行相对应的执行文件。
{
"bin": "./path/to/program"
}
{
"bin" : { "my-program" : "./path/to/program" }
}
举个例子:
main
指定加载模块的入口文件,require() 导入的时候就会加载这个文件。默认值是模块根目录下的 index.js。
脚本配置
scripts
指定项目的一些内置脚本命令,这些命令可以通过 npm run 来执行。通常包含项目开发,构建 等 CI 命令。
举个例子:
"scripts": {
"zbh": "a"
},
除了指定基础命令,还可以配合 pre 和 post 完成命令的前置和后续操作。
举个例子:
"scripts": {
"prebuild" : "echo " this is pre build "",
"build" : "echo " this is build "",
"postbuild" : "echo " this is post build ""
}
当执行 npm run build 命令时,会按照 prebuild -> build -> postbuild 的顺序依次执行上方的命令。利用这个特性可以在执行某些命令前、后做一些操作,比如build前清空目录,build后做一些压缩之类的事。
依赖配置
dependencies
运行依赖,也就是项目生产环境下需要用到的依赖。比如 react,vue,状态管理库以及组件库等。
使用 npm install xxx 或则 npm install xxx --save 时,会被自动插入到该字段中。
devDependencies
开发依赖,项目开发环境需要用到而运行时不需要的依赖,用于辅助开发,通常包括项目工程化工具比如 webpack,vite,eslint 等(开发、测试、打包工具)。
使用 npm install xxx -D 或者 npm install xxx --save-dev 时,会被自动插入到该字段中。
peerDependencies
同伴依赖,一种特殊的依赖,不会被自动安装,通常用于表示与另一个包的依赖与兼容性关系来警示使用者。
比如我们安装 A,A 的正常使用依赖 B@2.x 版本,那么 B@2.x 就应该被列在 A 的 peerDependencies 下,表示“如果你使用我,那么你也需要安装 B,并且至少是 2.x 版本”。
举个例子:
仔细查看 antd 的 package.json 中的 dependencies,发现并未安装 react 和 react-dom,但是随意打开一个组件你就会发现,它依赖 react 和 react-dom,如下:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
那 antd 是怎么运行起来的呢?根本原因就是peerDependencies,antd 的 package.json 中 peerDependencies,如下:
"peerDependencies": {
"react": ">=16.0.0",
"react-dom": ">=16.0.0"
}
antd 不能独立的运行,需要使用它的项目中安装了 react 和 react-dom 才可以运行。
所以,要想在你的项目中使用 antd,就必须得安装 react 和 react-dom。
作用:
- 没有peerDependencies会发生什么?
-
- 还是以 antd 为例,假设没有 peerDependencies,为了保证 antd 能够顺利的在宿主环境中运行起来,它不得不在 dependencies 中添加 react 和 react-dom:
"dependencies": {
"react": ">=16.0.0",
"react-dom": ">=16.0.0"
}
在 npm3 以前,也就是 node_modules 是嵌套而非平铺的年代。 如果开发者使用 antd 并且没有peerDependencies 的话,就需要安装两次 react 和 react-dom。
- peerDependencies的优点
-
- npm 3 以前避免重复安装(npm3之后 node_modules 层级发生了变化,直接解决了重复安装的问题)
- 提示宿主环境
-
-
- 当 npm 版本为 1、2 和 7 时,如果宿主环境没有安装 peerDependencies 中指定版本或更高版本的依赖,将自动安装,但自动安装将造成另外的问题,即可能宿主会在”不自主安装“的情形直接使用这些自动安装的依赖
- 当 npm 版本 3 到 6 时,并不会自动安装,但是将收到未安装 peerDependency 的警告。如下:
-
npm WARN antd@4.19.3 requires a peer of react@>=16.9.0 but none is installed. You must install peer dependencies yourself.
发布配置
private
设置为 true,npm 将拒绝发布它,为了防止私有模块被无意间发布出去。
如果是私有项目,不希望发布到公共 npm 仓库上,可以将 private 设为 true。
yarn.lock、package-lock.json
package-lock.json
package-lock出现的主要目的时为了保证共同的开发者,所安装的具体的package是同一个version,而不是semver所规定的版本规则。package-lock.json 会固化当前安装的每个软件包的版本。当运行 npm update 时,package-lock.json 中的软件包的版本会被更新。
不同 npm 版本下 npm install 的规则
npm 5.0.x不管 package.json 中依赖是否更新, 都会根据 package-lock.json 下载npm 5.1.0后,当 package.json 中的依赖项有新版本时,npm install会无视 package-lock.json 去下载新版本的依赖并更新 package-lock.json5.4.2版本后,
-
-
如果只有一个 package.json 文件,运行
npm i会根据它生成一个 package-lock.json 文件 -
如果 package.json 的
semver-range version和 package-lock.json 中版本兼容,即使 package.json 中有新的版本,也还是会根据 package-lock.json 下载 -
如果手动修改了 package.json 的 version ranges,且和 package-lock.json 中版本不兼容,那么执行
npm i时 package-lock.json 将会更新到兼容 package.json 的版本
-
如何生成 package-lock.json
npm install 安装 package.json 中的依赖,并生成 package-lock.json, 锁住依赖及依赖的依赖的版本
npm install <pkg-name> 安装某个软件包
npm update <pkg-name> 更新某个软件包
举个例子
yarn.lock
官方对 yarn.lock 文件的说明如下:
为了跨机器安装得到一致的结果, Yarn 需要比你配置在 package.json 中的依赖列表更多的信息。 Yarn 需要准确存储每个安装的依赖是哪个版本。
为了做到这样, Yarn 使用一个你项目根目录里的 yarn.lock 文件。这可以媲美其他像 Bundler 或 Cargo 这样的包管理器的 lockfiles。它类似于 npm 的 npm-shrinkwrap.json, 然而他并不是有损的并且它能创建可重现的结果。
如何生成 yarn.lock
yarn install 安装package.json里所有包,并将包及它的所有依赖项保存进yarn.lock
yarn install --flat 安装一个包的单一版本
yarn install --force 强制重新下载所有包
yarn install --production 只安装dependencies里的包
yarn upgrade 用于更新包到基于规范范围的最新版本
举个例子
区别
速度
yarn的速度要更快:
- 并行安装:无论 npm 还是 Yarn 在执行包的安装时,都会执行一系列任务。npm 是按照队列执行每个 package,也就是说必须要等到当前 package 安装完成之后,才能继续后面的安装。而 Yarn 是同步执行所有任务,提高了性能。
- 离线模式:如果之前已经安装过一个软件包,用Yarn再次安装时之间从缓存中获取,就不用像npm那样再从网络下载了。
文件结构
package-lock.json把所有的包的依赖顺序列出来,第一次出现的包名会提升到顶层,后面重复出现的将会放入被依赖包的node_modules当中。引起不完全扁平化问题。
显然yarn.lock锁文件把所有的依赖包都扁平化的展示了出来,对于同名包但是semver不兼容的作为不同的字段放在了yarn.lock的同一级结构中。
yarn.lock 文件中的信息也比 package.json 文件中详细了很多。
是否可提交
当前,我们有两个不同的程序包管理系统,它们都从package.json安装了相同的依赖项集,但是它们从两个不同的锁文件生成和读取。 NPM 5生成package-lock.json,而Yarn生成yarn.lock。
如果提交package-lock.json,则表示正在支持使用NPM 5安装依赖项的人员。如果提交yarn.lock,则表示正在支持使用Yarn安装依赖性的人。
是否选择提交yarn.lock或package-lock.json或同时提交,取决于在项目上开发的人仅使用Yarn还是NPM 5或同时使用这两种方式。如果您的项目是开源的,那么对社区最友好的事情可能就是同时提交这两者,并有一个自动化的过程来确保yarn.lock和package-lock.json始终保持同步。
yarn.lock 与 package-lock.json 相互转换
安装 synp:
npm install -g synp
命令行用法:
yarn.lock=>package-lock.json
yarn # be sure the node_modules folder dir and is updated
synp --source-file /path/to/yarn.lock
# will create /path/to/package-lock.json
package-lock.json=>yarn.lock
npm install # be sure the node_modules dir exists and is updated
synp --source-file /path/to/package-lock.json
# will create /path/to/yarn.lock
npm-shrinkwrap.json
npm-shrinkwrap.json 是在 npm5 之前(不包括5),主要是用来精确控制安装制定版本的npm包。它长得像package-lock.json,功能也很像。
如何生成 npm-shrinkwrap.json
****npm shrinkwrap 通过运行该命令行就可以生成 npm-shrinkwrap.json 文件
package-lock.json 与 npm-shrinkwrap.json 的区别与联系
npm版本
package-lock.json是npm5的新特性,也不向前兼容,如果npm版本是4或以下,需要使用npm-shrinkwrap.json
npm处理机制
在一个项目里,如果本身不存在这两个文件,那么在运行 npm install 时,会自动生成一个 package-lock.json ,或者在初始化一个项目 npm init 时,也会生成package-lock.json,安装信息会依据该文件进行,而不是单纯按照 package.json,这两个文件的优先级都比 package.json 高。
如果项目两个文件都存在,那么安装的依赖是依据npm-shrinkwrap.json来的,而忽略package-lock.json。官方说明如下:
npm shrinkwrap 命令创建和更新的文件将优先于任何其他现有或将有的 package-lock.json 文件。
运行命令 npm shrinkwrap 后,如果项目里不存在 package-lock.json ,那么会新建一个 npm-shrinkwrap.json 文件,如果存在 package-lock.json ,那么会把 package-lock.json 重命名为 npm-shrinkwrap.json 。
更新机制
npm-shrinkwrap.json只会在运行npm shrinkwrap才会创建/更新
package-lock.json会在修改pacakge.json或者node_modules时就会自动产生或更新了
发布包
package-lock.json不会在发布包中出现,就算出现了,也会遭到npm的无视。
npm-shrinkwrap.json可以在发布包中出现,建议库作者发布包时不要包含npm-shrinkwrap.json,因为这会阻止最终用户控制传递依赖性更新。第三方包如果在npm-shrinkwrap.json中锁定了版本号,引用该包的项目如果和该包有共同的依赖包,就可能会安装/打包多个版本的依赖包。
补充
semver
semver是一套跨语言的语义化版本号标准,简单解释为版本控制的机制,格式为X.Y.Z,X代表主版本,Y代表次要版本,Z代表补丁版本
package的开发者需要遵循semver的机制发布版本迭代:
X:代表大版本迭代,可能引起前后版本的不兼容
Y:代表小版本,功能性增加,不会引起低版本兼融问题
Z:bug修复补丁
如果没有package-lock的情况下,npm install默认安装最新的当前大版本的最新version,也就是说在不影响到兼容的情况下,默认安装最新版本。
semver通配符:
- 符号^:表示安装不低于当前版本的应用,但大版本号需要相同,如react: "^16.4.0",16.4.0及以上的16.X.X都满足。
- 无符号:无符号表示固定版本号,例如:react: "16.4.0",此时一定是安装16.4.0版本。
node_modules 层级
在 npm 的早期版本, npm 处理依赖的方式简单粗暴,以'递归的形式',在npm3版本 后采用了'扁平结构'
npm 2.x
执行 'npm install' 后,'npm' 根据 'dependencies' 和 'devDependencies' 属性中指定的包来确定第一层依赖,npm 2 会根据第一层依赖的子依赖,递归安装各个包到子依赖的 'node_modules' 中,直到子依赖不再依赖其他模块。执行完毕后,我们会看到 './node_modules' 这层目录中包含有我们 'package.json' 文件中所有的依赖包,而这些依赖包的子依赖包都安装在了自己的 'node_modules' 中 结构目录:
├── node_modules
│ ├── A@1.0.0
│ │ └── node_modules
│ │ │ └── D@1.0.0
│ ├── B@1.0.0
│ │ └── node_modules
│ │ │ └── D@2.0.0
│ └── C@1.0.0
│ │ └── node_modules
│ │ │ └── D@1.0.0
结构层级图
果你依赖的模块非常之多,你的 node_modules 将非常庞大,嵌套层级非常之深,出现下图的效果,并且也会导致接下来的问题:
- 在不同层级的依赖中,可能引用了同一个模块,导致大量冗余,举个例子当我的 A,B,C 三个包中有相同的依赖 D 时,执行 npm install 后,D 会被重复下载三次
- 在 Windows 系统中,文件路径最大长度为260个字符,嵌套层级过深可能导致不可预知的问题。
npm 3.x
npm3.x 版本后采用了扁平化的解决方案,把依赖以及依赖的依赖平铺'node_modules' 文件夹下共享使用,npm 3 会遍历所有的节点,逐个将模块放在 'node_modules'的第一层,当发现有重复模块时,则丢弃, 如果遇到某些依赖版本不兼容的问题,则继续采用 npm 2 的处理方式,前面的放在 'node_modules' 目录中,后面的放在依赖树中。'过程说明':下图 'A','B' 都依赖'D(v0.0.1)'那么在安装的时候将'A'的依赖包都平铺到'node_modules',即将'D(v0.0.1)'平铺到了'node_modules' ,此时到了安装B包发现B包依赖在'node_modules' 根已经重复这时候就跳过了,但是安装C的时候发现C也依赖'D'但是依赖的版本不同,如果将C依赖的'D'放到'node_modules'根就因为重名问题覆盖掉此时解决方案就像'npm2.x'的时候就将C依赖的'D(v0.0.2)'安装到了C的'node_modules'下。
遗留问题待解决:
npm3是否彻底解决了npm2的问题