很感謝若川大佬組織的源碼閱讀小組活動
天天下班後逼本身學習學習html
如下爲若川原文:juejin.cn/post/695934…vue
做爲一個Vue開發者(不是),天然少不了Chrome中的Vue調試插件。node
Vue-DevTools是一個能夠在Chrome中進行Vue項目調試的工具,能夠幫助開發者在使用Vue開發時,更清楚的瞭解目前頁面中的組件、數據狀況。git
目前該插件有兩個版本,支持Vue3的Beta版本,和支持Vue2的版本。 github
此次主要了解在新版本DevTools中支持了一個新特性:在選擇對應的組件後,點擊open-in-editor
的按鈕後,便可在編譯器中打開對應的組件。web
主要經過launch-editor-middleware和launch-editor
兩個庫實現了該功能,這兩個庫又經過調用node的process、child_process
能力,建立一個node的子進程調起編譯器打開選中的組件vue-cli
vue create
建立一個vue-cli3項目Open in editor在Vue3中是一個開箱即用的功能shell
具體如何配置使用:Open component in editornpm
根據上述文檔的項目引入配置,須要在編譯器中搜索'/__open-in-editor'
,便可在node_modules
中定位到該方法,此時在此處打個點~json
再繼續進入launchEditorMiddleware
發現這個中間件會調用launch-editor
進行後續的打開編譯器操做,此時能夠在調用launch函數這行打上一個點~
以Vscode爲例:
進入項目的package.json
,能夠看到在script
屬性上有一個「調試」或「debug」的按鈕,點擊後選擇serve
便可進入調試模式
在這裏我踩了一個小坑(也是由於本身不夠謹慎)
在npm i完成以後,先npm run serve在8080端口啓動了項目,再點擊調試
這會形成編譯器再開啓一個進程在8081端口啓動項目,這也許會讓你在後續調試時發現沒法進入斷點處
此時須要注意調試啓動的項目端口是否與瀏覽器端口一致
接下來就進入到閱讀源碼部分~
在項目開始編譯時,就會自動進入該部分代碼。
我的理解在這部分代碼中主要作了兩件事:
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) {
//返回一箇中間件
}
}
複製代碼
F12打開Vue-DevTools調試面板,選擇一個組件,點擊open-in-editor
便可進入斷點處
此時,若是切換到Chrome的Network欄時,會發現此時瀏覽器發送了一個請求:
結合編譯前的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()
}
}
}
複製代碼
進入到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…