【npm源码】config get命令大扒皮,一件衣服都不留

npm config get命令想必你们都很熟悉,经过它,咱们能够获得npm的配置项,你们都知道它的用法,可是它的源码是怎么实现的呢?知其然更要其因此然,今天我就带你们扒一扒config get的源码。node

.npmrc文件

要想了解config get命令确定绕不开.npmrc文件。.npmrc是npm的配置文件,具体能够配置什么不是咱们本篇文章的重点,若是你们有兴趣能够读下这篇文章npm-configgit

.npmrc文件有四种github

  1. 每一个项目的配置文件,一般在项目的根目录
  2. 用户配置文件
  3. 全局配置文件
  4. npm 内建配置文件,在npm安装的根目录

若是想查看每一个用户配置文件和全局配置文件的具体的路径是什么,能够经过如下命令找到。npm

获取用户配置文件的路径json

npm config get userconfig
复制代码

获取全局配置文件的路径数组

npm config get globalconfig
复制代码

这个四个配置文件的优先级是:bash

每一个项目的配置文件 > 用户配置文件 > 全局配置文件 > 内置配置文件dom

至于为何优先级是这样,这里先卖个关子。编辑器

找到源码

既然咱们想要看源码就要把源码找到,有两种方式可以获得源码:函数

  1. github上的 npm/cli

  2. 本地的已安装的npm

你们本地确定都装了npm的,使用本身本地的源码也能够,只不过本地的版本可能比较旧,github上是最新的版本,若是找不到源码在哪能够,能够在命令行运行

npm --help --verbose
复制代码

终端就会打印出node和npm的位置以及的版本信息。

第一行是node的位置,第二行是npm的位置,第3、四行是命令参数,第五行是npm版本,我使用的npm版本是6.9.2的,第六行是node版本。

咱们找到npm文件夹,若是是从github克隆下来的就是一个cli文件夹,在编辑器中打开

我建议使用本地的npm来读源码,这样咱们能够在代码中加一些调试信息,方便理解。

开始读源码

在源码中咱们找到 /lib/config.js ,

找到config构造函数

/npm/lib/config.js
function config (args, cb) {
  var action = args.shift()
  switch (action) {
    case 'set':
      return set(args[0], args[1], cb)
    case 'get':
      return get(args[0], cb)
    case 'delete':
    case 'rm':
    case 'del':
      return del(args[0], cb)
    case 'list':
    case 'ls':
      return npm.config.get('json') ? listJson(cb) : list(cb)
    case 'edit':
      return edit(cb)
    default:
      return unknown(action, cb)
  }
}
复制代码

config命令接受两个参数,第一个参数args是咱们在命令行输入的参数,第二个参数是get执行完以后的回调函数。

在命令行运行

npm config get heading
复制代码

继续向下看 action 变量保存了你想要执行的动做,里面有 set get delete等,咱们主要看下get,返回的是一个get方法,咱们找到get方法。

/npm/lib/config.js
function get (key, cb) {
  if (!key) return list(cb)
  if (!publicVar(key)) {
    return cb(new Error('---sekretz---'))
  }
  var val = npm.config.get(key)
  if (key.match(/umask/)) val = umask.toString(val)
  output(val)
  cb()
}
复制代码

能够看到在get方法里象是对传进来key进行校验,校验完成以后实际调用的是 npm.config.get(key)。

打印一下npm.config对象,看看他是何方神圣。

Conf {
    domain: null,
    _events: {
        error: { [Function: g] listener: [Object]
        }
    },
    _eventsCount: 1,
    _maxListeners: undefined,
    list: [{
        argv: [Object],
        _exit: true
    },
    {},
    {
        '//registry.npmjs.org/:_authToken': '7bea3c0d-a131-4620-9ad2-0d7346addd15',
        heading: '1'
    },
    {
        '//registry.npmjs.org/:_authToken': '7bea3c0d-a131-4620-9ad2-0d7346addd15',
        color: true,
        heading: '2'
    },
    {
        heading: '3'
    },
    {
        prefix: 'C:\\Users\\daimingyu\\AppData\\Roaming\\npm',
        heading: '4'
    }],
    root: [Getter / Setter],
    _awaiting: 0,
    _saving: 0,
    sources: {
        cli: {
            data: {
                argv: [Object],
                _exit: true,
                'user-agent': 'npm/6.9.2 node/v6.14.1 win32 x64',
                'metrics-registry': 'https://registry.npmjs.org/',
                scope: ''
            }
        },
        env: {
            data: {},
            source: {},
            prefix: ''
        },
        project: {
            data: {}
        },
        user: {
            path: 'C:\\Users\\daimingyu\\.npmrc',
            type: 'ini',
            data: {
                '//registry.npmjs.org/:_authToken': '7bea3c0d-a131-4620-9ad2-0d7346addd15',
                color: true
            }
        },
        global: {
            path: 'C:\\Users\\daimingyu\\AppData\\Roaming\\npm\\etc\\npmrc',
            type: 'ini',
            data: {
                heading: '3'
            }
        },
        builtin: {
            data: {
                prefix: 'C:\\Users\\daimingyu\\AppData\\Roaming\\npm',
                heading: '4'
            }
        }
    }
    usingBuiltin: true,
    prefix: [Getter / Setter],
    globalPrefix: [Getter / Setter],
    localPrefix: [Getter / Setter]
}
复制代码

