【源码学习】深入探索 vue-Devtools 源码的共读之旅【1】

306 阅读8分钟

简介

本文档记录了我参加 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中,点击调试按钮

image.png 选择 serve vue-cli-service serve进行调试

搜索node_modules下文件

一般来说搜索不到node_modules下的文件,这设置一下即可搜到
操作如下:

  1. 搜索'launch-editor-middleware'文件
  2. 点开三个点位置
  3. 找到排除的文件下面框框后面的小齿轮
  4. 点击文件打开文件,就可以看到'launch-editor-middleware'image.png

浏览器调试工具

如果无法访问谷歌应用商店,可点击下方链接进行下载
vue2点此下载安装对应的vue-devtools

vue3点此下载安装对应的vue-devtools

如需其他安装chrome浏览器工具也可在此站搜索

1、vue-Devtools 简介

Vue-Devtools 的主要功能是为用户提供了一个可视化的界面,用于检查和调试 Vue.js 应用的状态。 它的工作原理主要包括两部分:首先,它会监听 Vue 实例的变化;然后,当变化发生时,它会把这些变化反映到用户界面上。 在实现上,Vue-Devtools 使用了许多先进的技术,包括 ES6、Webpack 和 Babel 等。

不管是学习还是了解一个内容或者是知识点,看文档都是比较重要的Vue-Devtools官方文档

浏览器调试

点击vue-devtools中的图中 open in editor 按钮 image.png

此时会发送一个请求,http://localhost:8080/__open-in-editor?file=src/App.vue image.png

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 xGet-Process等),因此可能会受到操作系统版本、权限等问题的影响。此外,由于它只检查了几个常见的编辑器,所以可能无法正确识别某些不太常用的编辑器。

总结

  • 通过这期共读,我对 Vue-Devtools 的工作原理有了更深入的理解。
  • 虽然 Vue-Devtools 是一个非常强大的工具,但它的工作原理其实并不复杂。