前言
隔了挺久没更新npm源码分析系列,也不知道是比较忙还是比较忙,总之今天续上。
日常开发调试中,经常需要直接通过阅读源码来更快的找到问题线索。
我个人的习惯是git-clone源码库到本地,在VsCode中快速切换文件来查看源码。当然在github上配合Octotree这个浏览器插件也是不错的,但始终没有本地切换文件来的快。可能还有很多其他方式,大家可以在评论区发表下,说不定可以以外收货一些奇技淫巧哈。话不多说,直接进入本文的重点。
npm提供了能快速访问github源码页面的命令,就是npm repo xxx。
实现逻辑
还不熟悉npm如何启动的同学,可以传送门去看一下启动原理及npm各个命令的导出逻辑。
熟悉的同学,我们直接进入lib目录下的repo.js
function repo (args, cb) { const n = args.length ? args[0] : '.' fetchPackageMetadata(n, '.', {fullMetadata: true}, function (er, d) { if (er) return cb(er) getUrlAndOpen(d, cb) })}
repo命令逻辑非常简单,获取npm repo xxx里对应的xxx包名,然后获取对应package的源信息,根据对应包的package.json里的repository字段解析出对应的Git仓库地址信息,然后自动打开浏览器访问对应github源码页面。
大家开发自定义npm包的时候,尽量把Git仓库地址信息维护进去
获取Git仓库地址信息的逻辑全在getUrlAndOpen里,看函数名也可以很清楚的知道它做了什么。
function getUrlAndOpen (d, cb) {
const r = d.repository if (!r) return cb(new Error('no repository')) // XXX remove this when npm@v1.3.10 from node 0.10 is deprecated // from https://github.com/npm/npm-www/issues/418 const info = hostedGitInfo.fromUrl(r.url) const url = info ? info.browse() : unknownHostedUrl(r.url) if (!url) return cb(new Error('no repository: could not get url')) openUrl(url, 'repository available at the following URL', cb)}
这里要提一下[hosted-git-info](https://github.com/npm/hosted-git-info)这个包。支持对主流源码托管平台host解析及各类页面地址生成,通过它可以非常方便的拿到各种形式Git仓库地址链接。
到目前为止,所有的逻辑都非常的简单连贯,可是少了最重要的一步,就是fetchPackageMetadata。
即如何拿到对应npm包的源信息?
function fetchPackageMetadata (spec, where, opts, done) { ... pacote.manifest(dep, npmConfig({ annotate: true, fullMetadata: opts.fullMetadata, log: tracker || npmlog, memoize: CACHE, where: where })).then( (pkg) => logAndFinish(null, deprCheck(pkg)), (err) => { ... } )}
这里我截取了核心逻辑,用到了npm架构搭建核心依赖之一的pacote。
核心能力包括读取manifest、提取源码和拉取tarball等等。开发自定义npm包时,可以通过它来非常方便的处理依赖包。
简单贴一下它的用法,它不仅有api形式,而且可以直接作为cli来使用,更加方便。
const pacote = require('pacote')
// get a package manifest
pacote.manifest('foo@1.x').then(manifest => console.log('got it', manifest))
// extract a package into a folder
pacote.extract('github:npm/cli', 'some/path', options)
.then(({from, resolved, integrity}) => {
console.log('extracted!', from, resolved, integrity)
})
pacote.tarball('https://server.com/package.tgz').then(data => {
console.log('got ' + data.length + ' bytes of tarball data')
})
回归正题,npm repo里通过pacote.manifest提取对应包的源信息,类似于package.json信息,然后传递package.repository字段到getUrlAndOpen。
不同思路
看到这里,大家有没有发现我们核心的诉求是获取包的源信息里的repository,拿到对应的Git仓库地址就够了。直接借助pacote就好了。
下面代码逻辑是我从自己写的一个工具里摘取出来的,主要功能是依赖pacote实现git-clone源码库到本地。
async function resolveSourceCodeGitClone(argv) { const [, , package] = argv._; if (!package) { log('Npm package is undefined\n'); process.exit(0); } else { const { name, repository = {} } = await pacote.manifest(package, { fullMetadata: true, where: '.', }); console.log('package repository: ', repository); const { url } = repository; if (!url) { log('Npm package repository not found'); process.exit(0); } const info = hostedGitInfo.fromUrl(url); if (!info) { log('Npm package repository not found'); process.exit(0); } const repo = info.https({ noGitPlus: true }); console.log('repo: ', repo); console.log('source: ', source); await runScript('git', ['clone', repo, name], { cwd: source }); }}
总结
通过源码阅读来解决日常开发中的问题,往往效率是比较高的,而且也能更好的熟悉这些依赖包,进一步拓宽自己的编码广度。必须要掌握npm repo这样的命令,在源码阅读的时候快人一步,从此养成经常阅读源码的习惯吧!