学习 launch-editor 源码整体架构,探究 vue-devtools「在编辑器中打开组件」功能实现原理...

1. 前言

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

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

本文仓库地址[2]git clone https://github.com/lxchuan12/open-in-editor.git,本文最佳阅读方式,克隆仓库自己动手调试,容易吸收消化。

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

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

阅读本文后你将学到:

  1. 如何解决该功能报错问题

  2. 如何调试学习源码

  3. launch-editor 等实现原理

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

不知道你们有没有碰到这样的场景,打开你自己(或者你同事)开发的页面,却短时间难以找到对应的源文件。

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

vue-devtools提供了这样的功能,也许你不知道。我觉得很大一部分人都不知道,因为感觉很多人都不常用vue-devtools

open-in-editor

你也许会问,我不用vue,我用react有没有类似功能啊,有啊,请看react-dev-inspector[3]

本文就是根据学习尤大写的 launch-editor[4] 源码,本着知其然,知其所以然的宗旨,探究 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[5]文档

Open component in editor
To enable this feature, follow this guide[6].

这篇指南中写了在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[7] package to setup an HTTP route with the /__open-in-editor path. It will receive file as an URL variable.

查看更多可以看这篇指南[8]

3. 环境准备工作

熟悉我的读者,都知道我都是推荐调试看源码的,正所谓:哪里不会点哪里。而且调试一般都写得很详细,是希望能帮助到一部分人知道如何看源码。于是我特意新建一个仓库open-in-editor[9] 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按钮。

debug示意图

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

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

其他的就不赘述了。可以看这篇知乎回答:vscode怎么设置可以搜索包含node_modules中的文件?[10]

这时就搜到了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-overlayapp.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,不出意外就会打开该组件啦。

open src/App.vue in editor

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

5. launch-editor-middleware

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

debug-launch

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 = specifiedEditorspecifiedEditor = undefined}// 如果第二个参数是函数,同样把它赋值给错误回调函数// 这里传递过来的是undefinedif (typeof srcRoot === 'function') {onErrorCallback = srcRootsrcRoot = 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 = 500res.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 } = parsedconst { lineNumber, columnNumber } = parsed// 判断文件是否存在,不存在,直接返回。if (!fs.existsSync(fileName)) {return}// 所以和 onErrorCallback 切换下,把它赋值给错误回调函数if (typeof specifiedEditor === 'function') {onErrorCallback = specifiedEditorspecifiedEditor = undefined}// 包裹一层函数onErrorCallback = wrapErrorCallback(onErrorCallback)// 猜测当前进程运行的是哪个编辑器const [editor, ...args] = guessEditor(specifiedEditor)if (!editor) {onErrorCallback(fileName, null)return}// 省略剩余部分,后文再讲述...
}

6.1 wrapErrorCallback 包裹错误函数回调

onErrorCallback = wrapErrorCallback(onErrorCallback)

这段的代码,我相信读者朋友能看懂,我单独拿出来讲述,主要是因为这种包裹函数的形式在很多源码里都很常见。这里也就是文章开头终端错误图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/Microsoftif (specifiedEditor) {return shellQuote.parse(specifiedEditor)}// We can find out which editor is currently running by:// `ps x` on macOS and Linux// `Get-Process` on Windowstry {//  省略...} catch (error) {// Ignore...}// Last resort, use old skool env varsif (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 = nullif (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[11]

7.1 一句话简述其原理

我们回顾下开头的原理内容。

code path/to/file

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

最后还能做什么呢。

可以再看看 umijs/launch-editor[12] 和 react-dev-utils/launchEditor.js[13] 。他们的代码几乎类似。

也可以利用Node.js做一些提高开发效率等工作,同时可以学习child_process等模块。

参考资料

[1]

若川: https://lxchuan12.gitee.io

[2]

本文仓库地址: https://github.com/lxchuan12/open-in-editor.git


其他引用略,具体可以点击阅读原文查看。


最近组建了一个江西人的前端交流群,如果你也是江西人可以加我微信 ruochuan12 拉你进群。


················· 若川出品 ·················

今日话题

五一结束啦,我在假期最后一天耗时一天把这篇文章写完,这是今年第一篇技术文,惭愧惭愧。接下来会发布第二篇《学习 Vuex 4 源码整体架构,深入理解其原理及provide/inject原理》。欢迎在下方留言~  欢迎分享、收藏、点赞、在看我的公众号文章~

一个愿景是帮助5年内前端人走向前列的公众号

可加我个人微信 ruochuan12,长期交流学习

推荐阅读

我在阿里招前端,我该怎么帮你?(现在还能加我进模拟面试群)

若川知乎问答:2年前端经验,做的项目没什么技术含量,怎么办?

点击方卡片关注我、加个星标,或者查看源码等系列文章。
学习源码整体架构系列、年度总结、JS基础系列

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/275931.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

:传递给 left 或 substring 函数的长度参数无效。_Java函数式编码结构-好程序员

好程序员Java培训分享Java函数式编码结构,本文将探讨三种下一代JVM语言:Groovy、Scala和Clojure,比较并对比新的功能和范例,让Java开发人员对自己近期的未来发展有大体的认识,下面我们一起来看一下吧。当垃圾回收成为主…

跨库一致性_设计跨平台的一致性

跨库一致性I offended an Apple employee the other day when I was checking out the new iPad Pro and I told him that I was an Android phone user. Eyes rolled, jokes were made, and we agreed to disagree.前几天,我在检阅新iPad Pro时冒犯了一名苹果员工&…

漫画 | 一个NB互联网项目的上线过程…

大家好,我是若川(点这里加我微信 ruochuan12,长期交流学习)。今天虽然是周六,但还是要上班,所以就推荐一篇比较轻松的漫画。点击下方卡片关注我、加个星标,或者查看源码等系列文章。学习源码整体…

