自定义插件实现Vue项目点击DOM定位VSCode代码

345 阅读5分钟

前言

近些年来,前端技术的飞速发展,大多数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 属性,可以通过它来监听发送给开发服务器的请求。

beforefunction (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文件夹目录,并将该目录添加到系统环境变量当中。

该项目借鉴了这位大佬的文章 文章链接