【源码学习】第⑤期 | 解锁vue-devtools的"隐藏"技能

1,108 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情

前言

你知道vue-devtools除了看state还可以快速定位打开源文件吗?你知道vscode如何搜索node_modules中的文件吗?不知道的就一起实践一下,知道的就一起看原理温故知新吧!

环境准备

了解无论是组件库还是插件最好的途径就是看官方文档,先贴个vue-tools地址,按官方文档步骤安装vue-tools,个人是采用安装包的形式安装:

图片.png

vue-tools指南解读

  • 敲重点,能快速打开源文件的就是它了,日常工作中可能会被很多人忽略掉,但是定位bug文件真的很nice 图片.png
  • 按官方文档的说法,vue-cli3以上版本是直接支持在编辑器打开组件文件的,webpack呢就可能需要引入launch-editor-middleware

图片.png

代码准备

既然是源码学习,肯定是少不了调试代码啦,克隆一下下面的库,或者安装了vue-cli的直接vue create yourapp

git clone `https://github.com/lxchuan12/open-in-editor.git`
// or安装vue-cli
yarn global add @vue/cli
vue create vue3-project
  • 鉴于咱们要搜索的中间件launch-editor-middleware在node_modules,因此咱们要先设置一下vscode
  1. ctrl+,打开设置,也可以点齿轮
  2. 搜索search.exclude把node_module选项去掉或设为false,噔噔,就是它了: 图片.png
  3. 然后搜索定位launch-editor-middleware,准备开启调试模式! 图片.png

调试分析

  • 断点调试,launch-editor-middleware通过调用launch-editor达到打开组件文件命令 图片.png
  • 接着细看一下launch-editor的实现~
  • launch-editor函数总览
function launchEditor (file, specifiedEditor, onErrorCallback) {
  // 解析文件信息,包括名称、行列号
  const parsed = parseFile(file)

  let { fileName } = parsed

  const { lineNumber, columnNumber } = parsed

  // 文件不存在直接返回
  if (!fs.existsSync(fileName)) {

    return

  }
 // specifiedEditor若为函数直接赋给回调函数
  if (typeof specifiedEditor === 'function') {

    onErrorCallback = specifiedEditor

    specifiedEditor = undefined

  }
  // 处理错误回调
  onErrorCallback = wrapErrorCallback(onErrorCallback)
  // 判断编辑器类型
  const [editor, ...args] = guessEditor(specifiedEditor)
   // 不匹配就报错
  if (!editor) {

    onErrorCallback(fileName, null)

    return

  }

 ...根据行列号等信息执行命令
}
  • 源码大体思路:
    1. 解析文件信息,包括名称、行列号,判断文件是否存在
    2. 判断正在运行的编辑器,并返回相应信息
    3. 查找文件位置
    4. 利用子进程模块执行类似打开文件命令
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)

  }

}

源码分析:容错处理,打印报错信息

guessEditor 判断编辑器
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]

}

源码分析: 大体意思是若有传编辑器则解析,如果没有则找出正在运行的编辑器,并根据macOS系统、windows系统、linux系统的对应子进程通信返回编辑器信息,如果都没找到就利用process.env,否则返回null

关键代码
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' })

  }

源码分析: 使用nodejs的child_process模块创建子进程,达到执行打开代码所在文件的命令

总结

本次学习实践了利用vue-devtools打开文件,设置了vscode搜索node_module包,跟着若川大佬调试分析了一下launch-editor-middleware中间件,对vue-devtools打开组件文件的实现有了更进一步的了解,源码学习其实静下心来慢慢分析,好像也没有想象中的那么难,还有就是几期的源码学习下来发现学好node.js极其重要,毕竟站在巨人的肩膀上才看得更远!

参考文章