Chrome插件vue-devtools是如何把VSCode中对应的组件文件打开的

简介: 点击如图所示的图标,就是打开对应的组件文件。可能初次使用,点击好几次都没有响应,然后返回到VSCode编辑器后发现,如下图所示的错误,该怎么办呢?

image.png


前言


通过本文可以学到哪些知识?


  1. 如何在Chrome中安装vue-devtools插件
  2. 创建vue3 demo项目,并调试一键打开对应组件文件
  3. 通过关联图可清晰整个流程的大体脉络
  4. 通过源码解剖原理
  5. vite项目如何进行调试使用
  6. 我为什么看源码?以及看源码能学到什么呢?


1、安装Chrome浏览器扩展插件vue-devtools


1、打开Chrome浏览器,点击最右侧`三个点`
2、点击:更多工具
3、继续点击:扩展程序
4、然后点击:页面左上角`三横杠`
5、继续点击:打开Chrome 网上应用店
6、然后搜索框输入:`vue`
7、出现列表:选择'Vue.js devtools (https://vuejs.org)',点击此项进入
8、然后点击`添加至Chrome`
9、出现弹框后,点击`添加扩展程序`,执行完,便添加完毕


注意了,这个插件也就是在开发的时候可以使用哟。


2、创建项目并测试


// 更新脚手架
npm update -g @vue/cli
//查看版本
vue -V // 5.0.8,此版本下不需要配置任何便可调试打开对应组件文件
// 创建项目
vue create vue3-ts-demo
// 选择一系列的模版组合,创建完毕,并安装依赖
// 直接通过VSCode打开此项目
// 直接运行
npm run serve


浏览器打开页面


image.png


会发现浏览器调试右侧出现一个标签页vue


image.png


点击如图所示的图标,就是打开对应的组件文件。可能初次使用,点击好几次都没有响应,然后返回到VSCode编辑器后发现,如下图所示的错误,该怎么办呢?


image.png


我现在使用的mac电脑,在VSCode中尝试快捷键command + shift + p,如果是window电脑可能是 Ctrl + Shift + p,然后输入shell,选择安装code。如下图:


image.png


选中安装Code命令后,重新运行项目,然后再次点击如图箭头所示位置


image.png


就会跳转到VSCode并打开对应的组件文件


image.png


原理:其实就是通过code命令,进行打开对应的文件,你可以在项目根目录进行测试code package.json或者其他文件,稍等片刻便会打开该文件。


3、一图胜千言


其实调试完上面的代码以后,我才开始想这其中的原理到底是怎么实现的,心里还在嘀咕有那么神奇吗?


其实作为菜如鸡的我一开始还真想不明白,于是我不断的阅读源码,一步一步的揭开了神秘的面纱,后来才发现也不过尔尔。


image.png


这里需要搞清楚几个小问题,也是我在梳理的过程中花费时间比较长的疑惑。


1.vue-devTools也算是Chrome插件,是Chrome DevTools扩展,开发者工具里面的Tab扩展页


2.点击按钮发送的请求,然后通过webpack里的devServer进行请求拦截


3.请求拦截后,调用尤大大写的小插件 launch-editor


4.而launch-editor最终调用的child_process.spawn来执行命令


5.最终执行vscode中的code命令(我上面写过如何去安装)


4、从源码的角度解剖原理


4.1、准备工作


  • vue-devtools chrome浏览器插件已经安装


  • 过vuecli 创建好了vue3的项目

4.2、vue-devTools 插件中点击按钮


查看的代码当然就在devtools


按钮的模版代码


<VueButton
  v-if="fileIsPath"
  v-tooltip="{
    content: $t('ComponentInspector.openInEditor.tooltip', { file: data.file }),
    html: true
  }"
  icon-left="launch"
  class="flat icon-button"
  @click="openFile()"
/>
// tooltip显示的字符串
ComponentInspector: {
  openInEditor: {
    tooltip: 'Open <mono><<insert_drive_file>>{{file}}</mono> in editor',
  },
},


通过代码可以发现按钮事件openFile


function openFile () {
    if (!data.value) return
    openInEditor(data.value.file)
  }


可以看到里面又调用了openInEditor


export function openInEditor (file) {
  // Console display
  const fileName = file.replace(/\\/g, '\\\\')
  const src = `fetch('${SharedData.openInEditorHost}__open-in-editor?file=${encodeURI(file)}').then(response => {
    if (response.ok) {
      console.log('File ${fileName} opened in editor')
    } else {
      const msg = 'Opening component ${fileName} failed'
      const target = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : {}
      if (target.__VUE_DEVTOOLS_TOAST__) {
        target.__VUE_DEVTOOLS_TOAST__(msg, 'error')
      } else {
        console.log('%c' + msg, 'color:red')
      }
      console.log('Check the setup of your project, see https://devtools.vuejs.org/guide/open-in-editor.html')
    }
  })`
  if (isChrome) {
    target.chrome.devtools.inspectedWindow.eval(src)
  } else {
    // eslint-disable-next-line no-eval
    eval(src)
  }
}


