Vue-DevTools源码阅读--打开组件文件

很感谢若川大佬组织的源码阅读小组活动

天天下班后逼本身学习学习html

如下为若川原文:juejin.cn/post/695934…vue

什么是Vue-DevTools?

做为一个Vue开发者(不是),天然少不了Chrome中的Vue调试插件。node

Vue-DevTools是一个能够在Chrome中进行Vue项目调试的工具,能够帮助开发者在使用Vue开发时,更清楚的了解目前页面中的组件、数据状况。git

目前该插件有两个版本,支持Vue3的Beta版本,和支持Vue2的版本。 image.pnggithub

要了解什么?

此次主要了解在新版本DevTools中支持了一个新特性:在选择对应的组件后,点击open-in-editor的按钮后,便可在编译器中打开对应的组件。web

image.png

实现原理:

主要经过launch-editor-middleware和launch-editor两个库实现了该功能,这两个库又经过调用node的process、child_process能力,建立一个node的子进程调起编译器打开选中的组件vue-cli

阅读前准备:

  1. 在Chrome中准备支持Vue3的最新版本插件(目前最新版本号6.0.0 beta 15)
  2. vue create 建立一个vue-cli3项目
  3. 准备一个编译器

开始调试:

Open in editor在Vue3中是一个开箱即用的功能shell

具体如何配置使用:Open component in editornpm

1.寻找入口,进行调试

1.1寻找入口

根据上述文档的项目引入配置,须要在编译器中搜索'/__open-in-editor',便可在node_modules 中定位到该方法,此时在此处打个点~json

image.png

再继续进入launchEditorMiddleware 发现这个中间件会调用launch-editor进行后续的打开编译器操做,此时能够在调用launch函数这行打上一个点~

image.png

1.2启动调试

以Vscode为例:

进入项目的package.json,能够看到在script属性上有一个“调试”或“debug”的按钮,点击后选择serve便可进入调试模式

image.png

在这里我踩了一个小坑(也是由于本身不够谨慎)

在npm i完成以后,先npm run serve在8080端口启动了项目,再点击调试

这会形成编译器再开启一个进程在8081端口启动项目,这也许会让你在后续调试时发现没法进入断点处

此时须要注意调试启动的项目端口是否与浏览器端口一致

接下来就进入到阅读源码部分~

开始阅读:

1.launchEditorMiddleware部分

在项目开始编译时,就会自动进入该部分代码。

我的理解在这部分代码中主要作了两件事:

1.函数重载,知足不一样开发传参需求

2.经过node.js获取当前进程所在的位置,为后续打开编译器作准备