胖子脸:库珀·布莱克100年

In 16th century Europe, roman typefaces were the first to surpass blackletter as the preferred choice for expressing emphasis in print. True bold weight roman letters didn’t appear until the 19th century, which critics quickly coined “Fat Faces” due to …

C语言中的布尔值

C语言的布尔类型在C语言标准(C89)没有定义布尔类型,所以C语言判断真假时以0为假,非0为真。所以我们通常使用逻辑变量的做法: //定义一个int类型变量,当变量值为0时表示false,值为1时表示trueint flag;flag 0;//......…

c++ explicit关键字_聊一聊 C++的特性 explicit 匿名空间

聊一聊 C的特性 explicit && 匿名空间explicit关键字首先看一下explicit的作用:explicit 是避免构造函数的参数自动转换为类对象的标识符,平时代码中并不是经常用到,但是,有时候就是因为这个,会造成一定的BUG出…

谷歌浏览器那些有趣的隐藏功能

大家好,我是若川(点这里加我微信 ruochuan12,长期交流学习)。今天推荐一篇实用文章。文末有抽奖。点击下方卡片关注我、加个星标,或者查看源码等系列文章。学习源码整体架构系列、年度总结、JS基础系列很多小伙伴说还是…

yii mysql_Yii2框架操作数据库的方法分析【以mysql为例】

本文实例讲述了Yii2框架操作数据库的方法。分享给大家供大家参考,具体如下:准备数据库DROP TABLE IF EXISTS pre_user;CREATE TABLE pre_user(id int(11) AUTO_INCREMENT PRIMARY KEY,username varchar(255) NOT NULL,password varchar(32) NOT NULL DEF…

C++接口注意

1. 用Record接口,要注意 Packed的区别 2. cdecl和stdcall的区别 3. C导出的函数建议用C格式stdcall导出,使用Def文件定义名称 4. 用VS写的API dll要注意是否引用了MFC的DLL,否则会使LoadLibrary失败,并GetLastError后返回14001 Ap…

Vue 3.1.0 的 beta 版发布

大家好,我是若川(点这里加我微信 ruochuan12,长期交流学习)。昨晚尤大视频号直播说到vue 3.1.0 beta版发布了,今天分享这篇文章。也有小伙伴可能注意到了昨晚我一直在送礼物。点击下方卡片关注我、加个星标&#xff0c…

设计模式练习_设计练习是邪恶的

设计模式练习It was the final round of interviews. Or, so the candidate thought.这是采访的最后一轮。 或者,所以候选人认为。 She’d spent all day interviewing in our office. As the final interviewer, I walked her out the building. She seemed confi…

morningcat2018 LearningDocs

2019独角兽企业重金招聘Python工程师标准>>> LearningDocs 学习资料与文档 JCP(Java Community Process ,Java社区进程 ) https://www.jcp.org/en/home/index JSR(Java Specification Requests,Java规范请求…

据说 99% 的人不知道 vue-devtools 还能直接打开对应组件文件?本文原理揭秘

1. 前言你好,我是若川[1],微信搜索「若川视野」关注我,专注前端技术分享,一个愿景是帮助5年内前端开阔视野走向前列的公众号。欢迎加我微信ruochuan12,长期交流学习。这是学习源码整体架构系列 之 launch-editor 源码&…

mysql 存储 事务_MYSQL 可以在存储过程里实现事务控制吗

展开全部6.7 MySQL 事务与锁定命令6.7.1 BEGIN/COMMIT/ROLLBACK 句法缺省的,MySQL 运行在 autocommit 模式。这就意味着,当你执行完一e69da5e887aa62616964757a686964616f31333361326265个更新时,MySQL 将立刻将更新存储到磁盘上。如果你使用…

如何忽略证书继续访问_前5个最容易被忽视的可访问性问题

如何忽略证书继续访问Accessibility is quickly becoming one of the most important aspects of the way we use the web, if not the most important. Just between 2017 and 2018, the number of federal court cases regarding web accessibility nearly tripled, signifyi…

作为前端开发,如何高效学习 TypeScript

大家好,我是若川。有朋友跟我说最近面试前端候选人,问到关于 JavaScript 的一些少见误区问题,候选人很多都没回答上来,他很诧异,一个从国际大厂出来的面试者,竟然对 JavaScript 的一些误区问题都不了解。他…

figma下载_对于这10家公司,Figma是迈向新高度的起点

figma下载Hey everyone! In this post, we are highlighting 10 companies for which the use of Figma has become the starting point on the path to new heights. These are the use cases of problems and their solutions, where Figma played a decisive role.嘿大家&am…

sql server(常用)

普通用法 //生成 uuid 并转为小写 select LOWER(SUBSTRING(uuid,1,8)-SUBSTRING(uuid,10,4)-SUBSTRING(uuid,15,4)-SUBSTRING(uuid,20,4)-SUBSTRING(uuid,25,12)) from (select cast(NEWID() as varchar(36)) as uuid) s //ea52a7bb-a2aa-44b8-be28-5ebc64defcf9//获取时分秒…

TypeScript 原来可以这么香?!

先问一个问题,JavaScript有几种数据类型?number、string、boolean、null、undefined、symbol、bigint、object其中 bigint 是 ES2020 新增的数据类型,而早在 TS3.2 时便成为 TS 的标准,其实还有好多 ES 标准是 TS 率先提出的&…

java8新特性stream深入解析

2019独角兽企业重金招聘Python工程师标准>>> 继续java8源码的发烧热,越看越是有充实的感觉。 数据时代下的产物 Java顺应时代的发展推出的高效处理大量数据能力的api,它专注于对集合对象进行各种非常便利、高效的聚合操作,借助于同…