关于前端:据说-99-的人不知道-vuedevtools-还能直接打开对应组件文件本文原理揭秘

48次阅读

共计 10394 个字符,预计需要花费 26 分钟才能阅读完成。

1. 前言

你好,我是若川,微信搜寻「若川视线」关注我,专一前端技术分享,一个愿景是帮忙 5 年内前端开阔视野走向前列的公众号。欢送加我微信ruochuan12,长期交流学习。

这是 学习源码整体架构系列 之 launch-editor 源码(第九篇)。学习源码整体架构系列文章(有哪些必看的 JS 库):jQuery、underscore、lodash、sentry、vuex、axios、koa、redux。整体架构这词语如同有点大,权且就算是源码整体构造吧,次要就是学习是代码整体构造,不深究其余不是主线的具体函数的实现。本篇文章学习的是理论仓库的代码。下一篇应该是《 学习 Vuex 4 源码整体架构,深刻了解其原理及 provide/inject 原理》。

本文仓库地址:git clone https://github.com/lxchuan12/open-in-editor.git,本文最佳浏览形式,克隆仓库本人入手调试,容易排汇消化。

要是有人说到怎么读源码,正在读文章的你能举荐我的源码系列文章,那真是无以为报啊

我的文章尽量写得让想看源码又不晓得怎么看的读者能看懂。我都是举荐应用 搭建环境断点调试源码学习 哪里不会点哪里 边调试边看,而不是硬看 。正所谓: 授人与鱼不如授人予渔

浏览本文后你将学到:

  1. 如何解决该性能报错问题
  2. 如何调试学习源码
  3. launch-editor-middleware、launch-editor 等实现原理

1.1 短时间找不到页面对应源文件的场景

不晓得你们有没有碰到这样的场景,关上你本人(或者你共事)开发的页面,却短时间难以找到对应的源文件。

这时你可能会想要是能有 点击页面按钮主动用编辑器关上对应文件 的性能,那该多好啊。

vue-devtools 提供了这样的性能,兴许你不晓得。我感觉很大一部分人都不晓得,因为感觉很多人都不罕用vue-devtools

你兴许会问,我不必 vue,我用react 有没有相似性能啊,有啊,请看 react-dev-inspector。你可能还会问,反对哪些编辑器呀,支流的 vscode、webstorm、atom、sublime 等都反对,更多能够看这个列表 Supported editors。

本文就是依据学习尤大写的 launch-editor 源码,本着 知其然,知其所以然 的主旨,探索 vue-devtools「在编辑器中关上组件」性能实现原理。

1.2 一句话简述其原理

code path/to/file

一句话简述原理:利用 nodejs 中的 child_process,执行了相似code path/to/file 命令,于是对应编辑器就关上了相应的文件,而对应的编辑器则是通过在过程中执行 ps xWindow 则用Get-Process)命令来查找的,当然也能够本人指定编辑器。

1.3 关上编辑器无奈关上组件的报错解决办法

而你真正用这个性能时,你可能碰到报错,说不能关上这个文件。

Could not open App.vue in the editor.

To specify an editor, specify the EDITOR env variable or add "editor" field to your Vue project config.

这里阐明下写这篇文章时用的是 Windows 电脑,在 Ubuntu 子系统下应用的终端工具。同时举荐我的文章应用 ohmyzsh 打造 windows、ubuntu、mac 零碎高效终端命令行工具,用过的都说好

解决办法也简略,就是这句英文的意思 。具体阐明编辑器,在环境变量中阐明指定编辑器。在vue 我的项目的根目录下,对应本文则是:vue3-project,增加 .env.delelopment 文件,其内容是EDITOR=code

# .env.development
# 当然,我的命令行终端曾经有了 code 这个命令。EDITOR=code

不必指定编辑器的对应门路(c/Users/lxchu/AppData/Local/Programs/Microsoft VS Code/bin/code),因为会报错。为什么会报错,因为我看了源码且试过。因为会被依据空格截断,变成c/Users/lxchu/AppData/Local/Programs/Microsoft,当然就报错了。

