作者:vivo 互联网大前端团队 - Youchen
一、背景
当初大型的 Vue 我的项目基本上都是多人合作开发,并且随着版本的迭代,Vue 我的项目中的组件数也会越来越多,如果此时让你负责不相熟的页面性能开发,甚至你才刚刚退出这个我的项目,那么怎么样能力疾速找到相干组件在整个我的项目代码中的文件地位呢?想必大家都有采取过以下这几种办法:
- 【搜类名】,在工程文件里搜寻页面 DOM 元素中的款式类名
- 【找路由】,依据页面链接找到 Vue 路由匹配的页面组件
- 【找人】,找到当初负责开发该页面的人询问对应的代码门路
以上几种办法的确可能帮忙咱们找到具体的代码文件门路,但都须要人工去搜寻,并不是很高效,那有没有其它更高效的形式呢?
答案是有的。Vue 官网就提供了一款 vue-devtools 插件,应用该插件就能主动在 VSCode 中关上对应页面组件的源代码文件,操作门路如下:
应用 vue-devtools 插件能够很好地进步咱们查找对应页面组件代码的效率,但只能定位到对应的组件代码,如果咱们想要间接找到页面上某个元素相干的具体代码地位,还须要在以后组件源代码中进行二次查找,并且每次都要先抉择组件,再点击关上按钮能力关上代码文件,不是特地快捷。
针对这个问题,咱们开发了轻量级的页面元素代码映射插件,应用该插件能够通过点击页面元素的形式,一键关上对应代码源文件,并且精准定位对应代码行,无需手动查找,可能极大地提高开发效率和体验,理论的应用成果如下:
二、实现原理
整个插件次要分为 3 个功能模块:client、server、add-code-location,client 端发送特定申请给 server 端,server 端接管到该申请后执行定位代码行命令,而 add-code-location 模块用于源码的转换。
2.1 client
client 端这里其实就是指浏览器,咱们在点击页面元素时,浏览器就会发送一个特定申请给 server 端,该申请信息蕴含了具体的代码文件门路和对应代码行号信息。
function openEditor(filePath) {
axios
.get(`${protocol}//${host}:${port}/code`, {
params: {filePath: `${filePath}`
}
})
.catch(error => {console.log(error)
})
}
而监听页面元素的点击事件则通过事件代理的形式全局监听,给 document 绑定了点击事件,监听键盘和鼠标点击组合事件来发动定位代码行申请,防止和页面原生的 click 事件发生冲突。
function openCode(e) {if (isShiftKey || isMetaKey || e.metaKey || e.shiftKey) {e.preventDefault()
const filePath = getFilePath(e.target)
openEditor(filePath)
}
...
}
2.2 server
server 端是指本地起的一个服务器,能够监听 client 端发送的特定申请,当接管到执行定位命令的申请时,执行 VSCode 关上代码文件命令,并定位到对应的代码行。
2.2.1 webpack devServer
如果是采纳 webpack 构建的我的项目,webpack 的 devServer 开发服务器曾经提供了一个 before 属性,能够通过它来监听发送给开发服务器的申请。
before: function (app) {app.get('/code', function (req, res) {if (req.query.filePath) {
// 执行 vscode 定位代码行命令
openCodeFile(req.query.filePath)
...
}
...
})
}
2.2.2 vite configureServer
如果是采纳 Vite 构建的我的项目,能够应用 Vite 插件来实现 server 端监听特定申请,Vite 插件扩大于 rollup 插件接口,并且在原有的根底上减少了一些特有的钩子函数,例如 configureServer 钩子,通过该钩子函数能够用于配置开发服务器来监听特定的申请。
const codeServer = () => ({
name: 'open-code-vite-server',
configureServer(server) {server.middlewares.use((req, res, next) => {
...
if (pathname == '/code') {
...
if (filePath) {openCodeFile(filePath) // 执行 vscode 定位代码行命令
...
}
res.end()}
...
})
}
})
2.2.3 执行 VSCode 定位命令
当 server 端监听到 client 端发送的特定申请后,接下来就是执行 VSCode 定位代码行命令。实际上,VSCode 编辑器是能够通过 code 命令来启动,并且能够相应应用一些命令行参数,例如:
“code –reuse-window” 或 ”code -r” 命令能够关上最初流动窗口的文件或文件夹;”code –goto” 或 ”code -g” 命令前面能够拼接具体文件门路和行列号,当应用 ”code -g file:line:column” 命令时能够关上某个文件并定位到具体的行列地位。
利用 VSCode 编辑器的这个个性,咱们就能实现主动定位代码行性能,对应的代码门路信息能够从 client 端发送的申请信息当中取得,再借助 node 的 child_process.exec 办法来执行 VSCode 定位代码行命令。
const child_process = require('child_process')
function openCodeFile(path) {let pathBefore = __dirname.substring(0, __dirname.search('node_modules'))
let filePath = pathBefore + path
child_process.exec(`code -r -g ${filePath}`)
}
另外,为了失常应用 VSCode 的 Code 命令,咱们须要确保增加 VSCode Code 命令到环境变量当中。Mac 零碎用户能够在 VSCode 界面应用 command+shift+ p 快捷键,而后搜寻 Code 并抉择 install ‘code’ command in path;Windows 用户能够找到 VSCode 装置地位的 bin 文件夹目录,并将该目录增加到零碎环境变量当中。
2.3 add-code-location
通过后面的介绍,大家应该理解了 client 端和 server 端的执行机制,并且在执行定位命令时须要获取到页面元素的代码门路,而具体的代码门路是以属性的形式绑定到了 DOM 元素上,这时候就须要用到 add-code-location 模块在编译时转换咱们的源码,并给 DOM 元素增加对应的代码门路属性。
整个源码转换解决流程如下:
2.3.1 获取文件门路
源码转换过程的第一步是获取代码文件的具体门路,对于 webpack 打包的我的项目来说,webpack loader 用来解决源码字符串再适合不过,loader 的上下文 this 对象蕴含一个 resourcePath 资源文件的门路属性,利用这个属性咱们很容易就能取得每个代码文件的具体门路。
module.exports = function (source) {const { resourcePath} = this
return sourceCodeChange(source, resourcePath)
}
对于 Vite 构建的我的项目来说,源码的转化操作也是通过插件来实现,Vite 插件有通用的钩子 transform,可用于转换已加载的模块内容,它接管两个参数,code 参数代表着源码字符串,id 参数是文件的全门路。
module.exports = function() {
return {
name: 'add-code-location',
transform(code, id) {
...
return sourceCodeChange(code, id)
}
}
}
2.3.2 计算代码行号
接着在遍历源码文件的过程中,须要解决对应 Vue 文件 template 模板中的代码,以“\n”宰割 template 模板局部字符串为数组,通过数组的索引即可精准失去每一行 html 标签的代码行号。
function codeLineTrack(str, resourcePath) {let lineList = str.split('\n')
let newList = []
lineList.forEach((item, index) => {newList.push(addLineAttr(item, index + 1, resourcePath)) // 增加地位属性,index+ 1 为具体的代码行号
})
return newList.join('\n')
}
2.3.3 增加地位属性
在获取到代码文件门路和代码行号当前,接下来就是对 Vue template 模板中宰割的每一行标签元素增加最终的地位属性。这里采纳的是正则替换的形式来增加地位属性,别离对每一行标签元素先正则匹配出所有元素的开始标签局部,例如 <div、<span、<img 等,而后将其正则替换成带有 code-location 属性的开始标签,对应的属性值就是后面获取的代码门路和对应标签的行号。
function addLineAttr(lineStr, line, resourcePath) {let reg = /<[\w-]+/g
let leftTagList = lineStr.match(reg)
if (leftTagList) {leftTagList = Array.from(new Set(leftTagList))
leftTagList.forEach(item => {if (item && item.indexOf('template') == -1) {let regx = new RegExp(`${item}`, 'g')
let location = `${item} code-location="${resourcePath}:${line}"`
lineStr = lineStr.replace(regx, location)
}
})
}
return lineStr
}
2.4 其余解决
2.4.1 源码相对路径
在给 DOM 元素增加对应的源码地位属性时,实际上采纳的是相对路径,这样能够使得 DOM 元素上的属性值更加简洁明了。node\_modules 文件夹通常是在我的项目的根目录下,而插件是以 npm 包的模式装置在 node\_modules 门路下,利用 node 的__dirname 变量能够取得以后模块的绝对路径,因而在源码转换过程中就能够获取到我的项目的根门路,从而就能取得 Vue 代码文件的相对路径。
let pathBefore = __dirname.substring(0, __dirname.search('node_modules'))
let filePath = filePath.substring(pathBefore.length) // vue 代码相对路径
在 server 端执行代码定位命令时,再将对应的代码相对路径拼接成残缺的绝对路径。
2.4.2 内部引入组件
add-code-location 尽管能够对本地的 Vue 文件进行代码门路信息的增加,然而对于内部引入或解析加载的组件目前是没有方法进行转换的,例如 element ui 组件,实际上的代码行信息只会增加在 element ui 组件的最外层。这时候 client 端在获取点击元素的代码门路时会做一个向上查找的解决,获取其父节点的代码门路,如果还是没有,会持续查找父节点的父节点,直到胜利获取代码门路。
function getFilePath(element) {if (!element || !element.getAttribute) return null
if (element.getAttribute('code-location')) {return element.getAttribute('code-location')
}
return getFilePath(element.parentNode)
}
这样就能够在点击后盾 element ui 搭建的页面元素时,也能胜利定位关上对应代码文件。
三、接入计划
通过后面的介绍,想必大家对页面元素代码映射插件原理有了清晰的理解,接下来就介绍一下在我的项目中的接入形式。接入形式其实很简略,并且能够抉择只在本地开发环境接入,不必放心对咱们的生产环境造成影响,放心使用。
3.1 webpcak 构建我的项目
对于 webpack 构建的我的项目来说,首先在构建配置项 vue.config.js 文件中配置一下 devServer 和 webpack loader,接着在 main.js 入口文件中初始化插件。
// vue.config.js
const openCodeServe = require('@vivo/vue-dev-code-link/server')
devServer: {
...
before: openCodeServe.before
},
if (!isProd) { // 本地开发环境
config.module
.rule('vue')
.test(/\.vue/)
.use('@vivo/vue-dev-code-link/add-location-loader')
.loader('@vivo/vue-dev-code-link/add-location-loader')
.end()}
// main.js
import openCodeClient from '@vivo/vue-dev-code-link/client'
if (process.env.NODE_ENV == 'development') {openCodeClient.init()
}
3.2 Vite 构建我的项目
Vite 构建我的项目接入该插件的计划和 webpack 构建我的项目基本上统一,惟一不一样的中央在于打包配置文件里引入的是两个 Vite 插件。
// vite.config.js
import openCodeServer from '@vivo/vue-dev-code-link/vite/server'
import addCodeLocation from '@vivo/vue-dev-code-link/vite/add-location'
export default defineConfig({
plugins: [openCodeServer(),
addCodeLocation()]
}
四、总结
以上就是对页面元素代码映射插件外围原理和接入计划的介绍,实现的形式充分利用了我的项目代码打包构建的流程,实际上无论是哪个打包工具,实质上都是对源码文件的转换解决,当咱们了解了打包工具的运行机制后,就能够做一些本人认为有意义的事。就拿页面元素代码映射插件来说,应用它能够极大晋升开发效率,不再须要破费工夫在寻找代码文件上,特地是页面数和组件数比拟多的我的项目,只需点击页面元素,即可一键关上对应代码文件,精准定位具体代码行,无需查找,哪里不会点哪里,so easy!