你好,我是若川,微信搜索「若川視野」關注我,專一前端技術分享,一個願景是幫助5年內前端開闊視野走向前列的公衆號。歡迎加我微信
ruochuan12
,長期交流學習。前端這是
學習源碼總體架構系列
之 launch-editor 源碼(第九篇)。學習源碼總體架構系列文章(有哪些必看的JS庫):jQuery、underscore、lodash、sentry、vuex、axios、koa、redux。總體架構這詞語好像有點大,姑且就算是源碼總體結構吧,主要就是學習是代碼總體結構,不深究其餘不是主線的具體函數的實現。本篇文章學習的是實際倉庫的代碼。下一篇應該是《學習 Vuex 4 源碼總體架構,深刻理解其原理及provide/inject原理》。vue本文倉庫地址:
git clone https://github.com/lxchuan12/open-in-editor.git
,本文最佳閱讀方式,克隆倉庫本身動手調試,容易吸取消化。node要是有人說到怎麼讀源碼,正在讀文章的你能推薦個人源碼系列文章,那真是無覺得報啊。react
個人文章儘可能寫得讓想看源碼又不知道怎麼看的讀者能看懂。我都是推薦使用搭建環境斷點調試源碼學習,哪裏不會點哪裏,邊調試邊看,而不是硬看。正所謂:授人與魚不如授人予漁。linux
閱讀本文後你將學到:ios
launch-editor-middleware、launch-editor
等實現原理不知道大家有沒有碰到這樣的場景,打開你本身(或者你同事)開發的頁面,卻短期難以找到對應的源文件。git
這時你可能會想要是能有點擊頁面按鈕自動用編輯器打開對應文件的功能,那該多好啊。github
而vue-devtools
提供了這樣的功能,也許你不知道。我以爲很大一部分人都不知道,由於感受不少人都不經常使用vue-devtools
。web
你也許會問,我不用vue
,我用react
有沒有相似功能啊,有啊,請看react-dev-inspector。你可能還會問,支持哪些編輯器呀,主流的 vscode、webstorm、atom、sublime
等都支持,更多能夠看這個列表 Supported editors。vuex
本文就是根據學習尤大寫的 launch-editor 源碼,本着知其然,知其因此然的宗旨,探究 vue-devtools
「在編輯器中打開組件」功能實現原理。
code path/to/file
一句話簡述原理:利用nodejs
中的child_process
,執行了相似code path/to/file
命令,因而對應編輯器就打開了相應的文件,而對應的編輯器則是經過在進程中執行ps x
(Window
則用Get-Process
)命令來查找的,固然也能夠本身指定編輯器。
而你真正用這個功能時,你可能碰到報錯,說不能打開這個文件。
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
電腦,在Ubuntu
子系統下使用的終端工具。同時推薦個人文章 使用 ohmyzsh 打造 windows、ubuntu、mac 系統高效終端命令行工具, 用過的都說好。
解決辦法也簡單,就是這句英文的意思。具體說明編輯器,在環境變量中說明指定編輯器。在vue
項目的根目錄下,對應本文則是:vue3-project
,添加.env.delelopment
文件,其內容是EDITOR=code
。
# .env.development # 固然,個人命令行終端已經有了code這個命令。 EDITOR=code
不用指定編輯器的對應路徑(c/Users/lxchu/AppData/Local/Programs/Microsoft VS Code/bin/code
),由於會報錯。爲何會報錯,由於我看了源碼且試過。由於會被根據空格截斷,變成c/Users/lxchu/AppData/Local/Programs/Microsoft
,固然就報錯了。
接下來咱們從源碼角度探究「在編輯器中打開組件」功能的實現原理。
探究原理以前,先來看看vue-devtools
官方文檔。
Open component in editor
To enable this feature, follow this guide.
這篇指南中寫了在Vue CLI 3
中是開箱即用。
Vue CLI 3 supports this feature out-of-the-box when running vue-cli-service serve.
也詳細寫了如何在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`開發者工具備提示點擊的組件的顯示具體路徑,那麼你能夠在編輯器打開。
同時也寫了如何在Node.js
中使用等。
Node.js
You can use the launch-editor package to setup an HTTP route with the/__open-in-editor
path. It will receive file as an URL variable.
查看更多能夠看這篇指南。
熟悉個人讀者,都知道我都是推薦調試看源碼的,正所謂:哪裏不會點哪裏。並且調試通常都寫得很詳細,是但願能幫助到一部分人知道如何看源碼。因而我特地新建一個倉庫open-in-editor git clone https://github.com/lxchuan12/open-in-editor.git
,便於你們克隆學習。
安裝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 # 這裏選擇的是vue三、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
按鈕。
選擇第一項,serve vue-cli-service serve
。
咱們來搜索下'launch-editor-middleware'
這個中間件,通常來講搜索不到node_modules
下的文件,須要設置下。固然也有個簡單作法。就是「排除的文件」右側旁邊有個設置圖標「使用「排查設置」與「忽略文件」」,點擊下。
其餘的就不贅述了。能夠看這篇知乎回答:vscode怎麼設置能夠搜索包含node_modules中的文件?
這時就搜到了vue3-project/node_modules/@vue/cli-service/lib/commands/serve.js
中有使用這個中間件。
接着咱們來看Vue CLI 3
中開箱即用具體源碼實現。
// 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-overlay 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` ))) // 省略若干代碼... }
點擊vue-devtools
中的時,會有一個請求,http://localhost:8080/__open-in-editor?file=src/App.vue
,不出意外就會打開該組件啦。
接着咱們在launchEditorMiddleware
的具體實現。
看源碼時,先看調試截圖。
在launch-editor-middleware
中間件中做用在於最終是調用 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() 函數 // 因此和 onErrorCallback 切換下,把它賦值給錯誤回調函數 if (typeof specifiedEditor === 'function') { onErrorCallback = specifiedEditor specifiedEditor = undefined } // 若是第二個參數是函數,一樣把它賦值給錯誤回調函數 // 這裏傳遞過來的是undefined if (typeof srcRoot === 'function') { onErrorCallback = srcRoot srcRoot = undefined } // srcRoot 是傳遞過來的參數,或者當前node進程的目錄 srcRoot = srcRoot || process.cwd() // 最後返回一個函數, express 中間件 return function launchEditorMiddleware (req, res, next) { // 省略 ... } }
上一段中,這種切換參數的寫法,在不少源碼中都很常見。爲的是方便用戶調用時傳參。雖然是多個參數,但能夠傳一個或者兩個。
能夠根據狀況打上斷點。好比這裏我會在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) { // 根據請求解析出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打開。 launch(path.resolve(srcRoot, file), specifiedEditor, onErrorCallback) res.end() } } }
跟着斷點來看,走到了launchEditor
函數。
// vue3-project/node_modules/launch-editor/index.js function launchEditor (file, specifiedEditor, onErrorCallback) { // 解析出文件路徑和行號列號等信息 const parsed = parseFile(file) let { fileName } = parsed const { lineNumber, columnNumber } = parsed // 判斷文件是否存在,不存在,直接返回。 if (!fs.existsSync(fileName)) { return } // 因此和 onErrorCallback 切換下,把它賦值給錯誤回調函數 if (typeof specifiedEditor === 'function') { onErrorCallback = specifiedEditor specifiedEditor = undefined } // 包裹一層函數 onErrorCallback = wrapErrorCallback(onErrorCallback) // 猜想當前進程運行的是哪一個編輯器 const [editor, ...args] = guessEditor(specifiedEditor) if (!editor) { onErrorCallback(fileName, null) return } // 省略剩餘部分,後文再講述... }
onErrorCallback = wrapErrorCallback(onErrorCallback)
這段的代碼,就是傳遞錯誤回調函數,wrapErrorCallback
返回給一個新的函數,wrapErrorCallback
執行時,再去執行 onErrorCallback(cb)。
我相信讀者朋友能看懂,我單獨拿出來說述,主要是由於這種包裹函數的形式在不少源碼裏都很常見。
這裏也就是文章開頭終端錯誤圖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) } }
這個函數主要作了以下四件事情:
macOS
和 Linux
用 ps x
命令windows
則用 Get-Process
命令process.env.VISUAL
或者process.env.EDITOR
。這就是爲啥開頭錯誤提示可使用環境變量指定編輯器的緣由。[null]
,則會報錯。const [editor, ...args] = guessEditor(specifiedEditor) if (!editor) { onErrorCallback(fileName, null) return }
// vue3-project/node_modules/launch-editor/guess.js const shellQuote = require('shell-quote') module.exports = function guessEditor (specifiedEditor) { // 若是指定了編輯器,則解析一下,這裏沒有傳入。若是本身指定了路徑。 // 好比 c/Users/lxchu/AppData/Local/Programs/Microsoft VS Code/bin/code // 會根據空格切割成 c/Users/lxchu/AppData/Local/Programs/Microsoft if (specifiedEditor) { 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 { // 省略... } catch (error) { // Ignore... } // 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] }
看完了 guessEditor 函數,咱們接着來看 launch-editor
剩餘部分。
如下這段代碼不用細看,調試的時候細看就行。
// 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 = null if (errorCode) { onErrorCallback(fileName, '(code ' + errorCode + ')') } }) _childProcess.on('error', function (error) { onErrorCallback(fileName, error.message) }) }
這一大段中,主要的就是如下代碼,用子進程模塊。簡單來講子進程模塊有着執行命令的能力。
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' }) }
行文至此,就基本接近尾聲了。
這裏總結一下:首先文章開頭經過提出「短期找不到頁面對應源文件的場景」,並針對容易碰到的報錯狀況給出瞭解決方案。
其次,配置了環境跟着調試學習了vue-devtools
中使用的尤大寫的 yyx990803/launch-editor。
咱們回顧下開頭的原理內容。
code path/to/file
一句話簡述原理:利用nodejs
中的child_process
,執行了相似code path/to/file
命令,因而對應編輯器就打開了相應的文件,而對應的編輯器則是經過在進程中執行ps x
(Window
則用Get-Process
)命令來查找的,固然也能夠本身指定編輯器。
最後還能作什麼呢。
能夠再看看 umijs/launch-editor 和 react-dev-utils/launchEditor.js 。他們的代碼幾乎相似。
也能夠利用Node.js
作一些提升開發效率等工做,同時能夠學習child_process
等模塊。
也不要禁錮本身的思惟,把前端禁錮在頁面中,應該把視野拓寬。
Node.js
是咱們前端人探索操做文件、操做網絡等的好工具。
若是讀者朋友發現有不妥或可改善之處,再或者哪裏沒寫明白的地方,歡迎評論指出。另外以爲寫得不錯,對您有些許幫助,能夠點贊、評論、轉發分享,也是對個人一種支持,萬分感謝。若是能關注個人前端公衆號: 「若川視野」,就更好啦。
你好,我是 若川,微信搜索 「若川視野」關注我,專一前端技術分享,一個願景是幫助5年內前端開闊視野走向前列的公衆號。歡迎加我微信ruochuan12
,長期交流學習。
主要有如下系列文章: 學習源碼總體架構系列、 年度總結、 JS基礎系列
yyx990803/launch-editor
umijs/launch-editor
vuejs/vue-devtools
vue-devtools open-in-editor.md
"Open in editor" button doesn't work in Win 10 with VSCode if installation path contains spaces
react-dev-utils/launchEditor.js