接下来咱们从源码角度探索「在编辑器中关上组件」性能的实现原理。

2. vue-devtools Open component in editor 文档

探索原理之前,先来看看 vue-devtools 官网文档。

vuejs/vue-devtools
文档

Open component in editor

To enable this feature, follow this guide.

这篇指南中写了在 Vue CLI 3 中是 开箱即用

Vue CLI 3 supports this feature out-of-the-box when running vue-cli-service serve.

也具体写了如何在 Webpack 下应用。

# 1. Import the package:
var openInEditor = require('launch-editor-middleware')
# 2. In the devServer option, register the /__open-in-editor HTTP route:
devServer: {before (app) {app.use('/__open-in-editor', openInEditor())
  }
}
# 3. The editor to launch is guessed. You can also specify the editor app with the editor option. See the supported editors list.
# 用哪个编辑器关上会主动猜想。你也能够具体指明编辑器。这里显示更多的反对编辑器列表
openInEditor('code')
# 4. You can now click on the name of the component in the Component inspector pane (if the devtools knows about its file source, a tooltip will appear).
# 如果 `vue-devtools` 开发者工具有提醒点击的组件的显示具体门路,那么你能够在编辑器关上。

同时也写了如何在 Node.js 中应用等。

Node.js

You can use the launch-editor package to setup an HTTP route with the /__open-in-editor path. It will receive file as an URL variable.

查看更多能够看这篇指南。

3. 环境筹备工作

相熟我的读者,都晓得我都是 举荐调试看源码 的,正所谓:哪里不会点哪里。而且调试个别都写得很具体,是心愿能帮忙到一部分人晓得如何看源码。于是我特意新建一个仓库 open-in-editor git clone https://github.com/lxchuan12/open-in-editor.git,便于大家克隆学习。

装置vue-cli

npm install -g @vue/cli
# OR
yarn global add @vue/cli
node -V
# v14.16.0
vue -V 
# @vue/cli 4.5.12
vue create vue3-project
# 这里抉择的是 vue3、vue2 也是一样的。# Please pick a preset: Default (Vue 3 Preview) ([Vue 3] babel, eslint)
npm install
# OR
yarn install

这里同时阐明下我的 vscode 版本。

code -v
1.55.2

前文提到的 Vue CLI 3开箱即用 Webpack应用办法。

vue3-project/package.json中有一个 debug 按钮。

抉择第一项,serve vue-cli-service serve

咱们来搜寻下 'launch-editor-middleware' 这个中间件,一般来说搜寻不到 node_modules 下的文件,须要设置下。当然也有个简略做法。就是「排除的文件」右侧旁边有个设置图标「应用“排查设置”与“疏忽文件”」,点击下。

其余的就不赘述了。能够看这篇知乎答复:vscode 怎么设置能够搜寻蕴含 node_modules 中的文件?

这时就搜到了 vue3-project/node_modules/@vue/cli-service/lib/commands/serve.js 中有应用这个中间件。

4. vue-devtools 开箱即用具体源码实现

接着咱们来看 Vue CLI 3开箱即用 具体源码实现。

