0%

从源码角度理解npm

我在整理npm相关知识时,发现有些问题比较困惑,网上也没有从源码层面解释npm的文章,所以我去看了源码来解决我的困惑。为了加深理解,我把源码里的重点内容整理出来,希望大家在读完后也能够对npm有更深的理解。

什么是npm

npm全称_node package manager_,维基百科关于Node.js的介绍中指明npm是Node.js附带的包管理器。下载安装node时会附加安装了npm。npm是一个命令行工具,用于从NPM Registry中下载、安装Node.js程序,同时解决依赖问题。npm提高了开发的速度,因为它能够负责第三方Node.js程序的安装与管理。

👉 npm官网文档

👉 npm git仓库

安装

npm是Node.js附带的包管理器,也就是说安装node完后,自动安装npm。

  • node可在官网下载指定版本安装 node官网

  • 如果是mac电脑,也可以使用homebrew安装。打开终端,在命令行中输入

    brew install node

下载node包打开后也能看到里面默认带了npm和npx

img

使用

在开发代码阶段,我们时常要使用到 npm:

  • 在初始化项目时,我们会使用npm init -y 命令来生成package.json文件
  • 然后再npm install <packageName>安装模块
  • 开发代码完成后需要在package.json中配置scripts字段start,再通过npm run start执行代码

那么实际上执行npm命令的调用关系是什么?实际是执行哪个脚本呢?

我们可以在终端任意路径下输入which npm查看npm执行文件所在的位置,结果如下:

1
2
> /usr/local/bin/npm
复制代码

可知npm命令实际上是执行的是/usr/local/bin/npm文件。继续输入ll /usr/local/bin/npm

1
2
> lrwxr-xr-x 1 kin admin 46B 4 7 2020 /usr/local/bin/npm -> /usr/local/lib/node_modules/npm/bin/npm-cli.js
复制代码

也就是说_/usr/local/bin/npm_是个软链,实际执行的是_/usr/local/lib/node_modules/npm/bin/npm-cli.js_文件

img

如何调试npm源码

首先去 npm的git仓库把代码拉取到本地,开始调试源码。

这里介绍一个比较取巧的方式调试,先确认一下你使用的IDE是VSCode。

  1. 在项目根路径的package.json中给scripts字段添加一行命令👇
1
2
"debugger": "node ./bin/npm-cli.js install moment",
复制代码

解释一下,因为npm本质上就是node ./bin/npm-cli.js,故使用node ./bin/npm-cli.js 替代npm即可。也就是说配置的node ./bin/npm-cli.js install moment等价于 npm install moment

  1. 点击scripts字段上面的Debug小图标

img

  1. 弹窗中选择debugger,进入调试模式

img

源码解析

这里将从这几个方面分析npm源码:

  1. 整体介绍
  2. npm常用命令的实现,包括
  • npm init
  • npm install <packageName>
  • npm run scripts

1. 整体介绍

根据上面的分析可知,npm实际执行的文件是/usr/local/lib/node_modules/npm/bin/npm-cli.js,查看npm-cli.js文件的代码可知:

1
2
3
#!/usr/bin/env node 
require('../lib/cli.js')(process)
复制代码

内部实际调用的是/npm/lib/cli.js,核心逻辑在56行,从npm.commands上获取指令所对应的文件,如果存在就执行此文件。

1
2
3
4
5
6
7
8
9
const impl = npm.commands[cmd]
if (impl)
impl(npm.argv, errorHandler)
else {
npm.config.set('usage', false)
npm.argv.unshift(cmd)
npm.commands.help(npm.argv, errorHandler)
}
复制代码

也就是说npm run xxx命令最后会执行相对应的文件

img

2. npm常用命令的实现

重点介绍这几个常用命令npm initnpm install <packageName>npm run <scripts>

npm init。

如上分析可知,npm init 会对应执行lib/init.js,核心代码从71行开始,调用initJson()。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
await new Promise((res, rej) => {
initJson(dir, initFile, npm.config, (er, data) => {
npm.log.resume()
npm.log.enableProgress()
npm.log.silly('package data', data)
if (er && er.message === 'canceled') {
npm.log.warn('init', 'canceled')
return res()
}
if (er)
rej(er)
else {
npm.log.info('init', 'written successfully')
res(data)
}
})
})
复制代码

initJson是本质上调用的是init-package-json 中的init方法。主要做的就是写入package.json文件

img

总结

读完这篇文章,你应该对npm的实现机制有了基本的了解:

  1. npm是node.js附带的包管理器,在安装node时会附带安装上

  2. npm本质是一段node脚本,我们执行npm命令其实就是执行node /usr/local/lib/node_modules/npm/bin/npm-cli.js

  3. 最后分享一点阅读源码的心得:

  • 读源码时要抓大放小,先把整体脉络整理出来,在针对各个细节进行分析;
  • 抓住核心逻辑,读进去还能读出来,就是能表达出源码做了啥;
  • 进阶段位就是学习源码里的优秀思想,学以致用吧

希望这篇文章能够给你有所帮助,对文章中的内容有疑问的地方欢迎一起探讨。