Vue-DevTools源碼閱讀--打開組件文件

很感謝若川大佬組織的源碼閱讀小組活動

天天下班後逼本身學習學習html

如下爲若川原文:juejin.cn/post/695934…vue

什麼是Vue-DevTools?

做爲一個Vue開發者(不是),天然少不了Chrome中的Vue調試插件。node

Vue-DevTools是一個能夠在Chrome中進行Vue項目調試的工具,能夠幫助開發者在使用Vue開發時,更清楚的瞭解目前頁面中的組件、數據狀況。git

目前該插件有兩個版本,支持Vue3的Beta版本,和支持Vue2的版本。 image.pnggithub

要了解什麼?

此次主要了解在新版本DevTools中支持了一個新特性:在選擇對應的組件後,點擊open-in-editor的按鈕後,便可在編譯器中打開對應的組件。web

image.png

實現原理:

主要經過launch-editor-middleware和launch-editor兩個庫實現了該功能,這兩個庫又經過調用node的process、child_process能力,建立一個node的子進程調起編譯器打開選中的組件vue-cli

閱讀前準備:

  1. 在Chrome中準備支持Vue3的最新版本插件(目前最新版本號6.0.0 beta 15)
  2. vue create 建立一個vue-cli3項目
  3. 準備一個編譯器

開始調試:

Open in editor在Vue3中是一個開箱即用的功能shell

具體如何配置使用:Open component in editornpm

1.尋找入口,進行調試

1.1尋找入口

根據上述文檔的項目引入配置,須要在編譯器中搜索'/__open-in-editor',便可在node_modules 中定位到該方法,此時在此處打個點~json

image.png

再繼續進入launchEditorMiddleware 發現這個中間件會調用launch-editor進行後續的打開編譯器操做,此時能夠在調用launch函數這行打上一個點~

image.png

1.2啓動調試

以Vscode爲例:

進入項目的package.json,能夠看到在script屬性上有一個「調試」或「debug」的按鈕,點擊後選擇serve便可進入調試模式

image.png

在這裏我踩了一個小坑(也是由於本身不夠謹慎)

在npm i完成以後,先npm run serve在8080端口啓動了項目,再點擊調試

這會形成編譯器再開啓一個進程在8081端口啓動項目,這也許會讓你在後續調試時發現沒法進入斷點處

此時須要注意調試啓動的項目端口是否與瀏覽器端口一致

接下來就進入到閱讀源碼部分~

開始閱讀:

1.launchEditorMiddleware部分

在項目開始編譯時,就會自動進入該部分代碼。

我的理解在這部分代碼中主要作了兩件事:

1.函數重載,知足不一樣開發傳參需求

2.經過node.js獲取當前進程所在的位置,爲後續打開編譯器作準備

// serve.js
app.use('/__open-in-editor', launchEditorMiddleware(() => console.log(
  `To specify an editor, specify the EDITOR env variable or ` +
  `add "editor" field to your Vue project config.\n`
)))

//launch-editor-middleware/index.js
module.exports = (specifiedEditor, srcRoot, onErrorCallback) => {
  //這裏對傳入的第一個參數作一個判斷,若是該參數爲函數,則將這個參數與錯誤回調函數的值進行對調
  if (typeof specifiedEditor === 'function') {
      onErrorCallback = specifiedEditor
      specifiedEditor = undefined
    }
    //一樣對傳入的第二個參數也是作一樣的判斷
  if (typeof srcRoot === 'function') {
    onErrorCallback = srcRoot
    srcRoot = undefined
  }
    //第二個參數若是傳入的是目錄,則直接用
  //若是不是則調用node.js中process的能力,獲取當前進程所在的位置
  srcRoot = srcRoot || process.cwd()
  return function launchEditorMiddleware (req, res, next) {
    //返回一箇中間件
  }
}
複製代碼

2 launch-editor部分

2.1執行前路徑的判斷

F12打開Vue-DevTools調試面板,選擇一個組件,點擊open-in-editor便可進入斷點處

此時,若是切換到Chrome的Network欄時,會發現此時瀏覽器發送了一個請求: image.png

