- 1、运行 npm run xxx 的时候发生了什么?
- 运行 npm run xxx 的时候,npm 会先在当前目录的 node_modules/.bin 查找要执行的程序,如果找到则运行;
- 没有找到则从全局的 node_modules/.bin 中查找【npm i -g xxx 就安装到了全局目录】
- 如果全局目录还是没找到,那么就从 path 环境变量中查找有没有其他同名的可执行程序
- 2、yarn 和 npm 比,优点有哪些?
- 并行安装:无论 npm 还是 yarn 在执行包的安装时,都会执行一系列任务。npm 是按照队列执行每个 package,也就是说必须要等到当前 package 安装完成之后,才能继续后面的安装。而 yarn 是同步执行所有任务,提高了性能。
- 离线模式:如果之前已经安装过一个软件包,用 yarn 再次安装时之间从缓存中获取,就不用像 npm 那样再从网络下载了。
- 安装版本统一:为了防止拉取到不同的版本,yarn 有一个锁定文件 (lock file) 记录了被确切安装上的模块的版本号。每次只要新增了一个模块,yarn 就会创建(或更新)yarn.lock 这个文件。这么做就保证了,每一次拉取同一个项目依赖时,使用的都是一样的模块版本【npm 5 以后,这个和 yarn 统一了】
- 更好的语义化:yarn 改变了一些 npm 命令的名称,比如 yarn add/remove,比 npm 原本的 install/uninstall 要更清晰。
- 3、yarn 和 npm 在卸载了包以后,node_modules 里面的包会被删除吗?
- yarn 和 npm 在卸载了包以后,node_modules 里面的包是不会被删除的
- 这样做可能是为了复装更快捷
- 4、关于 npm 的 flatten【扁平】和 dedupe【去重】[alias:ddp] 如何理解?
- npm3 以前,采用了 flatten【扁平】机制来处理,使更多包扁平化,但是机制很不稳定,来回修改
- npm5 以后逐渐采用 dedupe【扁平且去重】机制,这个可以理解为升级版的 flatten 机制
- 模块扁平化(dedupe)。上一步获取到的是一棵完整的依赖树,其中可能包含大量重复模块。比如 A 模块依赖于 loadsh,B 模块同样依赖于 lodash。在 npm3 以前会严格按照依赖树的结构进行安装,因此会造成模块冗余。yarn 和从 npm5 开始默认加入了一个 dedupe 的过程。它会遍历所有节点,逐个将模块放在根节点下面,也就是 node-modules 的第一层。当发现有重复模块时,则将其丢弃。这里需要对重复模块进行一个定义,它指的是模块名相同且 semver 兼容。每个 semver 都对应一段版本允许范围,如果两个模块的版本允许范围存在交集,那么就可以得到一个兼容版本,而不必版本号完全一致,这可以使更多冗余模块在 dedupe 过程中被去掉。
- npm 官网在 6~8 的版本中基本也是采用这样一种机制,原文如下:
Searches the local package tree and attempts to simplify the overall structure by moving dependencies further up the tree, where they can be more effectively shared by multiple dependent packages. For example, consider this dependency graph: a -- b <-- depends on c@1.0.x | `-- c@1.0.3 `-- d <-- depends on c@~1.0.9 `-- c@1.0.10 In this case, npm dedupe will transform the tree to: a -- b -- d `-- c@1.0.10 Because of the hierarchical nature of node's module lookup, b and d will both get their dependency met by the single c package at the root level of the tree. 复制代码 // npm7 以后微调 // 在保持上述原则的基础上,升级了如下细微的规则: In some cases, you may have a dependency graph like this: a -- b <-- depends on c@1.0.x -- c@1.0.3 `-- d <-- depends on c@1.x `-- c@1.9.9 During the installation process, the c@1.0.3 dependency for b was placed in the root of the tree. Though d's dependency on c@1.x could have been satisfied by c@1.0.3, the newer c@1.9.0 dependency was used, because npm favors updates by default, even when doing so causes duplication. Running npm dedupe will cause npm to note the duplication and re-evaluate, deleting the nested c module, because the one in the root is sufficient. To prefer deduplication over novelty during the installation process, run npm install --prefer-dedupe or npm config set prefer-dedupe true. Arguments are ignored. Dedupe always acts on the entire tree. Note that this operation transforms the dependency tree, but will never result in new modules being installed. Using npm find-dupes will run the command in --dry-run mode. Note: npm dedupe will never update the semver values of direct dependencies in your project package.json, if you want to update values in package.json you can run: npm update --save instead.During the installation process, the c@1.0.3 dependency for b was placed in the root of the tree. Though d's dependency on c@1.x could have been satisfied by c@1.0.3, the newer c@1.9.0 dependency was used, because npm favors updates by default, even when doing so causes duplication. Running npm dedupe will cause npm to note the duplication and re-evaluate, deleting the nested c module, because the one in the root is sufficient. To prefer deduplication over novelty during the installation process, run npm install --prefer-dedupe or npm config set prefer-dedupe true. Arguments are ignored. Dedupe always acts on the entire tree. Note that this operation transforms the dependency tree, but will never result in new modules being installed. Using npm find-dupes will run the command in --dry-run mode. Note: npm dedupe will never update the semver values of direct dependencies in your project package.json, if you want to update values in package.json you can run: npm update --save instead. 复制代码
- 5、npm 安装依赖树的流程是什么?
- 执行工程自身 preinstall。当前 npm 工程如果定义了 preinstall 钩子此时会被执行。
- 确定首层依赖。模块首先需要做的是确定工程中的首层依赖,也就是 dependencies 和 devDependencies 属性中直接指定的模块(假设此时没有添加 npm install 参数)。工程本身是整棵依赖树的根节点,每个首层依赖模块都是根节点下面的一棵子树,npm 会开启多进程从每个首层依赖模块开始逐步寻找更深层级的节点。
- 获取模块。获取模块是一个递归的过程,分为以下几步:
- 获取模块信息。在下载一个模块之前,首先要确定其版本,这是因为 package.json 中往往是 semantic version(semver,语义化版本)。此时如果版本描述文件(npm-shrinkwrap.json 或 package-lock.json)中有该模块信息直接拿即可,如果没有则从仓库获取。如 package.json 中某个包的版本是 ^1.1.0,npm 就会去仓库中获取符合 1.x.x 形式的最新版本。
- 获取模块实体。上一步会获取到模块的压缩包地址(resolved 字段),npm 会用此地址检查本地缓存,缓存中有就直接拿,如果没有则从仓库下载。
- 查找该模块依赖,如果有依赖则回到第 1 步,如果没有则停止。
- 模块扁平化(dedupe)。上一步获取到的是一棵完整的依赖树,其中可能包含大量重复模块。比如 A 模块依赖于 loadsh,B 模块同样依赖于 lodash。在 npm3 以前会严格按照依赖树的结构进行安装,因此会造成模块冗余。yarn 和从 npm5 开始默认加入了一个 dedupe 的过程。它会遍历所有节点,逐个将模块放在根节点下面,也就是 node-modules 的第一层。当发现有重复模块时,则将其丢弃。这里需要对重复模块进行一个定义,它指的是模块名相同且 semver 兼容。每个 semver 都对应一段版本允许范围,如果两个模块的版本允许范围存在交集,那么就可以得到一个兼容版本,而不必版本号完全一致,这可以使更多冗余模块在 dedupe 过程中被去掉。
- 安装模块。这一步将会更新工程中的 node_modules,并执行模块中的生命周期函数(按照 preinstall、install、postinstall 的顺序)。
- 执行工程自身生命周期。当前 npm 工程如果定义了钩子此时会被执行(按照 install、postinstall、prepublish、prepare 的顺序)。
- 6、npm 的锁包机制发展历史?
- 6-0、重点:幽灵依赖、分身依赖、扁平化【flatten】、去重【dedupe】、lock 文件
- 6-1、初代的
npm
【npm v1-v2 阶段】- 问题:依赖嵌套太深,不能共享
- 初代的
npm
会导致重复安装依赖,比如 A 依赖 C,B 也依赖 C,这时会安装两次 C。(是安装两次,不是下载两次。会下载到本地缓存。) - 因为是树型结构,
node_modules
嵌套层级过深 (会导致文件路径过长的问题) - 模块实例不能共享。比如 React 有一些内部变量,在两个不同包引入的 React 不是同一个模块实例,因此无法共享内部变量,导致一些不可预知的 bug。
- 初代的
- 问题:依赖嵌套太深,不能共享
- 6-2、进阶阶段【npm v3 /yarn】
- 从
npm3
和yarn
开始,都来通过扁平化依赖的方式【flatten 机制】来解决上面的这个问题。 - 所有的依赖都被拍平到
node_modules
目录下,不再有很深层次的嵌套关系。这样在安装新的包时,根据node require
机制,会不停往上级的node_modules
当中去找,如果找到相同版本的包就不会重新安装,解决了大量包重复安装的问题,而且依赖层级也不会太深。 - 新的问题:
- 幽灵依赖:
package.json
里并没有写入的包竟然也可以在项目中使用了。 - 分身依赖: 比如 A 和 B 都依赖了 C,但是依赖
C
的版本不一样,一个是1.0.0
,一个是2.0.0
。这时取决于 A 和 B 在package.json
中的位置,使用的C
有可能是1.0.0
版本,也可能是2.0.0
版本。 - 平铺减少安装没有减省时间,因为算法的原因,时间居然还增加了
- 幽灵依赖:
- 从
- 6-3、现阶段【npm5 以后 /yarn】
- 该版本引入了一个
lock
文件,以解决node_modules
安装中的不确定因素。这使得无论你安装多少次,都能有一个一样结构的node_modules
。 - 然而,平铺式的算法的复杂性,幽灵依赖之类的问题还是没有解决。
- npm5 版本下 install 规则,npm 并不是一开始就是按照现有这种规则制定的:
- 5.0.x 版本:
- 不管 package.json 中依赖是否有更新,npm install 都会根据 package-lock.json 下载。针对这种安装策略,有人提出了这个 issue [6] ,然后就演变成了 5.1.0 版本后的规则。
- 5.1.0 版本后:
- 当 package.json 中的依赖项有新版本时,npm install 会无视 package-lock.json 去下载新版本的依赖项并且更新 package-lock.json。针对这种安装策略,又有人提出了一个 issue [7] 参考 npm 贡献者 iarna 的评论,得出 5.4.2 版本后的规则。
- 5.4.2 版本后:
- 如果只有一个 package.json 文件,运行 npm install 会根据它生成一个 package-lock.json 文件,这个文件相当于本次 install 的一个快照,它不仅记录了 package.json 指明的直接依赖的版本,也记录了间接依赖的版本。
- 如果 package.json 的 semver-range version 和 package-lock.json 中版本兼容 (package-lock.json 版本在 package.json 指定的版本范围内),即使此时 package.json 中有新的版本,执行 npm install 也还是会根据 package-lock.json 下载。
- 如果手动修改了 package.json 的 version ranges,且和 package-lock.json 中版本不兼容,那么执行 npm install 时 package-lock.json 将会更新到兼容 package.json 的版本。
- 该版本引入了一个
- 6-4、yarn v2 做的一些努力【PnP】
- 在
yarn
的 2.x 版本重点推出了Plug’n’Play(PnP)
零安装模式,放弃了node_modules
,更加保证依赖的可靠性,构建速度也得到更大的提升。 yarn 2.x
摆脱node_modules
,安装、模块速度加载快;所有 npm 模块都会存放在全局的缓存目录下,避免多重依赖;严格模式下子依赖不会提升,也避免了幽灵依赖。- 但是,自建
resolver
处理Node require
方法,脱离 Node 现存生态,兼容性不太好,所以依旧还是有些问题的
- 在
- 7、npm 锁版本常用的方法有哪些?
- 项目在以后重新构建,由于依赖树中有版本更新,造成意外事故是不可避免的,究其原因是整个依赖树版本没有锁死。解决方案分为如下四种:
- package.json 中固定版本。注意:仅能锁定当前依赖包版本,不能控制整棵依赖树版本。
- npm npm-shrinkwrap.json。
- npm package-lock.json。
- yarn yarn-lock.json。
- 思考:package.json 中固定版本 package-lock.json 值得思考【这样新需求需要装包时或者新的目录下重新初始化装包时波动性更小,可以选择性的锁定 package.json 的一些核心业务所需的包,比如:antd】
- 项目在以后重新构建,由于依赖树中有版本更新,造成意外事故是不可避免的,究其原因是整个依赖树版本没有锁死。解决方案分为如下四种:
- 8、当 npm 的 lock 文件冲突是怎么解决?【前端负责人必备思想】
- 当两个分支合并时,此时 lock 文件冲突了,最好的解决办法不是选择合并,而是放弃 lock 文件的合并,以合并后的 package.json 文件为基础,配合原来的 lock 文件重新 install,然后重新起服务检查是否正常【这种情况一般发生在新的需求装了新的包】
- 作为项目管控者,这种思路是很重要的,统筹项目时值得注意
- 一个项目不要轻而易举的随意的装包
- 当两个分支合并时,此时 lock 文件冲突了,最好的解决办法不是选择合并,而是放弃 lock 文件的合并,以合并后的 package.json 文件为基础,配合原来的 lock 文件重新 install,然后重新起服务检查是否正常【这种情况一般发生在新的需求装了新的包】
源码附件已经打包好上传到百度云了,大家自行下载即可~
代码语言:javascript复制链接: https://pan.baidu.com/s/14G-bpVthImHD4eosZUNSFA?pwd=yu27
提取码: yu27
百度云链接不稳定,随时可能会失效,大家抓紧保存哈。
如果百度云链接失效了的话,请留言告诉我,我看到后会及时更新~
开源地址
码云地址: http://github.crmeb.net/u/defu
Github 地址: http://github.crmeb.net/u/defu