npm源码分析(五)之npm repo

1,801 阅读3分钟

前言

隔了挺久没更新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这样的命令,在源码阅读的时候快人一步,从此养成经常阅读源码的习惯吧!

往期npm源码分析系列

  1. npm启动
  2. npm配置设置与读取
  3. npm install分析
  4. npm help分析