我正在参与掘金会员专属活动-源码共读第一期,点击参与。
前言
学习尤大写的 launch-editor 源码,了解 Vue Devtools 面板中 Open component in editor
功能的实现原理。
过程原理简述
-
当我们点击 Vue Devtools 面板上的 open in editor 按钮时,会发起一个 get 请求
/_open-in-editor?file=/path/to/xxx.vue
-
我们本地开发启动一个项目时,相应的工具(例如 vue-cli-service \ vite)会创建一个开发服务器(http server),开发服务器会挂载一个路径为
/__open-in-editor
的中间件launch-editor-middleware
;所以当我们请求/_open-in-editor?file=/path/to/file
时,就会去执行中间件launch-editor-middleware
; -
中间件
launch-editor-middleware
内部实际调用launch-editor
打开编辑器及对应文件。
launch-editor-middleware 源码
launch-editor-middleware
导出的是一个工厂函数,执行它会返回一个中间件函数 launchEditorMiddleware
,所以在使用的时候需要先执行下它,让它返回一个中间件,例如:
const launchMiddleware = require('launch-editor-middleware')
app.use('/__open-in-editor', launchMiddleware())
launchEditorMiddleware
函数内部调用 launch-editor
函数打开文件。
在调用 launch-editor
前,对参数 specifiedEditor
、srcRoot
判断是否为函数,为函数就将值赋给 onErrorCallback
并将自身置为 undefined
,这样做的原因是三个参数都是可选的,让onErrorCallback
回调参数可以放在任意位置,只要它是作为最后一个参数。
launch-editor-middleware 包源码:
const path = require('path')
const launch = require('launch-editor')
/**
* 中间件工厂函数,三个参数都是可选的,onErrorCallback 回调参数可以放在任意位置,只要它是作为最后一个参数
* @param {*} specifiedEditor 指定打开的编辑器,值见 https://github.com/yyx990803/launch-editor#supported-editors
* @param {*} srcRoot 源文件根目录
* @param {*} onErrorCallback 启动编辑器失败时的回调
*/
module.exports = (specifiedEditor, srcRoot, onErrorCallback) => {
if (typeof specifiedEditor === "function") {
onErrorCallback = specifiedEditor;
specifiedEditor = undefined;
}
if (typeof srcRoot === "function") {
onErrorCallback = srcRoot;
srcRoot = undefined;
}
// 源文件根目录,未指定则默认为当前 node 进程目录
srcRoot = srcRoot || process.cwd();
// 返回一个 express 中间件函数
return function launchEditorMiddleware(req, res, next) {
// 解析请求路径,获取查询参数 file; 例如请求 /__open-in-editor?file=/path/to/xxx.vue
const { file } = url.parse(req.url, true).query || {};
// file 值若不存在,则响应状态 500 及结果信息
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();
}
};
}
launch-editor 源码
代码较多就不贴出来了,可以克隆仓库代码下来看,lauch-editor
包的入口文件为 index.js
,index.js
导出的函数是 launchEditor
,从它开始看。
launchEditor 函数
该函数函数内部主要做了以下事情:
-
解析文件路径、行列号,判断文件是否存在,不存在则直接 return 结束;
-
找出用户指定或者当前进程中运行的编辑器及对应的可执行程序,若没找到则打印报错信息并 return 结束(调用了
guessEditor
函数来查找编辑器); -
调用 node 子进程
child_process.spawn()
方法,执行对应编辑器的可执行程序打开目标文件。比如 vscode 编辑器:在 Windows 系统中对应的可执行程序为
Code.exe
在 macOS 系统中对应的可执行程序为
code
guessEditor 函数
该函数用于找出用户指定或者当前进程中运行的编辑器及对应的可执行程序,函数内部做了以下事情:
-
若指定了编辑器参数
specifiedEditor
,则调用 shell-quote 包解析参数shellQuote.parse(specifiedEditor)
返回解析结果数组; -
若没有指定编辑器参数,则在当前运行的所有进程中,找出支持的编辑器中的哪一个编辑器正在运行;
查找当前所有进程使用的命令,macOS 和 Linux 用
ps x
命令,windows 则用Get-CimInstance
命令(旧代码用的Get-Process
命令)。支持打开的编辑器见这里,代码里会维护编辑器在不同系统中对应的可执行程序。
-
如果没找到相应编辑器就判断环境变量
process.env.VISUAL
或者process.env.EDITOR
有没有配置(所以我们也可以使用环境变量来指定编辑器); -
如果以上方式都没找到,则返回
[null]
。
该功能在工具中的集成实现
看完以上源码,我们知道了这个功能核心其实跟 Vue Devtools 没啥关系,主要在我们的相关工具有没有集成该功能,下面介绍在几个工具中的集成实现。
Vue CLI
在运行 vue-cli-service serve
命令启动一个开发服务器时 (基于 webpack-dev-server) ,会在内部挂载一个路径为/__open-in-editor
的中间件 launch-editor-middleware
,当发生请求时就会去执行相应的中间件。源码地址
Vite
在运行 vite
(别名:vite dev
,vite serve
) 命令启动一个开发服务器时,也是会在内部注册挂载一个路径为/__open-in-editor
的中间件 launch-editor-middleware
,当发生请求时去执行相应的中间件。源码地址
总结
一句话总结 Open component in editor
功能的原理:“使用 nodejs
中的子进程child_process.spawn()
方法,执行编辑器对应的可执行程序打开目标文件。”,通过了解该功能实现原理,在以后我们需要类似功能的场景中,也可以集成进去。