共计 3579 个字符,预计需要花费 9 分钟才能阅读完成。
问题
run 一个 CRA 我的项目,应用 npm 与 yarn 安装包,发现 npm 装置的包有 @babel/plugin-proposal-optional-chaining
, 而 yarn 装置的没有 @babel/plugin-proposal-optional-chaining
。本地 npm 安装包后启动失常,而生产环境应用的 yarn, 造成构建失败。
起因
yarn install 装置生成的 yarn.lock 文件 与 npm install 生成的 package-lock.json 文件工夫相差较远,造成了 yarn.lock 的包版本低于 package-lock.json 的包版本。因为以 ˆx.x.x
模式定义的包版本在不同期间安装包版本不统一。
yarn 装置 @babel/preset-env
版本有:“7.5.5”, “^7.4.5″,理论装置的 version 是 “7.5.5”。
npm 装置 @babel/preset-env
版本有:“7.9.0”, “^7.4.5″,理论装置的 version 是 “7.9.0” 和 “7.11.5”。而在“7.8.3”版本里首次依赖 @babel/plugin-proposal-optional-chaining
。
解决:如果执行 yarn upgeade
就会更新 yark.lock 文件, 获取最新的包版本。
拓展
package.json
package.json 次要用来记录依赖包名称、版本、运行指令等信息字段。
其中,dependencies 字段指定了我的项目运行所依赖的模块,devDependencies 指定我的项目开发所须要的模块。它们都指向一个对象,该对象的各个成员,别离由模块名和对应的版本组成,示意依赖的模块及其版本范畴。
版本限定形式【语义化版本】,次要有以下几种:
- 指定版本:比方 1.2.2,遵循“大版本. 主要版本. 小版本”的格局规定,装置时只装置指定版本。
- 波浪号(tilde)+ 指定版本:比方 ~1.2.2,示意装置 1.2.x 的最新版本(不低于 1.2.2),然而不装置 1.3.x,也就是说装置时不扭转大版本号和主要版本号。
- 插入号(caret)+ 指定版本:比方 ˆ1.2.2,示意装置 1.x.x 的最新版本(不低于 1.2.2),然而不装置 2.x.x,也就是说装置时不扭转大版本号。须要留神的是,如果大版本号为 0,则插入号的行为与波浪号雷同,这是因为此时处于开发阶段,即便是主要版本号变动,也可能带来程序的不兼容。
- latest:装置最新版本。
当咱们应用比方 npm install package -save
装置一个依赖包时,版本是插入号模式。这样每次重新安装依赖包 npm install 时”主要版本“和“小版本”是会拉取最新的。个别的,主版本不变的状况下,不会带来外围性能变动,API 应该兼容旧版。
package-lock.json
在 npm5 版本后,运行 npm intall 会生成一个新文件 package-lock.json。
当我的项目中已有 package-lock.json 文件,在装置我的项目依赖时,将以该文件为主进行解析装置指定版本依赖包,而不是应用 package.json 来解析和装置模块。因为 package-lock 为每个模块及其每个依赖项指定了版本,地位和完整性哈希,所以它每次创立的装置都是雷同的。无论你应用什么设施,或者未来装置它都无关紧要,每次都应该给你雷同的后果。
yarn
yarn 的呈现次要指标是解决因为语义版本控制而导致的 npm 装置的不确定性问题。
每个 yarn 装置都会生成一个相似于 npm-shrinkwrap.json 的 yarn.lock 文件,而且它是默认创立的。除了惯例信息之外,yarn.lock 文件还蕴含要装置的内容的校验和,以确保应用的库的版本雷同。
yarn 的次要优化
- 并行装置:无论 npm 还是 yarn 在执行包的装置时,都会执行一系列工作。npm 是依照队列执行每个 package,也就是说必须要等到以后 package 装置实现之后,能力持续前面的装置。而 yarn 是同步执行所有工作,进步了性能。
- 离线模式:如果之前曾经装置过一个软件包,用 yarn 再次装置时之间从缓存中获取,就不必像 npm 那样再从网络下载了。
- 装置版本对立:为了避免拉取到不同的版本,yarn 有一个锁定文件 (lock file) 记录了被装置上的模块的版本号。每次只有新增了一个模块,yarn 就会创立(或更新)yarn.lock 这个文件。这么做就保障了,每一次拉取同一个我的项目依赖时,应用的都是一样的模块版本。
- 更好的语义化:yarn 扭转了一些 npm 命令的名称,比方 yarn add/remove,比 npm 本来的 install/uninstall 要更清晰。
装置依赖树流程
- 执行工程本身 preinstall
如果定义了 preinstall 钩子此时会被执行。 - 确定首层依赖
首先须要做的是确定我的项目中的首层依赖,也就是 dependencies 和 devDependencies 属性中间接指定的模块(假如此时没有增加 npm install 参数)。
我的项目自身是整棵依赖树的根节点,每个首层依赖模块都是根节点上面的一棵子树,npm 会开启多过程从每个首层依赖模块开始逐渐寻找更深层级的节点。 -
下载模块
下载模块是一个递归的过程,分为以下几步:- 获取模块版本信息。在下载一个模块之前,首先要确定其版本,这是因为 package.json 中往往是语义化版本。
如果版本形容文件(npm-shrinkwrap.json 或 package-lock.json)中有该模块信息间接用即可,如果没有则从仓库获取。如 package.json 中某个包的版本是 ^1.1.0,npm 就会去仓库中获取合乎 1.x.x 模式的最新版本。 - 获取模块实体。上一步会获取到模块的压缩包地址(resolved 字段),npm 会用此地址查看本地缓存,缓存中有就间接拿,如果没有则从仓库下载。
- 查找该模块依赖,如果有依赖则回到第 1 步,如果没有则进行。
- 获取模块版本信息。在下载一个模块之前,首先要确定其版本,这是因为 package.json 中往往是语义化版本。
- 模块扁平化(dedupe)。
上一步获取到的是一棵残缺的依赖树,其中可能蕴含大量反复模块。比方 A 模块依赖于 loadsh,B 模块同样依赖于 lodash。在 npm3 以前会严格依照依赖树的构造进行装置,因而会造成模块冗余。yarn 和从 npm5 开始默认退出了一个 dedupe 的过程,它会遍历所有节点,一一将模块放在根节点上面,也就是 node-modules 的第一层。当发现有 反复模块 时,则将其抛弃。
反复模块
它指的是模块名雷同且语义化兼容。每个语义化版本都对应一段版本容许范畴,如果两个模块的版本容许范畴存在交加,那么就能够失去一个兼容版本,而不用版本号完全一致,这能够使更多冗余模块在 dedupe 过程中被去掉。
- 装置模块
这一步将会更新工程中的 node_modules,并执行模块中的生命周期函数(依照 preinstall、install、postinstall 的程序)。 - 执行我的项目本身生命周期
以后 npm 工程如果定义了钩子此时会被执行(依照 install、postinstall、prepublish、prepare 的程序)
装置依赖实例
插件 htmlparser2@^3.10.1
和 dom-serializer@^0.2.2
都有应用了 entities
依赖包,不过应用的版本不同,同时咱们本人装置一个版本的 entities 包。具体如下:
--htmlparser2@^3.10.1
|--entities@^1.1.1
--dom-serializer@^0.2.2
|--entities@^2.0.0
--entities@^2.1.0
通过 npm install 装置后,生成的 package-lock.json 文件内容和它的 node_modules 目录构造:
npm-version
能够发现:
dom-serializer@^0.2.2 的依赖包 entities@^2.0.0 和咱们本人装置的 entities@^2.1.0 被理论装置成 entities@^2.2.0,并放在 node_modules 的第一层。因为这两个版本的 semver 范畴雷同,又先被遍历,所有会被合并装置在第一层;
htmlparser2@^3.10.1 的依赖包 entities@^1.1.1 被理论安放在 dom-serializer 包的 node_modules 中,并且和 package-lock.json 形容构造保持一致。
通过 yarn 装置后,生成的 yarn.lock 文件内容和它的 node_modules 目录构造:
yarn-version
能够发现与 npm install 不同的是:
yarn.lock 中所有依赖形容都是扁平化的,即没有依赖形容的嵌套关系;
在 yarn.lock 中,雷同名称版本号不同的依赖包,如果 semver 范畴雷同会被合并,否则,会存在多个版本形容。
参考:npm/yarn lock 真香