結合編譯前的app.use('/__open-in-editor', launchEditorMiddleware(...)不難知道這是一箇中間件的寫法,當瀏覽器發送請求時,就會進入到接下來的代碼邏輯中

module.exports = (specifiedEditor, srcRoot, onErrorCallback) => {
    // ....省略
  return function launchEditorMiddleware (req, res, next) {
    // 首先會讀取路徑中的file參數
    const { file } = url.parse(req.url, true).query || {}
    if (!file) {
      res.statusCode = 500
      res.end(`launch-editor-middleware: required query param "file" is missing.`)
    } else {
      // 若是存在該路徑,則會執行launch-editor邏輯
      launch(path.resolve(srcRoot, file), specifiedEditor, onErrorCallback)
      res.end()
    }
  }
}
複製代碼
2.2執行中最重要的一部分

進入到launchEditor函數後,也是該功能最重要的一部分

function launchEditor (file, specifiedEditor, onErrorCallback) {
  //2.2.1經過正則匹配的方式讀取文件路徑、行號、列號的信息並進行返回
  const parsed = parseFile(file)
  let { fileName } = parsed
  const { lineNumber, columnNumber } = parsed
    // 2.2.2調用node.js的方法,以同步的方式檢測該路徑是否存在,不存在就return結束
  if (!fs.existsSync(fileName)) {
    return
  }
    // 這裏一樣是一個函數重載的方法
  if (typeof specifiedEditor === 'function') {
    onErrorCallback = specifiedEditor
    specifiedEditor = undefined
  }
    // 2.2.3這裏跟錯誤回調調用了一個方法,比較有意思
  onErrorCallback = wrapErrorCallback(onErrorCallback)

}
複製代碼

2.2.3部分,採用了裝飾器模式(感謝同組的紀年小姐姐的總結),原理是將要執行的邏輯包裹起來,先執行其餘的須要處理的代碼,再執行onErrorCallback的邏輯。

繼續閱讀函數~

function wrapErrorCallback (cb) {
  return (fileName, errorMessage) => {
    console.log()
    //這裏先作了一個錯誤的輸出,同時調用node.js中path的方法,提取出用"/"隔開的path最後一部份內容共
    //而且用了一個chalk庫,能夠改變控制檯輸出內容的顏色
    console.log(
      chalk.red('Could not open ' + path.basename(fileName) + ' in the editor.')
    )
    // 此時若是有錯誤信息時,纔會輸出錯誤信息的提示
    if (errorMessage) {
      if (errorMessage[errorMessage.length - 1] !== '.') {
        errorMessage += '.'
      }
      console.log(
        chalk.red('The editor process exited with an error: ' + errorMessage)
      )
    }
    console.log()
    if (cb) cb(fileName, errorMessage)
  }
}
複製代碼

若此時在這部分沒有報錯,則會繼續進行接下來的流程。

2.2.4 此時會進入一個很「刺激」的猜想環節

//launch-editor/index.js
function launchEditor (file, specifiedEditor, onErrorCallback) {
  ...
    // 此時代碼進入猜想函數
  const [editor, ...args] = guessEditor(specifiedEditor)
}

// launch-editor/guess.js
module.exports = function guessEditor (specifiedEditor) {
  // 第一步:判斷有沒有傳入對應的shell命令
  if (specifiedEditor) {
    // 若是傳入,利用shell-quote庫解析shell命令
    return shellQuote.parse(specifiedEditor)
  }
  // We can find out which editor is currently running by:
  // `ps x` on macOS and Linux
  // `Get-Process` on Windows
  
  // 第二步:猜想環節
  // 上面的三行註釋也說明了能夠判斷當前是在哪一個系統環境下運行,從而決定用何種方式啓動編譯器
  try {
    // 經過node.js中process中標識運行node.js進程的操做系統的方法獲取當前的操做系統
    // 由於個人系統是MacOs,直接進入第一個猜想中
    if (process.platform === 'darwin') {
      // 此時調用了同步建立子進程的方法,這裏會獲取到目前的全部進程
      const output = childProcess.execSync('ps x').toString()
      // COMMON_EDITORS_OSX爲一個map表,裏面維護着MacOs下支持的編譯器,以及對應的字段
      // 經過遍歷的方式與當前系統中存在的編譯器進行匹配
      const processNames = Object.keys(COMMON_EDITORS_OSX)
      for (let i = 0; i < processNames.length; i++) {
        const processName = processNames[i]
        if (output.indexOf(processName) !== -1) {
          return [COMMON_EDITORS_OSX[processName]]
        }
      }
    }
  // ... 不一樣平臺的我就省略了,原理相似
  // 最後還有一個兜底的方案
  // Last resort, use old skool env vars
  if (process.env.VISUAL) {
    return [process.env.VISUAL]
  } else if (process.env.EDITOR) {
    return [process.env.EDITOR]
  }
  return [null]
}
複製代碼

2.2.5 猜想完以後的操做

function launchEditor (file, specifiedEditor, onErrorCallback) {
    // ...
  const [editor, ...args] = guessEditor(specifiedEditor)
  // 若是沒有找到,就會報錯
  if (!editor) {
    onErrorCallback(fileName, null)
    return
  }
    // 核心部分,根據不一樣的系統狀態,打開調起不一樣的工具打開編譯器
  // childProcess.spawn爲異步衍生子進程,而且不會阻塞node.js的事件循環
  if (process.platform === 'win32') {
    // On Windows, launch the editor in a shell because spawn can only
    // launch .exe files.
    _childProcess = childProcess.spawn(
      'cmd.exe',
      ['/C', editor].concat(args),
      { stdio: 'inherit' }
    )
  } else {
    // 由於是MacOs,所以調用Vscode,打開args地址(項目地址),而且子進程將使用父進程的標準輸入輸出。
    // 這塊Node文檔參考
    // http://nodejs.cn/api/child_process.html#child_process_child_process_spawn_command_args_options
    // 到這裏,對應的組件文件就已經在編譯器中被打開了
    _childProcess = childProcess.spawn(editor, args, { stdio: 'inherit' })
  }
    // 這裏是對子進程結束後觸發作監聽,檢測進程退出是否存在異常
  _childProcess.on('exit', function (errorCode) {
    _childProcess = null

    if (errorCode) {
      onErrorCallback(fileName, '(code ' + errorCode + ')')
    }
  })

  _childProcess.on('error', function (error) {
    onErrorCallback(fileName, error.message)
  })
}
複製代碼

總結

首先小小的表揚一下本身,終於克服了不會讀不敢讀源碼的問題

🎉🎉🎉🎉🎉🎉🎉

之前以爲源碼都很難懂,框架也很難了解真正的原理。可是經過此次活動,小小的明白了一個工具中一個小模塊的實現方法,頗有意思。

也很感謝若川大佬組織此次活動,辛苦了。

此次閱讀的過程同時也發現了原來Node能夠作不少事情,這也是以前沒有了解過的知識點。

相關文檔和資料:

Vue-DevTools:github.com/vuejs/devto…

尤大版本launch-editor:github.com/yyx990803/l…

Umijs/launch-editor:github.com/umijs/launc…

相關文章
相關標籤/搜索