你好,我是若川,微信搜索「若川视野」关注我,专一前端技术分享,一个愿景是帮助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