【若川视野 x 源码共读】第14期 | remote-git-tags

59 阅读2分钟

本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。

我们在平时 coding 的时候,会在本地 git 仓库切换 tags,或者 git 仓库切换 tags。那么我们如何获取 tags 呢?

本次要学习的源码 remote-git-tags 会告诉我们。

那么此次学习,我们将收益到:

  1. Node 加载采用什么模块
  2. 获取 git 仓库所有 tags 的原理
  3. 学会调试源码
  4. 学会面试高频考点 promisify 的原理和实现

使用该库获取 tags

import remoteGitTags from 'remote-git-tags'

console.log(await remoteGitTags('https://github.com/foxhsx/static-blog.git'))

// =>

源码

首先,我们从名称就可以得知,这个库的作用就是:从远程仓库获取所有的标签。

其次,我们一般使用 git 命令来直接获取仓库的所有 tags:

$ git ls-remote --tags [repo_url]

事实上,此仓库的实现原理也是需要通过上述命令来实现

应用场景:

  • 看有哪些包依赖的这个包(没想到场景)

npm-check-updates 库中,它会将我们的 package.json 中的依赖项升级到最新版本,忽略指定的版本

  • 获取到所有 tags 信息之后,进行 tag 切换或者选定 tag 进行版本发布等,比如微信小程序版本

package.json

我们说,看源码先看 package.json

{
	// 指定 Node 以什么模块加载,缺省时默认是 commonjs
  "type": "module",
  "exports": "./index.js",
  // 指定 nodejs 版本
  "engines": {
    "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
  },
  "scripts": {
    "test": "xo && ava"
  }
}

在 Node 13 版本之前,一直都是采用的 CommonJS 模块机制,而在13之后,则添加来对标准 ES6 模块的支持。

要告知 Node 要加载的是什么模块,最简单的方法就是将信息编码到不同的扩展名中。

  • .mjs 结尾的文件,是采用 ES6 的模块来加载
  • cjs 结尾的文件,是采用 CommonJS 的模块来加载
  • js 结尾的文件,默认是使用 CommonJS。
    • 如果在 package.json 中,有 type: module 的属性,则会使用 ES6 模块
    • 如果 type: commonjs 或者没有 package.json,都是会默认使用 commonjs 模块加载

调试源码

我们需要克隆源码到本地并通过编辑器打开,找到 package.json 中的 test 命令。

当鼠标停留在 test 命令上时,会出现如图所示的 运行脚本调试脚本 的选项,我们选择调试。

关于 VSCode 如何调试 Node.j,如下图:

调试如下:

调试的时候需要注意,在 Node 环境下进行调试:

  • 文件中的花括号不能有空格
  • 需要使用单引号
  • 需要使用 tab 进行缩进-

主文件的代码如下:

import {promisify} from 'node:util';
import childProcess from 'node:child_process';

const execFile = promisify(childProcess.execFile);

export default async function remoteGitTags(repoUrl) {
	const {stdout} = await execFile('git', ['ls-remote', '--tags', repoUrl]);
	const tags = new Map();

	for (const line of stdout.trim().split('\n')) {
		const [hash, tagReference] = line.split('\t');

		const tagName = tagReference
			.replace(/^refs/tags//, '')
			.replace(/^{}$/, '');

		tags.set(tagName, hash);
	}

	return tags;
}

可以看到在第7行,就使用了我们开始说的查看仓库所有 tags 命令的对应字段,Node 会帮我们去执行该命令,并将得到的信息以 Map 的数据形式返回。

我们在源码中,要使用 Node 原生库中的一些工具,所以这里的写法也是一目了然:node:util

promisify

在 Node 中,promisify 函数将 callback 的形式转为了 promise 的形式。

Nodejs 天生异步,回调函数的第一个参数是错误信息,也就是错误优先。如果不采用 promise 的写法,很容易造成回调地狱的场景出现。所以更优的实现是封装一个 promisify 的通用函数,来满足大多数场景。

function promisify(original) {
	function fn(...args) {
  	return new Promise((resolve, reject) => {
    	args.push((err, ...values) => {
      	if (err) {
        	return reject(err)
        }
        resolve(values)
      })
      Reflect.apply(original, this, args)
    })
  }
  return fn
}

总结

remote-git-tags 原理:

使用 Node.js 的子进程 child_process 模块的 execFile 方法执行 git ls-remote --tags [repo_url],获取到对应仓库所有的 tags 和对应 hash 值存放在 Map 对象中。

本次的源码学习,让我更加清楚如何有条理的、更高效的进行源码阅读,正如文章中所说:

  • 看源码前先看 package.json 文件
  • 循序渐进
  • 借助调试
  • 理清主线
  • 查阅资料
  • 总结记录

package.json 中有透出大量的信息给我们,我们可以根据一些字段知道要运行该库,应该需要什么、依赖什么。比如文章中列举的几个重要字段:

  • type:指定 Node 以什么模式加载
  • engines:指定环境及其版本
  • scripts:这个属性大家最熟悉不过了,里面会存放一些执行脚本命令

而在 VSCode 中进行调试也是异常的简单:

  • 可提前在文件中打好断点(左侧代码行数点一下即可)
  • package.json 中的 scripts 属性上面有一个灰色的「调试」字段,点击即可进行调试