据说 99% 的人不知道 vue-devtools 还能直接打开对应组件文件?本文原理揭秘
1. 前言
你好,我是若川[1],微信搜索「若川視野」關(guān)注我,專注前端技術(shù)分享,一個愿景是幫助5年內(nèi)前端開闊視野走向前列的公眾號。歡迎加我微信ruochuan12,長期交流學(xué)習(xí)。
這是學(xué)習(xí)源碼整體架構(gòu)系列 之 launch-editor 源碼(第九篇)。學(xué)習(xí)源碼整體架構(gòu)系列文章(有哪些必看的JS庫):jQuery、underscore、lodash、sentry、vuex、axios、koa、redux。
本文倉庫地址[2]:git clone https://github.com/lxchuan12/open-in-editor.git,本文最佳閱讀方式,克隆倉庫自己動手調(diào)試,容易吸收消化。
要是有人說到怎么讀源碼,正在讀文章的你能推薦我的源碼系列文章,那真是無以為報啊。
我的文章盡量寫得讓想看源碼又不知道怎么看的讀者能看懂。我都是推薦使用搭建環(huán)境斷點調(diào)試源碼學(xué)習(xí),哪里不會點哪里,邊調(diào)試邊看,而不是硬看。正所謂:授人與魚不如授人予漁。
閱讀本文后你將學(xué)到:
如何解決該功能報錯問題
如何調(diào)試學(xué)習(xí)源碼
launch-editor-middleware、launch-editor 等實現(xiàn)原理
1.1 短時間找不到頁面對應(yīng)源文件的場景
不知道你們有沒有碰到這樣的場景,打開你自己(或者你同事)開發(fā)的頁面,卻短時間難以找到對應(yīng)的源文件。
這時你可能會想要是能有點擊頁面按鈕自動用編輯器打開對應(yīng)文件的功能,那該多好啊。
而vue-devtools提供了這樣的功能,也許你不知道。我覺得很大一部分人都不知道,因為感覺很多人都不常用vue-devtools。
open-in-editor你也許會問,我不用vue,我用react有沒有類似功能啊,有啊,請看react-dev-inspector[3]。你可能還會問,支持哪些編輯器呀,主流的 vscode、webstorm、atom、sublime 等都支持,更多可以看這個列表 Supported editors[4]。
本文就是根據(jù)學(xué)習(xí)尤大寫的 launch-editor[5] 源碼,本著知其然,知其所以然的宗旨,探究 vue-devtools「在編輯器中打開組件」功能實現(xiàn)原理。
1.2 一句話簡述其原理
code?path/to/file一句話簡述原理:利用nodejs中的child_process,執(zhí)行了類似code path/to/file命令,于是對應(yīng)編輯器就打開了相應(yīng)的文件,而對應(yīng)的編輯器則是通過在進程中執(zhí)行ps x(Window則用Get-Process)命令來查找的,當(dāng)然也可以自己指定編輯器。
1.3 打開編輯器無法打開組件的報錯解決方法
而你真正用這個功能時,你可能碰到報錯,說不能打開這個文件。
Could?not?open?App.vue?in?the?editor.To?specify?an?editor,?specify?the?EDITOR?env?variable?or?add?"editor"?field?to?your?Vue?project?config. 控制臺不能打開編輯器的錯誤提示這里說明下寫這篇文章時用的是 Windows 電腦,VSCode 編輯器,在Ubuntu子系統(tǒng)下使用的終端工具。同時推薦我的文章使用 ohmyzsh 打造 windows、ubuntu、mac 系統(tǒng)高效終端命令行工具,用過的都說好。
解決辦法也簡單,就是這句英文的意思。
1.3.1 方法一:先確保在終端能用命令打開你使用的編輯器,文中以VSCode為例
如果你的命令行本身就不能運行code等命令打開編輯器,那肯定是報錯的。這時需要把VSCode注入到命令行終端中。注入方法也簡單。我的交流群里有小伙伴提供了mac電腦的截圖。
mac 電腦在 VSCode command + shift + p,Windows 則是 ctrl + shift + p。然后輸入shell,選擇安裝code。如下圖:
Install 'code' command in PATH這樣就能在終端中打開VSCode的了。
如果能在終端打開使用命令編輯器能打開,但實際上還是報錯,那么大概率是沒有識別到你的編輯器。那么可以通過方法二設(shè)置指定編輯器。
1.3.2 方法二:具體說明編輯器,在環(huán)境變量中說明指定編輯器
在vue項目的根目錄下,對應(yīng)本文則是:vue3-project,添加.env.delelopment文件,其內(nèi)容是EDITOR=code。這里重點說明下,我的 vue-cli 版本是4.5.12,好像在vue-cli 3.5及以上版本才支持自定義EDITOR這樣的環(huán)境變量。
#?.env.development #?當(dāng)然,我的命令行終端已經(jīng)有了code這個命令。 EDITOR=code不用指定編輯器的對應(yīng)路徑(c/Users/lxchu/AppData/Local/Programs/Microsoft VS Code/bin/code),因為會報錯。為什么會報錯,因為我看了源碼且試過。因為會被根據(jù)空格截斷,變成c/Users/lxchu/AppData/Local/Programs/Microsoft,當(dāng)然就報錯了。
也有可能你的編輯器路徑有中文路徑導(dǎo)致報錯,可以在環(huán)境變量中添加你的編輯器路徑。
如果你通過以上方法,還沒解決報錯問題。歡迎留言,或者加我微信 ruochuan12 交流。畢竟電腦環(huán)境不一,很難保證所有人都能正常執(zhí)行,但我們知道了其原理,就很容易解決問題。
接下來我們從源碼角度探究「在編輯器中打開組件」功能的實現(xiàn)原理。
2. vue-devtools Open component in editor 文檔
探究原理之前,先來看看vue-devtools官方文檔。
vuejs/vue-devtools[6]文檔
Open component in editor
To enable this feature, follow this guide[7].
這篇指南中寫了在Vue CLI 3中是開箱即用。
Vue?CLI?3?supports?this?feature?out-of-the-box?when?running?vue-cli-service?serve.也詳細(xì)寫了如何在Webpack下使用。
#?1.?Import?the?package: var?openInEditor?=?require('launch-editor-middleware') #?2.?In?the?devServer?option,?register?the?/__open-in-editor?HTTP?route: devServer:?{before?(app)?{app.use('/__open-in-editor',?openInEditor())} } #?3.?The?editor?to?launch?is?guessed.?You?can?also?specify?the?editor?app?with?the?editor?option.?See?the?supported?editors?list. #?用哪個編輯器打開會自動猜測。你也可以具體指明編輯器。這里顯示更多的支持編輯器列表 openInEditor('code') #?4.?You?can?now?click?on?the?name?of?the?component?in?the?Component?inspector?pane?(if?the?devtools?knows?about?its?file?source,?a?tooltip?will?appear). #?如果`vue-devtools`開發(fā)者工具有提示點擊的組件的顯示具體路徑,那么你可以在編輯器打開。同時也寫了如何在Node.js中使用等。
Node.js
You can use the launch-editor[8] package to setup an HTTP route with the /__open-in-editor path. It will receive file as an URL variable.
查看更多可以看這篇指南[9]。
3. 環(huán)境準(zhǔn)備工作
熟悉我的讀者,都知道我都是推薦調(diào)試看源碼的,正所謂:哪里不會點哪里。而且調(diào)試一般都寫得很詳細(xì),是希望能幫助到一部分人知道如何看源碼。于是我特意新建一個倉庫open-in-editor[10] git clone https://github.com/lxchuan12/open-in-editor.git,便于大家克隆學(xué)習(xí)。
安裝vue-cli
npm?install?-g?@vue/cli #?OR yarn?global?add?@vue/cli node?-V #?v14.16.0 vue?-V? #?@vue/cli?4.5.12 vue?create?vue3-project #?這里選擇的是vue3、vue2也是一樣的。 #?Please?pick?a?preset:?Default?(Vue?3?Preview)?([Vue?3]?babel,?eslint) npm?install #?OR yarn?install這里同時說明下我的vscode版本。
code?-v 1.55.2前文提到的Vue CLI 3中開箱即用和Webpack使用方法。
vue3-project/package.json中有一個debug按鈕。
debug示意圖選擇第一項,serve vue-cli-service serve。
我們來搜索下'launch-editor-middleware'這個中間件,一般來說搜索不到node_modules下的文件,需要設(shè)置下。當(dāng)然也有個簡單做法。就是「排除的文件」右側(cè)旁邊有個設(shè)置圖標(biāo)「使用“排查設(shè)置”與“忽略文件”」,點擊下。
其他的就不贅述了。可以看這篇知乎回答:vscode怎么設(shè)置可以搜索包含node_modules中的文件?[11]
這時就搜到了vue3-project/node_modules/@vue/cli-service/lib/commands/serve.js中有使用這個中間件。
4. vue-devtools 開箱即用具體源碼實現(xiàn)
接著我們來看Vue CLI 3中開箱即用具體源碼實現(xiàn)。
//?vue3-project/node_modules/@vue/cli-service/lib/commands/serve.js //?46行 const?launchEditorMiddleware?=?require('launch-editor-middleware') //?192行 before?(app,?server)?{//?launch?editor?support.//?this?works?with?vue-devtools?&?@vue/cli-overlayapp.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`)))//?省略若干代碼... }點擊vue-devtools中的時,會有一個請求,http://localhost:8080/__open-in-editor?file=src/App.vue,不出意外就會打開該組件啦。
open src/App.vue in editor接著我們在launchEditorMiddleware的具體實現(xiàn)。
5. launch-editor-middleware
看源碼時,先看調(diào)試截圖。
debug-launch在launch-editor-middleware中間件中作用在于最終是調(diào)用 launch-editor 打開文件。
//?vue3-project/node_modules/launch-editor-middleware/index.js const?url?=?require('url') const?path?=?require('path') const?launch?=?require('launch-editor')module.exports?=?(specifiedEditor,?srcRoot,?onErrorCallback)?=>?{//?specifiedEditor?=>?這里傳遞過來的則是?()?=>?console.log()?函數(shù)//?所以和?onErrorCallback?切換下,把它賦值給錯誤回調(diào)函數(shù)if?(typeof?specifiedEditor?===?'function')?{onErrorCallback?=?specifiedEditorspecifiedEditor?=?undefined}//?如果第二個參數(shù)是函數(shù),同樣把它賦值給錯誤回調(diào)函數(shù)//?這里傳遞過來的是undefinedif?(typeof?srcRoot?===?'function')?{onErrorCallback?=?srcRootsrcRoot?=?undefined}//?srcRoot?是傳遞過來的參數(shù),或者當(dāng)前node進程的目錄srcRoot?=?srcRoot?||?process.cwd()//?最后返回一個函數(shù),?express?中間件return?function?launchEditorMiddleware?(req,?res,?next)?{//?省略?...} }上一段中,這種切換參數(shù)的寫法,在很多源碼中都很常見。為的是方便用戶調(diào)用時傳參。雖然是多個參數(shù),但可以傳一個或者兩個。
可以根據(jù)情況打上斷點。比如這里我會在launch(path.resolve(srcRoot, file), specifiedEditor, onErrorCallback)打斷點。
//?vue3-project/node_modules/launch-editor-middleware/index.js module.exports?=?(specifiedEditor,?srcRoot,?onErrorCallback)?=>?{//?省略上半部分return?function?launchEditorMiddleware?(req,?res,?next)?{//?根據(jù)請求解析出file路徑const?{?file?}?=?url.parse(req.url,?true).query?||?{}//?如果沒有文件路徑,則報錯if?(!file)?{res.statusCode?=?500res.end(`launch-editor-middleware:?required?query?param?"file"?is?missing.`)}?else?{//?否則拼接路徑,用launch打開。launch(path.resolve(srcRoot,?file),?specifiedEditor,?onErrorCallback)res.end()}} }6. launch-editor
跟著斷點來看,走到了launchEditor函數(shù)。
//?vue3-project/node_modules/launch-editor/index.js function?launchEditor?(file,?specifiedEditor,?onErrorCallback)?{//?解析出文件路徑和行號列號等信息const?parsed?=?parseFile(file)let?{?fileName?}?=?parsedconst?{?lineNumber,?columnNumber?}?=?parsed//?判斷文件是否存在,不存在,直接返回。if?(!fs.existsSync(fileName))?{return}//?所以和?onErrorCallback?切換下,把它賦值給錯誤回調(diào)函數(shù)if?(typeof?specifiedEditor?===?'function')?{onErrorCallback?=?specifiedEditorspecifiedEditor?=?undefined}//?包裹一層函數(shù)onErrorCallback?=?wrapErrorCallback(onErrorCallback)//?猜測當(dāng)前進程運行的是哪個編輯器const?[editor,?...args]?=?guessEditor(specifiedEditor)if?(!editor)?{onErrorCallback(fileName,?null)return}//?省略剩余部分,后文再講述... }6.1 wrapErrorCallback 包裹錯誤函數(shù)回調(diào)
onErrorCallback?=?wrapErrorCallback(onErrorCallback)這段的代碼,就是傳遞錯誤回調(diào)函數(shù),wrapErrorCallback 返回給一個新的函數(shù),wrapErrorCallback 執(zhí)行時,再去執(zhí)行 onErrorCallback(cb)。
我相信讀者朋友能看懂,我單獨拿出來講述,主要是因為這種包裹函數(shù)的形式在很多源碼里都很常見。
這里也就是文章開頭終端錯誤圖Could not open App.vue in the editor.輸出的代碼位置。
//?vue3-project/node_modules/launch-editor/index.js function?wrapErrorCallback?(cb)?{return?(fileName,?errorMessage)?=>?{console.log()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)} }6.2 guessEditor 猜測當(dāng)前正在使用的編輯器
這個函數(shù)主要做了如下四件事情:
如果具體指明了編輯器,則解析下返回。
找出當(dāng)前進程中哪一個編輯器正在運行。macOS 和 Linux 用 ps x 命令
windows 則用 Get-Process 命令
如果都沒找到就用 process.env.VISUAL或者process.env.EDITOR。這就是為啥開頭錯誤提示可以使用環(huán)境變量指定編輯器的原因。
最后還是沒有找到就返回[null],則會報錯。
看完了 guessEditor 函數(shù),我們接著來看 launch-editor 剩余部分。
6.3 launch-editor 剩余部分
以下這段代碼不用細(xì)看,調(diào)試的時候細(xì)看就行。
//?vue3-project/node_modules/launch-editor/index.js function?launchEditor(){//??省略上部分...if?(process.platform?===?'linux'?&&fileName.startsWith('/mnt/')?&&/Microsoft/i.test(os.release()))?{//?Assume?WSL?/?"Bash?on?Ubuntu?on?Windows"?is?being?used,?and//?that?the?file?exists?on?the?Windows?file?system.//?`os.release()`?is?"4.4.0-43-Microsoft"?in?the?current?release//?build?of?WSL,?see:?https://github.com/Microsoft/BashOnWindows/issues/423#issuecomment-221627364//?When?a?Windows?editor?is?specified,?interop?functionality?can//?handle?the?path?translation,?but?only?if?a?relative?path?is?used.fileName?=?path.relative('',?fileName)}if?(lineNumber)?{const?extraArgs?=?getArgumentsForPosition(editor,?fileName,?lineNumber,?columnNumber)args.push.apply(args,?extraArgs)}?else?{args.push(fileName)}if?(_childProcess?&&?isTerminalEditor(editor))?{//?There's?an?existing?editor?process?already?and?it's?attached//?to?the?terminal,?so?go?kill?it.?Otherwise?two?separate?editor//?instances?attach?to?the?stdin/stdout?which?gets?confusing._childProcess.kill('SIGKILL')}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?{_childProcess?=?childProcess.spawn(editor,?args,?{?stdio:?'inherit'?})}_childProcess.on('exit',?function?(errorCode)?{_childProcess?=?nullif?(errorCode)?{onErrorCallback(fileName,?'(code?'?+?errorCode?+?')')}})_childProcess.on('error',?function?(error)?{onErrorCallback(fileName,?error.message)}) }這一大段中,主要的就是以下代碼,用子進程模塊。簡單來說子進程模塊有著執(zhí)行命令的能力。
const?childProcess?=?require('child_process')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?{_childProcess?=?childProcess.spawn(editor,?args,?{?stdio:?'inherit'?}) }行文至此,就基本接近尾聲了。原理其實就是利用nodejs中的child_process,執(zhí)行了類似code path/to/file命令。
7. 總結(jié)
這里總結(jié)一下:首先文章開頭通過提出「短時間找不到頁面對應(yīng)源文件的場景」,并針對容易碰到的報錯情況給出了解決方案。其次,配置了環(huán)境跟著調(diào)試學(xué)習(xí)了vue-devtools中使用的尤大寫的 yyx990803/launch-editor[12]。
7.1 一句話簡述其原理
我們回顧下開頭的原理內(nèi)容。
code?path/to/file一句話簡述原理:利用nodejs中的child_process,執(zhí)行了類似code path/to/file命令,于是對應(yīng)編輯器就打開了相應(yīng)的文件,而對應(yīng)的編輯器則是通過在進程中執(zhí)行ps x(Window則用Get-Process)命令來查找的,當(dāng)然也可以自己指定編輯器。
最后還能做什么呢。
可以再看看 umijs/launch-editor[13] 和 react-dev-utils/launchEditor.js[14] 。他們的代碼幾乎類似。
也可以利用Node.js做一些提高開發(fā)效率等工作,同時可以學(xué)習(xí)child_process等模塊。
也不要禁錮自己的思維,把前端禁錮在頁面中,應(yīng)該把視野拓寬。
Node.js是我們前端人探索操作文件、操作網(wǎng)絡(luò)等的好工具。
如果讀者朋友發(fā)現(xiàn)有不妥或可改善之處,再或者哪里沒寫明白的地方,歡迎評論指出。另外覺得寫得不錯,對您有些許幫助,可以點贊、評論、轉(zhuǎn)發(fā)分享,也是對我的一種支持,萬分感謝。如果能關(guān)注我的前端公眾號:「若川視野」,就更好啦。
參考資料
[1]
若川:?https://lxchuan12.gitee.io
[2]本文倉庫地址:?https://github.com/lxchuan12/open-in-editor.git
[3]更多參考鏈接,可以點擊閱讀原文查看
最近組建了一個江西人的前端交流群,如果你也是江西人可以加我微信 ruochuan12 拉你進群。
·················?若川出品?·················
今日話題
之前發(fā)的閱讀量慘淡,所以現(xiàn)在主要改了標(biāo)題,還有根據(jù)讀者一些反饋修改更新,所以重新編輯發(fā)布下原創(chuàng),爭取投稿到一些大號。歡迎在下方留言~? 歡迎分享、收藏、點贊、在看我的公眾號文章~
一個愿景是幫助5年內(nèi)前端人走向前列的公眾號
可加我個人微信?ruochuan12,長期交流學(xué)習(xí)
推薦閱讀
我在阿里招前端,我該怎么幫你?(現(xiàn)在還能加我進模擬面試群)
若川知乎問答:2年前端經(jīng)驗,做的項目沒什么技術(shù)含量,怎么辦?
點擊上方卡片關(guān)注我、加個星標(biāo),或者查看源碼等系列文章。
學(xué)習(xí)源碼整體架構(gòu)系列、年度總結(jié)、JS基礎(chǔ)系列
總結(jié)
以上是生活随笔為你收集整理的据说 99% 的人不知道 vue-devtools 还能直接打开对应组件文件?本文原理揭秘的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: tuxedo中间件tmadmin的命令使
- 下一篇: Rhino基础教程---三管混接