// vue3-project/node_modules/@vue/cli-service/lib/commands/serve.js
// 46 行
const launchEditorMiddleware = require('launch-editor-middleware')
// 192 行
before (app, server) {
    // launch editor support.
    // this works with vue-devtools & @vue/cli-overlay
    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`
    )))
    // 省略若干代码...
}

点击 vue-devtools 中的时,会有一个申请,http://localhost:8080/__open-in-editor?file=src/App.vue,不出意外就会关上该组件啦。

接着咱们在 launchEditorMiddleware 的具体实现。

5. launch-editor-middleware

看源码时,先看调试截图。

launch-editor-middleware 中间件中作用在于最终是调用 launch-editor 关上文件。

// vue3-project/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 => 这里传递过去的则是 () => console.log() 函数
  // 所以和 onErrorCallback 切换下,把它赋值给谬误回调函数
  if (typeof specifiedEditor === 'function') {
    onErrorCallback = specifiedEditor
    specifiedEditor = undefined
  }

  // 如果第二个参数是函数,同样把它赋值给谬误回调函数
  // 这里传递过去的是 undefined
  if (typeof srcRoot === 'function') {
    onErrorCallback = srcRoot
    srcRoot = undefined
  }

  // srcRoot 是传递过去的参数,或者以后 node 过程的目录
  srcRoot = srcRoot || process.cwd()

  // 最初返回一个函数,express 中间件
  return function launchEditorMiddleware (req, res, next) {// 省略 ...}
}

上一段中,这种切换参数的写法,在很多源码中都很常见。为的是不便用户调用时传参。尽管是多个参数,但能够传一个或者两个

能够依据状况打上断点。比方这里我会在 launch(path.resolve(srcRoot, file), specifiedEditor, onErrorCallback) 打断点。

// vue3-project/node_modules/launch-editor-middleware/index.js
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 关上。launch(path.resolve(srcRoot, file), specifiedEditor, onErrorCallback)
      res.end()}
  }
}

6. launch-editor

跟着断点来看,走到了 launchEditor 函数。

// vue3-project/node_modules/launch-editor/index.js
function launchEditor (file, specifiedEditor, onErrorCallback) {
  // 解析出文件门路和行号列号等信息
  const parsed = parseFile(file)
  let {fileName} = parsed
  const {lineNumber, columnNumber} = parsed

  // 判断文件是否存在,不存在,间接返回。if (!fs.existsSync(fileName)) {return}
  // 所以和 onErrorCallback 切换下,把它赋值给谬误回调函数
  if (typeof specifiedEditor === 'function') {
    onErrorCallback = specifiedEditor
    specifiedEditor = undefined
  }
  // 包裹一层函数
  onErrorCallback = wrapErrorCallback(onErrorCallback)

  // 猜想以后过程运行的是哪个编辑器
  const [editor, ...args] = guessEditor(specifiedEditor)
  if (!editor) {onErrorCallback(fileName, null)
    return
  }
  // 省略残余局部,后文再讲述...
}

6.1 wrapErrorCallback 包裹谬误函数回调

onErrorCallback = wrapErrorCallback(onErrorCallback)

这段的代码,就是传递谬误回调函数,wrapErrorCallback 返回给一个新的函数,wrapErrorCallback 执行时,再去执行 onErrorCallback(cb)。

我置信读者敌人能看懂,我独自拿进去讲述,次要是因为 这种包裹函数的模式在很多源码里都很常见

这里也就是文章结尾终端谬误图 Could not open App.vue in the editor. 输入的代码地位。

// vue3-project/node_modules/launch-editor/index.js
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)
  }
}

6.2 guessEditor 猜想以后正在应用的编辑器

这个函数次要做了如下四件事件:

  1. 如果具体指明了编辑器,则解析下返回。
  2. 找出以后过程中哪一个编辑器正在运行。macOSLinuxps x 命令
    windows 则用 Get-Process 命令
  3. 如果都没找到就用 process.env.VISUAL或者process.env.EDITOR。这就是为啥结尾谬误提醒能够应用环境变量指定编辑器的起因。
  4. 最初还是没有找到就返回[null],则会报错。
const [editor, ...args] = guessEditor(specifiedEditor)
if (!editor) {onErrorCallback(fileName, null)
    return
}
// vue3-project/node_modules/launch-editor/guess.js
const shellQuote = require('shell-quote')

module.exports = function guessEditor (specifiedEditor) {
  // 如果指定了编辑器,则解析一下,这里没有传入。如果本人指定了门路。// 比方 c/Users/lxchu/AppData/Local/Programs/Microsoft VS Code/bin/code 
  //   会依据空格切割成 c/Users/lxchu/AppData/Local/Programs/Microsoft
  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 {//  省略...} 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 函数,咱们接着来看 launch-editor 残余局部。

6.3 launch-editor 残余局部

以下这段代码不必细看,调试的时候细看就行。

// vue3-project/node_modules/launch-editor/index.js
function launchEditor(){
  //  省略上局部...
  if (
    process.platform === 'linux' &&
    fileName.startsWith('/mnt/') &&
    /Microsoft/i.test(os.release())
  ) {
    // Assume WSL / "Bash on Ubuntu on Windows" is being used, and
    // that the file exists on the Windows file system.
    // `os.release()` is "4.4.0-43-Microsoft" in the current release
    // build of WSL, see: https://github.com/Microsoft/BashOnWindows/issues/423#issuecomment-221627364
    // When a Windows editor is specified, interop functionality can
    // handle the path translation, but only if a relative path is used.
    fileName = path.relative('', fileName)
  }

  if (lineNumber) {const extraArgs = getArgumentsForPosition(editor, fileName, lineNumber, columnNumber)
    args.push.apply(args, extraArgs)
  } else {args.push(fileName)
  }

  if (_childProcess && isTerminalEditor(editor)) {
    // There's an existing editor process already and it's attached
    // to the terminal, so go kill it. Otherwise two separate editor
    // instances attach to the stdin/stdout which gets confusing.
    _childProcess.kill('SIGKILL')
  }

  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'})
  }
  _childProcess.on('exit', function (errorCode) {
    _childProcess = null

    if (errorCode) {onErrorCallback(fileName, '(code' + errorCode + ')')
    }
  })

  _childProcess.on('error', function (error) {onErrorCallback(fileName, error.message)
  })
}

这一大段中,次要的就是 以下代码,用子过程模块。简略来说子过程模块有着执行命令的能力。

const childProcess = require('child_process')

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'})
}

行文至此,就根本靠近序幕了。

7. 总结

这里总结一下:首先文章结尾通过提出「短时间找不到页面对应源文件的场景」,并针对容易碰到的报错状况给出了解决方案。
其次,配置了环境跟着调试学习了 vue-devtools 中应用的尤大写的 yyx990803/launch-editor。

7.1 一句话简述其原理

咱们回顾下结尾的原理内容。

code path/to/file

一句话简述原理:利用 nodejs 中的 child_process,执行了相似code path/to/file 命令,于是对应编辑器就关上了相应的文件,而对应的编辑器则是通过在过程中执行 ps xWindow 则用Get-Process)命令来查找的,当然也能够本人指定编辑器。

最初还能做什么呢。

能够再看看 umijs/launch-editor 和 react-dev-utils/launchEditor.js。他们的代码简直相似。

也能够利用 Node.js 做一些进步开发效率等工作,同时能够学习 child_process 等模块。

也不要禁锢本人的思维,把前端禁锢在页面中,应该把视线拓宽

Node.js是咱们前端人摸索操作文件、操作网络等的好工具

如果读者敌人发现有不妥或可改善之处,再或者哪里没写明确的中央,欢送评论指出。另外感觉写得不错,对您有些许帮忙,能够点赞、评论、转发分享,也是对我的一种反对,万分感激。如果能关注我的前端公众号:「若川视线」,就更好啦。

对于

你好,我是若川,微信搜寻「若川视线」关注我,专一前端技术分享,一个愿景是帮忙 5 年内前端开阔视野走向前列的公众号。欢送加我微信 ruochuan12,长期交流学习。
次要有以下系列文章:学习源码整体架构系列、年度总结、JS 根底系列

参考链接

yyx990803/launch-editor

umijs/launch-editor

vuejs/vue-devtools

vue-devtools open-in-editor.md

“Open in editor” button doesn’t work in Win 10 with VSCode if installation path contains spaces

react-dev-utils/launchEditor.js

正文完
 0