其中主要的代码便是src变量的赋值,以及通过eval或者target.chrome.devtools.inspectedWindow.eval来执行src字符串。


src变量中的代码,不看错误处理的代码,其实也非常简单 就是一个fetch请求,其中变量SharedData.openInEditorHost就是一个/


fetch(`/__open-in-editor?file=${encodeURI(file)}`).then(response => {
    if (response.ok) {
      console.log('File ${fileName} opened in editor')
    } else {
      const msg = 'Opening component ${fileName} failed'
    }
  })


这里最重要的其实就是这个请求路径__open-in-editor


4.3、再来看我们创建的demo项目vue3-ts-demo


通过一个截图来看看代码位置


image.png


代码其实是在node_modules/@vue/cli-service../serve.js,也就是运行一下命令后执行的脚本代码


"serve": "vue-cli-service serve",


执行完上述命令,会将__open-in-editor请求地址注册到devServer,后续如果有请求过来,地址刚好一样,便会调用下面的launchEditorMiddleware,并传递了一个匿名函数。


const launchEditorMiddleware = require('launch-editor-middleware')
// .....省略很多暂时用不到的代码
devServer.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`
)))


4.4、中间件launch-editor-middleware


上面launchEditorMiddleware最终使用的便是该npm包,里面只有一个文件,代码如下所示


const url = require('url')
const path = require('path')
const launch = require('launch-editor')
module.exports = (specifiedEditor, srcRoot, onErrorCallback) => {
  // specifiedEditor 传递进来的cnsole.log匿名函数
  // srcRoot和onErrorCallback都为undefined
  if (typeof specifiedEditor === 'function') {
    onErrorCallback = specifiedEditor
    specifiedEditor = undefined
  }
  if (typeof srcRoot === 'function') {
    onErrorCallback = srcRoot
    srcRoot = undefined
  }
  // 获得当前执行node命令时候的文件夹目录名 
  // /Users/user/Desktop/aehyok/github/demo/vue3-ts-demo
  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(path.resolve(srcRoot, file), specifiedEditor, onErrorCallback)
      res.end()
    }
  }
}


这里初始化的时候,并不会执行return function launchEditorMiddleware。等到下次有请求为__open-in-editor的时候,才会真正的调用 launchEditorMiddleware


4.5、点击open xxx path in editor


image.png


看顶部引用


const launch = require('launch-editor')


4.6、最后的执行函数 launch-editor


然后开始执行如下函数


function launchEditor (file, specifiedEditor, onErrorCallback) {
  // 最重要的是解析出文件名 
  const parsed = parseFile(file)
  let { fileName } = parsed
  const { lineNumber, columnNumber } = parsed
  // 判断次文件是否存在
  if (!fs.existsSync(fileName)) {
    return
  }
  // 根据guessEditor推断出当前使用的代码编辑器
  const [editor, ...args] = guessEditor(specifiedEditor)
  // 如果editor,则会报错
  if (!editor) {
    onErrorCallback(fileName, null)
    // 其中打印错误就是之前上面的截图
    // colors.red('Could not open ' + path.basename(fileName) + ' in the editor.')
    return
  }
  //... 中间忽略很多的代码
  // window系统下调用cmd命令行工具执行code
  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 {
    // 其他平台则直接使用nodejs中childProcess模块的spawn,来执行code命令行
    _childProcess = childProcess.spawn(editor, args, { stdio: 'inherit' })
  }
}


至此整个调试的大致过程已经清晰明了了,不知道你看懂了没,如果没看懂,一定是我没表述清楚,不过整个思路我已经了然于胸了。


5、通过vite 创建测试项目


// 创建项目
pnpm create vite
//项目名称
vite3-vue3-demo
// 选择框架
vue
// 选择一个变体
vue-ts
//OK,项目创建完毕,安装依赖
pnpm i
// 运行项目
pnpm dev


这里我试过好几个办法都没有解决去给process.env.EDITOR赋值,可能是我打开方式不对,如果有看我文章有解决方案的,可以告知菜鸡一下,非常感谢。


我这里就是直接修改node_modules中launch-editor中的guess.js


// 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]


将最后的return [null]改为 return ['code'],其实就是上面的process.env.EDITOR不知道如何赋值,我尝试了两种方式去修改,都没成功,这其中原因我还没找出来???


6、总结


通过调试源码发现,只要仔细一点稍微花点时间,原来也能看懂尤大写的代码,没有想象中的那么难,而且感觉逻辑非常清晰,阅读起来很优雅。所以大家如果有想看源码,或者参加若川源码共读活动的,一定要大胆一些,不要怂,事情真的没有那么难。


有点目的性的阅读源码似乎更高效,这样针对性很强,不会大一统所有的源码都会过一下,时间一下子就过去了,每次带着一个小问题去看源码或许也是若川大佬的精髓所指。


通过阅读源码,就是把看不懂的函数方法关键字等,不断的查漏补缺。或者在这里的用法或者写法不一样,等等各种超乎你想象的用法、场景...,收获真的是非常大,尤其是看完后再写一篇小文总结出来,真的就比读一遍别人写的收获要多好几倍的感觉。


所以如果你还在犹豫自己看不懂,自己行不行等等借口,作为一个前端还不到两年经验的人告诉你,加加油相信自己,你完全可以的。最后一定要行动起来。


写的文章不要总想着有好多好多的浏览量,又或者有几十几百上千的点赞,我的要求不高,首先是自己真的学到了,思路真的整理总结出来了,那么自己在遇到同样问题的时候回看也是非常的方便的。最后送给有需要的人士,哪怕只有一位同仁看了我的文章,也就证明了它的价值所在。


谢谢在看的朋友,欢迎你一起来参加源码共读活动,学习真是一件快乐的事情,尤其是真的学到了,而不是浑浑噩噩的在混日子,当然自己之前浪费过太多的时间,假以时日,我定会.......

目录
相关文章
|
30天前
|
JavaScript
在 Vue 中处理组件选项与 Mixin 选项冲突的详细解决方案
【10月更文挑战第18天】通过以上的分析和探讨,相信你对在 Vue 中使用 Mixin 时遇到组件选项与 Mixin 选项冲突的解决方法有了更深入的理解。在实际开发中,要根据具体情况灵活选择合适的解决方案,以确保代码的质量和可维护性。
85 7
|
12天前
|
存储 JavaScript 开发者
Vue 组件间通信的最佳实践
本文总结了 Vue.js 中组件间通信的多种方法,包括 props、事件、Vuex 状态管理等,帮助开发者选择最适合项目需求的通信方式,提高开发效率和代码可维护性。
|
12天前
|
存储 JavaScript
Vue 组件间如何通信
Vue组件间通信是指在Vue应用中,不同组件之间传递数据和事件的方法。常用的方式有:props、自定义事件、$emit、$attrs、$refs、provide/inject、Vuex等。掌握这些方法可以实现父子组件、兄弟组件及跨级组件间的高效通信。
|
29天前
|
缓存 JavaScript UED
Vue 的动态组件与 keep-alive
【10月更文挑战第19天】总的来说,动态组件和 `keep-alive` 是 Vue.js 中非常实用的特性,它们为我们提供了更灵活和高效的组件管理方式,使我们能够更好地构建复杂的应用界面。深入理解和掌握它们,以便在实际开发中能够充分发挥它们的优势,提升我们的开发效率和应用性能。
45 18
|
24天前
|
缓存 JavaScript UED
Vue 中实现组件的懒加载
【10月更文挑战第23天】组件的懒加载是 Vue 应用中提高性能的重要手段之一。通过合理运用动态导入、路由配置等方式,可以实现组件的按需加载,减少资源浪费,提高应用的响应速度和用户体验。在实际应用中,需要根据具体情况选择合适的懒加载方式,并结合性能优化的其他措施,以打造更高效、更优质的 Vue 应用。
|
28天前
|
前端开发 UED
vue3知识点:Suspense组件
vue3知识点:Suspense组件
31 4
|
27天前
|
JavaScript 前端开发 测试技术
组件化开发:创建可重用的Vue组件
【10月更文挑战第21天】组件化开发:创建可重用的Vue组件
25 1
|
28天前
|
JavaScript 前端开发 Java
《vue3第五章》新的组件,包含:Fragment、Teleport、Suspense
《vue3第五章》新的组件,包含:Fragment、Teleport、Suspense
32 2
|
28天前
|
Java
vue3知识点:Teleport组件
vue3知识点:Teleport组件
27 1
|
1月前
|
存储 JavaScript
Vue 组件间通信的方式有哪些?
Vue组件间通信主要通过Props、Events、Provide/Inject、Vuex(状态管理)、Ref、Event Bus等实现,支持父子组件及跨级组件间的高效数据传递与状态共享。