若川的解读文章
据说 99% 的人不知道 vue-devtools 还能直接打开对应组件文件?本文原理揭秘: juejin.cn/post/695934…
尤雨溪开发的 vue-devtools 如何安装,为何打开文件的功能鲜有人知?:juejin.cn/post/699428…
川哥最近组织了一次源码共读活动。每周读 200 行左右的源码。感兴趣可以关注他的公众号若川视野。
准备
- 把川哥的文章过一遍,明确目标
- 学习目标:了解vue-devtools打开组件实现原理
具体操作
使用的组件
主要launch-editor-middleware和launch-editor两个库的使用
launch-editor-middleware
launch-editor-middleware中间件的主要作用调用launch-editor
源码调用
// 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.
// 调用launchEditorMiddleware
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`
)))
// 省略若干代码...
}
const url = require('url')
const path = require('path')
const launch = require('launch-editor')
// 根据上面的代码看出specifiedEditor传入的是个返回值为console.log()的函数
module.exports = (specifiedEditor, srcRoot, onErrorCallback) => {
if (typeof specifiedEditor === 'function') {
onErrorCallback = specifiedEditor// 赋值给onErrorCallback
specifiedEditor = undefined
}
if (typeof srcRoot === 'function') {
onErrorCallback = srcRoot
srcRoot = undefined
}
// srcRoot 是传递过来的参数,或者当前node进程的目录
srcRoot = srcRoot || process.cwd()
// 返回中间件函数
return function launchEditorMiddleware (req, res, next) {
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()
}
}
}
打断点调试,点击左边红点,当点击文件进来时就停在断点处
launch-editor
但resolve解析完路径就会进入launchEditor函数
// vue3-project/node_modules/launch-editor/index.js
function launchEditor (file, specifiedEditor, onErrorCallback) {
// 解析file路径
const parsed = parseFile(file)
let { fileName } = parsed
const { lineNumber, columnNumber } = parsed
// 判断文件是否存在
if (!fs.existsSync(fileName)) {
return
}
if (typeof specifiedEditor === 'function') {
onErrorCallback = specifiedEditor
specifiedEditor = undefined
}
onErrorCallback = wrapErrorCallback(onErrorCallback)
// editor主要是获取编译器的参数
const [editor, ...args] = guessEditor(specifiedEditor)
if (!editor) {
onErrorCallback(fileName, null)
return
}
// ...
// child_process 是 Node.js 的一个模块,它提供了衍生子进程的能力,默认情况下,会在父 Node.js 进程和衍生的子进程之间建立 stdin、stdout 和 stderr 的管道。
if (process.platform === 'win32') {
// On Windows, launch the editor in a shell because spawn can only
// launch .exe files.
// 使用editor也就是code命令打开文件
_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)
})
}
guessEditor
这个函数主要做了如下四件事情:
- 如果具体指明了编辑器,则解析下返回。
- 找出当前进程中哪一个编辑器正在运行。macOS 和 Linux 用 ps x 命令
windows 则用 Get-Process 命令
- 如果都没找到就用 process.env.VISUAL或者process.env.EDITOR。这就是为啥开头错误提示可以使用环境变量指定编辑器的原因。
- 最后还是没有找到就返回[null],则会报错。
guessEditor解构赋值完的参数显示
function guessEditor (specifiedEditor) {
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 {
// 判断平台
if (process.platform === 'darwin') {
const output = childProcess.execSync('ps x').toString()
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]]
}
}
} else if (process.platform === 'win32') {
const output = childProcess
.execSync('powershell -Command "Get-Process | Select-Object Path"', {
stdio: ['pipe', 'pipe', 'ignore']
})
.toString()
const runningProcesses = output.split('\r\n')
for (let i = 0; i < runningProcesses.length; i++) {
// `Get-Process` sometimes returns empty lines
if (!runningProcesses[i]) {
continue
}
const fullProcessPath = runningProcesses[i].trim()
const shortProcessName = path.basename(fullProcessPath)
if (COMMON_EDITORS_WIN.indexOf(shortProcessName) !== -1) {
return [fullProcessPath]
}
}
} else if (process.platform === 'linux') {
// --no-heading No header line
// x List all processes owned by you
// -o comm Need only names column
const output = childProcess
.execSync('ps x --no-heading -o comm --sort=comm')
.toString()
const processNames = Object.keys(COMMON_EDITORS_LINUX)
for (let i = 0; i < processNames.length; i++) {
const processName = processNames[i]
if (output.indexOf(processName) !== -1) {
return [COMMON_EDITORS_LINUX[processName]]
}
}
}
} 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]
}
总结
- vue-devtool能够打开文件的原理是通过code命令执行的
code /path/file
- 使用的是node的childProcess来运行命令,学会了一些childProcess的应用
- 主要步骤分为两步:
1. 点击打开,进入launch中间件
2. 解析相关路径
3. 使用childProcess开启子进程,使用cmd执行code命令打开路径文件