【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 命令的源碼,一塊兒學習,一塊兒成長。

ヽ✿゜▽゜)ノ

相關文章
相關標籤/搜索