前言
近些年来,前端技术的飞速发展,大多数Vue项目都在向更大、更多开发者参与的方向发展。随着项目中组件数量的增加,阅读并定位对应代码的时间成本变得越来越高,传统的路由、类名搜索法效率也变得越来越低,为解决这一问题,可以通过自定义插件实现Vue项目点击DOM定位VSCode代码。下面就插件的实现原理及方法做一个简单介绍。
自定义vue-code-click插件
插件主要分为三个部分:add-code-location、client、server。
-
add-code-location:add-code-location部分主要负责在编译阶段通过遍历源码,为DOM元素添加代码路径相关属性,以便client在点击页面元素时可以获得正确的源代码路径信息。
-
client:client部分即浏览器端,通过为页面(document)绑定点击事件,在监听到用户的shift按键与鼠标点击的组合事件时向服务器端(server)发送请求,该请求参数携带被点击元素的代码路径、代码行号等相关信息。
-
server:server即本地服务器,通过server监听client点击事件发送的请求,从而执行命令打开VSCode并且指到特定代码行。
实现方式
1.add-code-location
add-code-location主要包括:获取文件路径、计算代码行号、添加位置属性三部分操作。
1.1 获取文件路径
不同项目下,获取文件路径的方式并不相同,对于使用webpack打包的项目,可以通过loader上下文this对象中包含的resourcePath资源文件路径属性获取代码文件的具体路径。
module.exports = function (source) {
const { resourcePath } = this
// 通过webpack上下文this解构出resourcePath,作为参数传至转换函数
return sourceCodeChange(source, resourcePath)
}
对于使用Vite完成的项目,Vite插件有通用的钩子transform,可用于转换已加载的模块内容,它接收两个参数,code参数代表着源码字符串,id参数是文件的全路径.
import type { Plugin } from 'vite';
export default function vueCodeLinkPlugin(entry: string | string[]): Plugin {
...
return {
name: 'vite-plugin-vue-code-link',
enforce: 'pre',
apply: 'serve',
transform(code, id) {
if (!id.includes('/node_modules/') && id.endsWith('.vue')) {
...
}
},
}
}
1.2 计算代码行号
该部分操作主要将源码文件进行遍历,在遍历过程中将Vue文件中 template 模版中的代码使用 "\n" 进行分割,并按顺序放入数组中,这样以来,数组的索引值 + 1 即为 template 中各行代码的代码行号,以此获取每行代码的具体位置。
function codeLineTrack(str, resourcePath) {
let lineList = str.split('\n')
let newList = []
lineList.forEach((item, index) => {
// 添加位置属性:文件路径,代码所在 template 模版中所处的行号
newList.push(addLineAttr(item, index + 1, resourcePath))
})
return newList.join('\n')
}
1.3 添加位置属性
该部分对切割后的每一行标签元素添加位置属性。分别对每一行标签元素进行正则匹配,得到标签的开始部分,如:<div、<span、<img 等,然后将其替换成带有 code-location 属性的开始标签,如: <div code-location、<span code-location、<img code-location , code-location中包含该行代码对应的文件路径及行号。
function addLineAttr(lineStr, line, resourcePath) {
let reg = /<[\w-]+/g
// 通过正则表达式匹配标签起始部分
let leftTagList = lineStr.match(reg)
if (leftTagList) {
// 使用 Arra.from 方法将类数组转化为数组
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.client
function getFilePath(element: HTMLElement): string | null {
if (!element || element.tagName === 'body') return null
return element.dataset.codeLocation || getFilePath(element.parentElement!)
}
export default function codeLinkClient() {
document.onmousedown = (e) => {
if (e.shiftKey) {
e.preventDefault();
const filePath = getFilePath(e.target as HTMLElement);
fetch(`${window.origin}/code?filePath=${filePath}`);
}
}
}
3.server
3.1 webpack devSever
使用 webpack 构建的项目,在其devServer开发服务器提供了一个 before 属性,可以通过它来监听发送给开发服务器的请求。
before: function (app) {
app.get('/code', function (req, res) {
if (req.query.filePath) {
// 执行vscode定位代码行命令
openCodeFile(req.query.filePath)
...
}
...
})
}
3.2 vite configureServer
使用 Vite 构建的项目可以使用 Vite 插件来实现 server 端监听特定请求,如 configureServer 钩子,通过该钩子函数可以用于配置开发服务器来监听特定的请求。 configureServer vite官方文档
const codeServer = () => ({
name: 'open-code-vite-server',
configureServer(server) {
server.middlewares.use((req, res, next) => {
...
if (pathname == '/code') {
...
if (filePath) {
// 执行vscode定位代码行命令
openCodeFile(filePath)
...
}
res.end()
}
...
})
}
})
3.3 执行 VSCode 定位命令
当 server 端监听到 client 端发送的特定请求后,通过code命令执行 VSCode 代码定位
// 打开最后活动窗口的文件或文件夹命令
code --reuse-window || code -r
// 该命令后可拼接具体文件路径和行列号
code --goto || code -g
//使该用命令时可以打开某个文件并定位到具体的行列位置。
code -g file:line:column
对应的代码路径信息从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}`
)}
接入方案
webpack 构建项目
对于webpack构建的项目来说,首先在构建配置项vue.config.js文件中配置一下devServer和webpack loader,接着在main.js入口文件中初始化插件。
// vue.config.js
const openCodeServe = require('@vue-dev-code-link/server')
devServer: {
...
before: openCodeServe.before
},
if (!isProd) { // 本地开发环境
config.module
.rule('vue')
.test(/.vue/)
.use('vue-dev-code-link/add-location-loader')
.loader('vue-dev-code-link/add-location-loader')
.end()
}
// main.js
import openCodeClient from 'vue-dev-code-link/client
'if (process.env.NODE_ENV == 'development') {
openCodeClient.init()
}
Vite 构建项目
Vite 构建项目时在打包配置文件里引入两个 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()
]
}
使用
在使用时,需要确保添加VSCode Code命令到环境变量当中。Mac系统用户可以在VSCode界面使用command+shift+p快捷键,然后搜索Code 并选择install 'code' command in path;Windows用户可以找到VSCode安装位置的bin文件夹目录,并将该目录添加到系统环境变量当中。
该项目借鉴了这位大佬的文章 文章链接