Config对象有两个很是重要的字段:list和sources,这两个字段存放了六个对象,这六个对象分别描述了:

  • 命令行
  • 环境变量
  • 项目.npmrc文件
  • 用户.npmrc文件
  • 全局.npmrc文件
  • npm内置的.npmrc文件

list存放了六个对象的内容,source字段存放了六个对象信息的地址和内容。

可是仔细看config对象发现并无get和set方法。

其实get,set方法就在config对象的原型链上。

咱们沿着原型链继续向上找

//npm.config.__proto__
Conf {
  loadPrefix: [Function: loadPrefix],
  loadCAFile: [Function: loadCAFile],
  loadUid: [Function: loadUid],
  setUser: [Function: setUser],
  getCredentialsByURI: [Function: getCredentialsByURI],
  setCredentialsByURI: [Function: setCredentialsByURI],
  clearCredentialsByURI: [Function: clearCredentialsByURI],
  loadExtras: [Function],
  save: [Function],
  addFile: [Function],
  parse: [Function],
  add: [Function],
  addEnv: [Function] 
}
复制代码

能够看到仍是没有get和set方法,继续向上找,不撞南墙不回头。

继续沿着原型链继续向上找

//npm.config.__proto__.__proto__
ConfigChain {
  domain: undefined,
  _events: undefined,
  _maxListeners: undefined,
  setMaxListeners: [Function: setMaxListeners],
  getMaxListeners: [Function: getMaxListeners],
  emit: [Function: emit],
  addListener: [Function: addListener],
  on: [Function: addListener],
  prependListener: [Function: prependListener],
  once: [Function: once],
  prependOnceListener: [Function: prependOnceListener],
  removeListener: [Function: removeListener],
  removeAllListeners: [Function: removeAllListeners],
  listeners: [Function: listeners],
  listenerCount: [Function: listenerCount],
  eventNames: [Function: eventNames],
  del: [Function],
  set: [Function],
  get: [Function],
  save: [Function],
  addFile: [Function],
  addEnv: [Function],
  addUrl: [Function],
  addString: [Function],
  add: [Function],
  parse: [Function],
  _await: [Function],
  _resolve: [Function]
}
复制代码

终于找到get方法了,他是ConfigChain上的一个方法。

咱们进到 get 方法里,看下它的具体实现。

/npm/node_modules/config-chain/index.js
ConfigChain.prototype.get = function (key, where) {
  if (where) {
    where = this.sources[where]
    if (where) where = where.data
    if (where && Object.hasOwnProperty.call(where, key)) return where[key]
    return undefined
  }
  return this.list[0][key]
}
复制代码

get函数接受两个参数: key和where。

打印一下这两个参数看下它们是什么:

有同窗可能会有疑问,咱们明明只执行了一次get命令,为何会打印这么多呢,其实读过了源码你就知道,不光咱们本身会用config get去取配置项,npm本身也会使用config get拿配置项。

图片中红色箭头的地方就是咱们在cmd中运行命令的时候打印的。key参数就是咱们想要取得配置项的key,where参数都是undefined,可见该参数不是必须的。

若是传了where值(where值取值为user或global),就去config对象的source中拿到user或global对象的data,若是key是本身的属性就返回这个key对应的value,若是不是就返回undefiend。

若是没有传where值,该方法直接返回 this.list[0][key]。

可是咱们能够看到config.list[0]里面没有 heading 属性,那它是怎么找到这个值的呢?

其实list数组里面的内容构成了一条原型链:

this.list[n].proto = this.list[n+1]

查找值的过程就是沿着原型链向上找的过程。

this.list[0] 若是没有 key 则从原型链向上找 ,有则返回

this.list[1] 若是没有 key 则从原型链向上找 ,有则返回

this.list[2] 若是没有 key 在从原型链向上找 ,有则返回

this.list[3] 若是没有 key 在从原型链向上找 ,有则返回

this.list[4] 若是没有 key 在从原型链向上找 ,有则返回

this.list[5] 若是没有 key 则找到default默认对象

总结

  1. 先看命令行有没有该参数,有就返回,没有进入下一步。

  2. 再看环境变量有没有该参数,有就返回,没有进入下一步。

  3. 检查用户配置文件,若是没有.npmrc文件或者有.npmrc文件可是没有设置xxx字段,则进行下一步,有.npmrc文件而且设置xxx字段则直接返回。

  4. 检查当前项目根目录,若是没有.npmrc文件或者有.npmrc文件可是没有设置xxx字段,则进行下一步,有.npmrc文件而且设置xxx字段则直接返回。

  5. 检查全局配置文件,若是没有.npmrc文件或者有.npmrc文件可是没有设置xxx字段,则进行下一步,有.npmrc文件而且设置xxx字段则直接返回。

  6. 检查npm内置文件,若是没有.npmrc文件或者有.npmrc文件可是没有设置xxx字段,则返回默认值,有.npmrc文件而且设置xxx字段则直接返回。

下期预告

下篇文章我打算分析一下 install 命令的源码,一块儿学习,一块儿成长。

ヽ✿゜▽゜)ノ

相关文章
相关标签/搜索