简介
本文档记录了我参加 Vue-Devtools 源码共读的过程中的学习和思考。
- 本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
- 这是源码共读的第1期,链接:vue-devtools 组件可以打开编辑器。
参与目的:
- 探索源码,理解工作原理
- 学习和成长
- 提升技术水平
作为一名前端开发者,参与这次源码共读活动。这是一个很好的机会,与志同道合的开发者一起深入探索源码、理解工作原理,并在过程中学习和成长。
源码分析
源码准备
本文的代码仓库源自若川视野的GitHub文件
源码克隆
git clone https://github.com/lxchuan12/open-in-editor.git
安装vue-cli
npm install -g @vue/cli
或
yarn global add @vue/cli
安装依赖
npm install 或 yarn
注:此码Vue对应版本Vue版本为3.0
代码调试
在vue3-project/package.json中,点击调试按钮
选择 serve
vue-cli-service serve进行调试
搜索node_modules下文件
一般来说搜索不到node_modules下的文件,这设置一下即可搜到
操作如下:
- 搜索
'launch-editor-middleware'文件 - 点开三个点位置
- 找到排除的文件下面框框后面的小齿轮
- 点击文件打开文件,就可以看到
'launch-editor-middleware'了
浏览器调试工具
如果无法访问谷歌应用商店,可点击下方链接进行下载
vue2:点此下载安装对应的vue-devtools
如需其他安装chrome浏览器工具也可在此站搜索
1、vue-Devtools 简介
Vue-Devtools 的主要功能是为用户提供了一个可视化的界面,用于检查和调试 Vue.js 应用的状态。 它的工作原理主要包括两部分:首先,它会监听 Vue 实例的变化;然后,当变化发生时,它会把这些变化反映到用户界面上。 在实现上,Vue-Devtools 使用了许多先进的技术,包括 ES6、Webpack 和 Babel 等。
不管是学习还是了解一个内容或者是知识点,看文档都是比较重要的Vue-Devtools官方文档
浏览器调试
点击vue-devtools中的图中 open in editor 按钮
此时会发送一个请求,http://localhost:8080/__open-in-editor?file=src/App.vue
2、launch-editor-middleware
是一个用于在编辑器中打开组件的功能。它可以让你快速地在编辑器中查看和编辑代码,而不需要手动查找和打开文件。这对于开发人员来说是一个非常有用的功能,可以更快地完成工作。
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、srcRoot 和 onErrorCallback
// 这些参数分别用于指定要使用的编辑器、源代码根目录和错误处理回调函数
if (typeof specifiedEditor === 'function') {
onErrorCallback = specifiedEditor
specifiedEditor = undefined
}
// 这里使用了 ES6 语法中的解构赋值和默认参数。
if (typeof srcRoot === 'function') {
onErrorCallback = srcRoot
srcRoot = undefined
}
srcRoot = srcRoot || process.cwd()
// 定义中间件函数
// 在这个函数内部,首先对传入的参数进行了检查和默认值设置
// 然后,定义了一个名为 launchEditorMiddleware 的中间件函数,该函数接收三个参数:req、res 和 next
return function launchEditorMiddleware (req, res, next) {
// 解析请求中的查询参数
// 这些参数是 Express 框架中的标准中间件参数。
// 在这个函数内部,首先从请求 URL 中解析出了一个名为 file 的查询参数:
const { file } = url.parse(req.url, true).query || {}
// 如果 req.url 中包含 file 查询参数,则将其值赋给变量 file;否则,将 file 赋为 undefined
// 如果没有查询参数,返回错误响应
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()
}
}
}
在 launchEditorMiddleware 函数中,首先从请求 URL 中解析出了一个名为 file 的查询参数。如果没有找到这个参数,则向客户端发送一个错误响应。如果找到了 file 参数,则使用 launch 函数来打开指定的文件,并将响应发送给客户端。
总的来说,这个模块提供了一个简单的 API 来启动编辑器并打开指定的文件。它可以用于构建一些基于 Web 的开发工具或集成到其他应用程序中。
3、launch-editor
vue3-project/node_modules/launch-editor/index.js
// 接受三个参数:file、specifiedEditor 和 onErrorCallback
function launchEditor (file, specifiedEditor, onErrorCallback) {
// 这里使用了 ES6 语法中的解构赋值来提取出 parsed 对象中的 fileName、lineNumber 和 columnNumber 属性。
//这些属性分别表示要打开的文件名、行号和列号。
const parsed = parseFile(file)
let { fileName } = parsed
const { lineNumber, columnNumber } = parsed
// 这里检查传入的文件名是否指向一个存在的文件。如果文件不存在,则直接返回,不执行后续操作。
if (!fs.existsSync(fileName)) {
return
}
// 这里对传入的参数进行一些处理。
// 首先,检查第一个参数是否是函数。如果是函数,则将其赋给第三个参数,并将第一个参数设为 undefined。
//这是因为如果不传递第二个参数,则可以将错误处理回调作为第一个参数传递
if (typeof specifiedEditor === 'function') {
onErrorCallback = specifiedEditor
specifiedEditor = undefined
}
// 然后,定义了一个名为 wrapErrorCallback 的函数,用于包装原始的错误处理回调,并添加一些额外的逻辑。
onErrorCallback = wrapErrorCallback(onErrorCallback)
// 接着,调用一个名为 guessEditor 的函数来猜测要使用的编辑器,这个函数接受一个参数 specifiedEditor,表示用户指定的编辑器。
// 如果没有指定编辑器,则尝试从环境变量或配置文件中猜测编辑器,这里也没有提供 guessEditor 函数的具体实现。
// 然后,根据猜测到的编辑器启动编辑器并打开指定的文件,这部分代码没有在这里展示,因为后面还会讲解。
const [editor, ...args] = guessEditor(specifiedEditor)
if (!editor) {
onErrorCallback(fileName, null)
return
}
// 省略剩余部分...
}
总的来说,这个模块提供了一个简单的 API 来启动编辑器并打开指定的文件。它可以用于构建一些基于 Web 的开发工具或集成到其他应用程序中。
3.1、wrapErrorCallback 包裹错误函数回调
然后,定义了一个名为 wrapErrorCallback 的函数,用于包装原始的错误处理回调,并添加一些额外的逻辑
onErrorCallback = wrapErrorCallback(onErrorCallback)
ctrl+点击wrapErrorCallback可跳转至,这里可以理解为如果传递错误的回调函数,wrapErrorCallback会返回给一个新的函数,wrapErrorCallback执行的时候,再去执行以下代码
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)
}
}
3.2、guessEditor
确定当前正在使用的文本编辑器,如果能够成功确定,则返回一个包含该编辑器名称的对象;否则,它将返回null。
const [editor, ...args] = guessEditor(specifiedEditor)
if (!editor) {
onErrorCallback(fileName, null)
return
}
然后点击guessEditor进行一个跳转,跳转至launch-editor/guess.js,看以下代码
const path = require('path')
const shellQuote = require('shell-quote')
const childProcess = require('child_process')
// Map from full process name to binary that starts the process
// We can't just re-use full process name, because it will spawn a new instance
// of the app every time
const COMMON_EDITORS_OSX = require('./editor-info/osx')
const COMMON_EDITORS_LINUX = require('./editor-info/linux')
const COMMON_EDITORS_WIN = require('./editor-info/windows')
module.exports = 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]
}
这段代码定义了一个名为guessEditor的Node.js模块,其作用是在脚本运行时尝试确定当前正在使用的文本编辑器。 以下是此函数的主要部分:
- 如果参数
specifiedEditor存在,则直接解析并返回该字符串。 - 如果运行环境为MacOS或Linux,则通过执行“ps x”命令来检查当前运行的所有进程,并查找是否存在任何常见的文本编辑器(如Sublime Text、Atom等)。
- 如果运行环境为Windows,则通过执行Powershell命令
Get-Process来检查当前运行的所有进程,并查找是否存在任何常见的文本编辑器(如Visual Studio Code、Notepad++等)。 - 如果以上步骤都无法确定编辑器,则使用操作系统环境变量VISUAL和EDITOR(如果没有设置则使用默认值)。
最后,如果没有找到任何可用的编辑器,则返回空数组。 需要注意的是,由于guessEditor函数依赖于外部命令(例如ps x、Get-Process等),因此可能会受到操作系统版本、权限等问题的影响。此外,由于它只检查了几个常见的编辑器,所以可能无法正确识别某些不太常用的编辑器。
总结
- 通过这期共读,我对 Vue-Devtools 的工作原理有了更深入的理解。
- 虽然 Vue-Devtools 是一个非常强大的工具,但它的工作原理其实并不复杂。