// serve.js
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`
)))

//launch-editor-middleware/index.js
module.exports = (specifiedEditor, srcRoot, onErrorCallback) => {
  //这里对传入的第一个参数作一个判断,若是该参数为函数,则将这个参数与错误回调函数的值进行对调
  if (typeof specifiedEditor === 'function') {
      onErrorCallback = specifiedEditor
      specifiedEditor = undefined
    }
    //一样对传入的第二个参数也是作一样的判断
  if (typeof srcRoot === 'function') {
    onErrorCallback = srcRoot
    srcRoot = undefined
  }
    //第二个参数若是传入的是目录,则直接用
  //若是不是则调用node.js中process的能力,获取当前进程所在的位置
  srcRoot = srcRoot || process.cwd()
  return function launchEditorMiddleware (req, res, next) {
    //返回一个中间件
  }
}
复制代码

2 launch-editor部分

2.1执行前路径的判断

F12打开Vue-DevTools调试面板,选择一个组件,点击open-in-editor便可进入断点处

此时,若是切换到Chrome的Network栏时,会发现此时浏览器发送了一个请求: image.png

结合编译前的app.use('/__open-in-editor', launchEditorMiddleware(...)不难知道这是一个中间件的写法,当浏览器发送请求时,就会进入到接下来的代码逻辑中

module.exports = (specifiedEditor, srcRoot, onErrorCallback) => {
    // ....省略
  return function launchEditorMiddleware (req, res, next) {
    // 首先会读取路径中的file参数
    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-editor逻辑
      launch(path.resolve(srcRoot, file), specifiedEditor, onErrorCallback)
      res.end()
    }
  }
}
复制代码
2.2执行中最重要的一部分

进入到launchEditor函数后,也是该功能最重要的一部分

function launchEditor (file, specifiedEditor, onErrorCallback) {
  //2.2.1经过正则匹配的方式读取文件路径、行号、列号的信息并进行返回
  const parsed = parseFile(file)
  let { fileName } = parsed
  const { lineNumber, columnNumber } = parsed
    // 2.2.2调用node.js的方法,以同步的方式检测该路径是否存在,不存在就return结束
  if (!fs.existsSync(fileName)) {
    return
  }
    // 这里一样是一个函数重载的方法
  if (typeof specifiedEditor === 'function') {
    onErrorCallback = specifiedEditor
    specifiedEditor = undefined
  }
    // 2.2.3这里跟错误回调调用了一个方法,比较有意思
  onErrorCallback = wrapErrorCallback(onErrorCallback)

}
复制代码

2.2.3部分,采用了装饰器模式(感谢同组的纪年小姐姐的总结),原理是将要执行的逻辑包裹起来,先执行其余的须要处理的代码,再执行onErrorCallback的逻辑。

继续阅读函数~

function wrapErrorCallback (cb) {
  return (fileName, errorMessage) => {
    console.log()
    //这里先作了一个错误的输出,同时调用node.js中path的方法,提取出用"/"隔开的path最后一部份内容共
    //而且用了一个chalk库,能够改变控制台输出内容的颜色
    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)
  }
}
复制代码

若此时在这部分没有报错,则会继续进行接下来的流程。

2.2.4 此时会进入一个很“刺激”的猜想环节

//launch-editor/index.js
function launchEditor (file, specifiedEditor, onErrorCallback) {
  ...
    // 此时代码进入猜想函数
  const [editor, ...args] = guessEditor(specifiedEditor)
}

// launch-editor/guess.js
module.exports = function guessEditor (specifiedEditor) {
  // 第一步:判断有没有传入对应的shell命令
  if (specifiedEditor) {
    // 若是传入,利用shell-quote库解析shell命令
    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 {
    // 经过node.js中process中标识运行node.js进程的操做系统的方法获取当前的操做系统
    // 由于个人系统是MacOs,直接进入第一个猜想中
    if (process.platform === 'darwin') {
      // 此时调用了同步建立子进程的方法,这里会获取到目前的全部进程
      const output = childProcess.execSync('ps x').toString()
      // COMMON_EDITORS_OSX为一个map表,里面维护着MacOs下支持的编译器,以及对应的字段
      // 经过遍历的方式与当前系统中存在的编译器进行匹配
      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]]
        }
      }
    }
  // ... 不一样平台的我就省略了,原理相似
  // 最后还有一个兜底的方案
  // 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]
}
复制代码

2.2.5 猜想完以后的操做

function launchEditor (file, specifiedEditor, onErrorCallback) {
    // ...
  const [editor, ...args] = guessEditor(specifiedEditor)
  // 若是没有找到,就会报错
  if (!editor) {
    onErrorCallback(fileName, null)
    return
  }
    // 核心部分,根据不一样的系统状态,打开调起不一样的工具打开编译器
  // childProcess.spawn为异步衍生子进程,而且不会阻塞node.js的事件循环
  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 {
    // 由于是MacOs,所以调用Vscode,打开args地址(项目地址),而且子进程将使用父进程的标准输入输出。
    // 这块Node文档参考
    // http://nodejs.cn/api/child_process.html#child_process_child_process_spawn_command_args_options
    // 到这里,对应的组件文件就已经在编译器中被打开了
    _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)
  })
}
复制代码

总结

首先小小的表扬一下本身,终于克服了不会读不敢读源码的问题

🎉🎉🎉🎉🎉🎉🎉

之前以为源码都很难懂,框架也很难了解真正的原理。可是经过此次活动,小小的明白了一个工具中一个小模块的实现方法,颇有意思。

也很感谢若川大佬组织此次活动,辛苦了。

此次阅读的过程同时也发现了原来Node能够作不少事情,这也是以前没有了解过的知识点。

相关文档和资料:

Vue-DevTools:github.com/vuejs/devto…

尤大版本launch-editor:github.com/yyx990803/l…

Umijs/launch-editor:github.com/umijs/launc…

相关文章
相关标签/搜索