1. 前言
大家好,我是若川。最近組織了源碼共讀活動,感興趣的可以加我微信 ruochuan12 參與,或者在公眾號:若川視野,回復"源碼"參與,每周大家一起學習200行左右的源碼,共同進步。已進行三個月了,很多小伙伴表示收獲頗豐。
想學源碼,極力推薦之前我寫的《學習源碼整體架構系列》 包含jQuery
、underscore
、lodash
、vuex
、sentry
、axios
、redux
、koa
、vue-devtools
、vuex4
、koa-compose
、vue-next-release
、vue-this
、create-vue
、玩具vite
等10余篇源碼文章。
本文倉庫 remote-git-tags-analysis,求個star^_^[1]
我們經常會在本地git倉庫切換tags,或者git倉庫切換tags。那么我們是否想過如果獲取tags呢。本文就是學習 remote-git-tags
這個22行代碼的源碼庫。源碼不多,但非常值得我們學習。
閱讀本文,你將學到:
1.?Node?加載采用什么模塊
2.?獲取?git?倉庫所有?tags?的原理
3.?學會調試看源碼
4.?學會面試高頻考點?promisify?的原理和實現
5.?等等
剛開始先不急著看上千行、上萬行的源碼。源碼長度越長越不容易堅持下來。看源碼講究循序漸進。比如先從自己會用上的百來行的開始看。
我之前在知乎上回答過類似問題。
一年內的前端看不懂前端框架源碼怎么辦?
簡而言之,看源碼
循序漸進
借助調試
理清主線
查閱資料
總結記錄
2. 使用
import?remoteGitTags?from?'remote-git-tags';console.log(await?remoteGitTags('https://github.com/lxchuan12/blog.git'));
//=>?Map?{'3.0.5'?=>?'6020cc35c027e4300d70ef43a3873c8f15d1eeb2',?…}
3. 源碼
Get tags from a remote Git repo
這個庫的作用是:從遠程倉庫獲取所有標簽。
原理:通過執行 git ls-remote --tags repoUrl
(倉庫路徑)獲取 tags
應用場景:可以看有哪些包依賴的這個包。npm 包描述信息[2]
其中一個比較熟悉的是npm-check-updates[3]
npm-check-updates 將您的 package.json 依賴項升級到最新版本,忽略指定的版本。
還有場景可能是 github
中獲取所有 tags
信息,切換 tags
或者選定 tags
發布版本等,比如微信小程序版本。
看源碼前先看 package.json
文件。
3.1 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
之前一直是 CommonJS
模塊機制。Node 13
添加了對標準 ES6
模塊的支持。
告訴 Node
它要加載的是什么模塊的最簡單的方式,就是將信息編碼到不同的擴展名中。如果是 .mjs
結尾的文件,則 Node
始終會將它作為 ES6
模塊來加載。如果是 .cjs
結尾的文件,則 Node
始終會將它作為 CommonJS
模塊來加載。
對于以 .js
結尾的文件,默認是 CommonJS
模塊。如果同級目錄及所有目錄有 package.json
文件,且 type
屬性為module
則使用 ES6
模塊。type
值為 commonjs
或者為空或者沒有 package.json
文件,都是默認 commonjs
模塊加載。
關于 Node
模塊加載方式,在《JavaScript權威指南第7版》16.1.4 Node 模塊 小節,有更加詳細的講述。此書第16章都是講述Node
,感興趣的讀者可以進行查閱。
3.2 調試源碼
#?推薦克隆我的項目,保證與文章同步,同時測試文件齊全
git?clone?https://github.com/lxchuan12/remote-git-tags-analysis.git
#?npm?i?-g?yarn
cd?remote-git-tags?&&?yarn
#?VSCode?直接打開當前項目
#?code?.#?或者克隆官方項目
git?clone?https://github.com/sindresorhus/remote-git-tags.git
#?npm?i?-g?yarn
cd?remote-git-tags?&&?yarn
#?VSCode?直接打開當前項目
#?code?.
用最新的VSCode
打開項目,找到 package.json
的 scripts
屬性中的 test
命令。鼠標停留在test
命令上,會出現 運行命令
和 調試命令
的選項,選擇 調試命令
即可。
調試如圖所示:

VSCode
調試 Node.js
說明如下圖所示:

更多調試詳細信息,可以查看這篇文章新手向:前端程序員必學基本技能——調試JS代碼。
跟著調試,我們來看主文件。
3.3 主文件僅有22行源碼
//?index.js
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');//?Strip?off?the?indicator?of?dereferenced?tags?so?we?can?override?the//?previous?entry?which?points?at?the?tag?hash?and?not?the?commit?hash//?`refs/tags/v9.6.0^{}`?→?`v9.6.0`const?tagName?=?tagReference.replace(/^refs\/tags\//,?'').replace(/\^{}$/,?'');tags.set(tagName,?hash);}return?tags;
}
源碼其實一眼看下來就很容易懂。
3.4 git ls-remote --tags
支持遠程倉庫鏈接。
git ls-remote 文檔[4]
如下圖所示:

獲取所有tags
git ls-remote --tags https://github.com/vuejs/vue-next.git
把所有 tags
和對應的 hash
值 存在 Map
對象中。
3.5 node:util
Node 文檔[5]
Core modules can also be identified using the node: prefix, in which case it bypasses the require cache. For instance, require('node:http') will always return the built in HTTP module, even if there is require.cache entry by that name.
也就是說引用 node
原生庫可以加 node:
前綴,比如 import util from 'node:util'
看到這,其實原理就明白了。畢竟只有22行代碼。接著講述 promisify
。
4. promisify
源碼中有一段:
const?execFile?=?promisify(childProcess.execFile);
promisify
可能有的讀者不是很了解。
接下來重點講述下這個函數的實現。
promisify
函數是把 callback
形式轉成 promise
形式。
我們知道 Node.js
天生異步
,錯誤回調的形式書寫代碼。回調函數的第一個參數是錯誤信息。也就是錯誤優先。
我們換個簡單的場景來看。
4.1 簡單實現
假設我們有個用JS加載圖片的需求。我們從 這個網站[6] 找來圖片。
examples
const?imageSrc?=?'https://www.themealdb.com/images/ingredients/Lime.png';function?loadImage(src,?callback)?{const?image?=?document.createElement('img');image.src?=?src;image.alt?=?'公眾號若川視野專用圖?';image.style?=?'width:?200px;height:?200px';image.onload?=?()?=>?callback(null,?image);image.onerror?=?()?=>?callback(new?Error('加載失敗'));document.body.append(image);
}
我們很容易寫出上面的代碼,也很容易寫出回調函數的代碼。需求搞定。
loadImage(imageSrc,?function(err,?content){if(err){console.log(err);return;}console.log(content);
});
但是回調函數有回調地獄等問題,我們接著用 promise
來優化下。
4.2 promise 初步優化
我們也很容易寫出如下代碼實現。
const?loadImagePromise?=?function(src){return?new?Promise(function(resolve,?reject){loadImage(src,?function?(err,?image)?{if(err){reject(err);return;}resolve(image);});});
};
loadImagePromise(imageSrc).then(res?=>?{console.log(res);
})
.catch(err?=>?{console.log(err);
});
但這個不通用。我們需要封裝一個比較通用的 promisify
函數。
4.3 通用 promisify 函數
function?promisify(original){function?fn(...args){return?new?Promise((resolve,?reject)?=>?{args.push((err,?...values)?=>?{if(err){return?reject(err);}resolve(values);});//?original.apply(this,?args);Reflect.apply(original,?this,?args);});}return?fn;
}const?loadImagePromise?=?promisify(loadImage);
async?function?load(){try{const?res?=?await?loadImagePromise(imageSrc);console.log(res);}catch(err){console.log(err);}
}
load();
需求搞定。這時就比較通用了。
這些例子在我的倉庫存放在 examples
文件夾中。可以克隆下來,npx http-server .
跑服務,運行試試。

跑失敗的結果可以把 imageSrc
改成不存在的圖片即可。
promisify
可以說是面試高頻考點。很多面試官喜歡考此題。
接著我們來看 Node.js
源碼中 promisify
的實現。
4.4 Node utils promisify 源碼
github1s node utils 源碼[7]
源碼就暫時不做過多解釋,可以查閱文檔。結合前面的例子,其實也容易理解。
utils promisify 文檔[8]
const?kCustomPromisifiedSymbol?=?SymbolFor('nodejs.util.promisify.custom');
const?kCustomPromisifyArgsSymbol?=?Symbol('customPromisifyArgs');let?validateFunction;function?promisify(original)?{//?Lazy-load?to?avoid?a?circular?dependency.if?(validateFunction?===?undefined)({?validateFunction?}?=?require('internal/validators'));validateFunction(original,?'original');if?(original[kCustomPromisifiedSymbol])?{const?fn?=?original[kCustomPromisifiedSymbol];validateFunction(fn,?'util.promisify.custom');return?ObjectDefineProperty(fn,?kCustomPromisifiedSymbol,?{value:?fn,?enumerable:?false,?writable:?false,?configurable:?true});}//?Names?to?create?an?object?from?in?case?the?callback?receives?multiple//?arguments,?e.g.?['bytesRead',?'buffer']?for?fs.read.const?argumentNames?=?original[kCustomPromisifyArgsSymbol];function?fn(...args)?{return?new?Promise((resolve,?reject)?=>?{ArrayPrototypePush(args,?(err,?...values)?=>?{if?(err)?{return?reject(err);}if?(argumentNames?!==?undefined?&&?values.length?>?1)?{const?obj?=?{};for?(let?i?=?0;?i?<?argumentNames.length;?i++)obj[argumentNames[i]]?=?values[i];resolve(obj);}?else?{resolve(values[0]);}});ReflectApply(original,?this,?args);});}ObjectSetPrototypeOf(fn,?ObjectGetPrototypeOf(original));ObjectDefineProperty(fn,?kCustomPromisifiedSymbol,?{value:?fn,?enumerable:?false,?writable:?false,?configurable:?true});return?ObjectDefineProperties(fn,ObjectGetOwnPropertyDescriptors(original));
}promisify.custom?=?kCustomPromisifiedSymbol;
5. ES6+ 等知識
文中涉及到了Map
、for of
、正則、解構賦值。
還有涉及封裝的 ReflectApply
、ObjectSetPrototypeOf
、ObjectDefineProperty
、ObjectGetOwnPropertyDescriptors
等函數都是基礎知識。
這些知識可以查看esma規范[9],或者阮一峰老師的《ES6 入門教程》[10] 等書籍。
6. 總結
一句話簡述 remote-git-tags
原理:使用Node.js
的子進程 child_process
模塊的execFile
方法執行 git ls-remote --tags repoUrl
獲取所有 tags
和 tags
對應 hash
值 存放在 Map
對象中。
文中講述了我們可以循序漸進,借助調試、理清主線、查閱資料、總結記錄的流程看源碼。
通過 remote-git-tags
這個22行代碼的倉庫,學會了 Node 加載采用什么模塊,知道了原來 git ls-remote --tags
支持遠程倉庫,學到了面試高頻考點 promisify
函數原理和源碼實現,鞏固了一些 ES6+
等基礎知識。
建議讀者克隆我的倉庫[11]動手實踐調試源碼學習。
后續也可以看看 es6-promisify[12] 這個庫的實現。
最后可以持續關注我@若川。歡迎加我微信 ruochuan12 交流,參與 源碼共讀 活動,大家一起學習源碼,共同進步。
參考資料
[1]
本文倉庫 remote-git-tags-analysis,求個star^_^: https://github.com/lxchuan12/remote-git-tags-analysis.git
[2]npm 包描述信息: https://npm.im/remote-git-tags
[3]npm-check-updates: https://www.npmjs.com/package/npm-check-updates
[4]git ls-remote 文檔: https://git-scm.com/docs/git-ls-remote
[5]Node 文檔: https://nodejs.org/dist/latest-v16.x/docs/api/modules.html
[6]這個網站: https://www.themealdb.com/api.php
[7]github1s node utils 源碼: https://github1s.com/nodejs/node/blob/master/lib/internal/util.js#L343
[8]utils promisify 文檔: http://nodejs.cn/api/util/util_promisify_original.html
[9]esma規范: https://yanhaijing.com/es5/
[10]《ES6 入門教程》: https://es6.ruanyifeng.com/
[11]我的倉庫: https://github.com/lxchuan12/remote-git-tags-analysis.git
[12]es6-promisify: https://github.com/mikehall314/es6-promisify
最近組建了一個江西人的前端交流群,如果你是江西人可以加我微信?ruochuan12?私信 江西 拉你進群。
推薦閱讀
1個月,200+人,一起讀了4周源碼
我歷時3年才寫了10余篇源碼文章,但收獲了100w+閱讀
老姚淺談:怎么學JavaScript?
我在阿里招前端,該怎么幫你(可進面試群)
·················?若川簡介?·················
你好,我是若川,畢業于江西高校。現在是一名前端開發“工程師”。寫有《學習源碼整體架構系列》10余篇,在知乎、掘金收獲超百萬閱讀。
從2014年起,每年都會寫一篇年度總結,已經寫了7篇,點擊查看年度總結。
同時,最近組織了源碼共讀活動,幫助1000+前端人學會看源碼。公眾號愿景:幫助5年內前端人走向前列。
識別上方二維碼加我微信、拉你進源碼共讀群
今日話題
略。歡迎分享、收藏、點贊、在看我的公眾號文章~