如何在 Vue 項目中涣楷,通過點擊 DOM 自動定位VSCode中的代碼行?

作者:vivo 互聯(lián)網(wǎng)大前端團隊- Youchen

一抗碰、背景

現(xiàn)在大型的 Vue項目基本上都是多人協(xié)作開發(fā)狮斗,并且隨著版本的迭代,Vue 項目中的組件數(shù)也會越來越多弧蝇,如果此時讓你負(fù)責(zé)不熟悉的頁面功能開發(fā)碳褒,甚至你才剛剛加入這個項目,那么怎么樣才能快速找到相關(guān)組件在整個項目代碼中的文件位置呢看疗?想必大家都有采取過以下這幾種方法:

  • 【搜類名】沙峻,在工程文件里搜索頁面 DOM元素中的樣式類名
  • 【找路由】,根據(jù)頁面鏈接找到Vue路由匹配的頁面組件
  • 【找人】两芳,找到當(dāng)初負(fù)責(zé)開發(fā)該頁面的人詢問對應(yīng)的代碼路徑

以上幾種方法確實能夠幫助我們找到具體的代碼文件路徑摔寨,但都需要人工去搜索,并不是很高效怖辆,那有沒有其它更高效的方式呢是复?

答案是有的。Vue官方就提供了一款 vue-devtools 插件竖螃,使用該插件就能自動在 VSCode 中打開對應(yīng)頁面組件的源代碼文件淑廊,操作路徑如下:

使用vue-devtools插件可以很好地提高我們查找對應(yīng)頁面組件代碼的效率,但只能定位到對應(yīng)的組件代碼特咆,如果我們想要直接找到頁面上某個元素相關(guān)的具體代碼位置季惩,還需要在當(dāng)前組件源代碼中進行二次查找,并且每次都要先選擇組件坚弱,再點擊打開按鈕才能打開代碼文件蜀备,不是特別快捷。

針對這個問題荒叶,我們開發(fā)了輕量級的頁面元素代碼映射插件碾阁,使用該插件可以通過點擊頁面元素的方式,一鍵打開對應(yīng)代碼源文件些楣,并且精準(zhǔn)定位對應(yīng)代碼行脂凶,無需手動查找宪睹,能夠極大地提高開發(fā)效率和體驗,實際的使用效果如下:

二蚕钦、實現(xiàn)原理

整個插件主要分為3個功能模塊:client亭病、server、add-code-location嘶居,client端發(fā)送特定請求給server端罪帖,server端接收到該請求后執(zhí)行定位代碼行命令,而add-code-location模塊用于源碼的轉(zhuǎn)換邮屁。


2.1 client

client端這里其實就是指瀏覽器整袁,我們在點擊頁面元素時,瀏覽器就會發(fā)送一個特定請求給server端佑吝,該請求信息包含了具體的代碼文件路徑和對應(yīng)代碼行號信息坐昙。

function openEditor(filePath) {
  axios
    .get(`${protocol}//${host}:${port}/code`, {
      params: {
        filePath: `${filePath}`
      }
    })
    .catch(error => {
      console.log(error)
    })
}

而監(jiān)聽頁面元素的點擊事件則通過事件代理的方式全局監(jiān)聽,給document綁定了點擊事件芋忿,監(jiān)聽鍵盤和鼠標(biāo)點擊組合事件來發(fā)起定位代碼行請求炸客,避免和頁面原生的click事件發(fā)生沖突。

function openCode(e) {
  if (isShiftKey || isMetaKey || e.metaKey || e.shiftKey) {
    e.preventDefault()
    const filePath = getFilePath(e.target)
    openEditor(filePath)
  }
  ...
}

2.2 server

server端是指本地起的一個服務(wù)器戈钢,可以監(jiān)聽client端發(fā)送的特定請求痹仙,當(dāng)接收到執(zhí)行定位命令的請求時,執(zhí)行VSCode打開代碼文件命令殉了,并定位到對應(yīng)的代碼行蝶溶。

2.2.1 webpack devServer

如果是采用webpack構(gòu)建的項目,webpack的devServer開發(fā)服務(wù)器已經(jīng)提供了一個before屬性宣渗,可以通過它來監(jiān)聽發(fā)送給開發(fā)服務(wù)器的請求。

before: function (app) {
  app.get('/code', function (req, res) {
    if (req.query.filePath) {
      // 執(zhí)行vscode定位代碼行命令
      openCodeFile(req.query.filePath)
      ...
    }
    ...
  })
}

2.2.2 vite configureServer

如果是采用Vite構(gòu)建的項目梨州,可以使用Vite插件來實現(xiàn)server端監(jiān)聽特定請求痕囱,Vite插件擴展于rollup插件接口,并且在原有的基礎(chǔ)上增加了一些特有的鉤子函數(shù)暴匠,例如configureServer鉤子鞍恢,通過該鉤子函數(shù)可以用于配置開發(fā)服務(wù)器來監(jiān)聽特定的請求。

const codeServer = () => ({
  name: 'open-code-vite-server',
  configureServer(server) {
    server.middlewares.use((req, res, next) => {
      ...
      if (pathname == '/code') {
        ...
        if (filePath) {
          openCodeFile(filePath) // 執(zhí)行vscode定位代碼行命令
          ...
        }
        res.end()
      }
      ...
    })
  }
})

2.2.3 執(zhí)行 VSCode 定位命令

當(dāng)server端監(jiān)聽到client端發(fā)送的特定請求后每窖,接下來就是執(zhí)行VSCode定位代碼行命令帮掉。實際上,VSCode編輯器是可以通過code命令來啟動窒典,并且可以相應(yīng)使用一些命令行參數(shù)蟆炊,例如:

"code --reuse-window"或"code -r"命令可以打開最后活動窗口的文件或文件夾;"code --goto"或"code -g"命令后面可以拼接具體文件路徑和行列號瀑志,當(dāng)使用"code -g file:line:column"命令時可以打開某個文件并定位到具體的行列位置涩搓。

利用 VSCode 編輯器的這個特性污秆,我們就能實現(xiàn)自動定位代碼行功能,對應(yīng)的代碼路徑信息可以從client端發(fā)送的請求信息當(dāng)中獲得昧甘,再借助node的child_process.exec方法來執(zhí)行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命令到環(huán)境變量當(dāng)中庸推。Mac系統(tǒng)用戶可以在VSCode界面使用command+shift+p快捷鍵,然后搜索Code 并選擇install 'code' command in path浇冰;Windows用戶可以找到VSCode安裝位置的bin文件夾目錄贬媒,并將該目錄添加到系統(tǒng)環(huán)境變量當(dāng)中。

2.3 add-code-location

通過前面的介紹湖饱,大家應(yīng)該了解了client端和server端的執(zhí)行機制掖蛤,并且在執(zhí)行定位命令時需要獲取到頁面元素的代碼路徑,而具體的代碼路徑是以屬性的方式綁定到了DOM元素上井厌,這時候就需要用到add-code-location模塊在編譯時轉(zhuǎn)換我們的源碼蚓庭,并給 DOM元素添加對應(yīng)的代碼路徑屬性。

整個源碼轉(zhuǎn)換處理流程如下:

2.3.1 獲取文件路徑

源碼轉(zhuǎn)換過程的第一步是獲取代碼文件的具體路徑仅仆,對于webpack打包的項目來說器赞,webpack loader用來處理源碼字符串再合適不過,loader的上下文this對象包含一個resourcePath資源文件的路徑屬性墓拜,利用這個屬性我們很容易就能獲得每個代碼文件的具體路徑港柜。

module.exports = function (source) {
  const { resourcePath } = this
  return sourceCodeChange(source, resourcePath)
}

對于Vite構(gòu)建的項目來說,源碼的轉(zhuǎn)化操作也是通過插件來完成咳榜,Vite插件有通用的鉤子transform夏醉,可用于轉(zhuǎn)換已加載的模塊內(nèi)容,它接收兩個參數(shù)涌韩,code參數(shù)代表著源碼字符串畔柔,id參數(shù)是文件的全路徑。

module.exports = function() {
  return {
    name: 'add-code-location',
    transform(code, id) {
      ...
      return sourceCodeChange(code, id)
    }
  }
}

2.3.2 計算代碼行號

接著在遍歷源碼文件的過程中臣樱,需要處理對應(yīng)Vue文件template模板中的代碼靶擦,以“\n”分割template模板部分字符串為數(shù)組,通過數(shù)組的索引即可精準(zhǔn)得到每一行html標(biāo)簽的代碼行號雇毫。

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模板中分割的每一行標(biāo)簽元素添加最終的位置屬性棚放。這里采用的是正則替換的方式來添加位置屬性枚粘,分別對每一行標(biāo)簽元素先正則匹配出所有元素的開始標(biāo)簽部分,例如<div席吴、<span赌结、<img等捞蛋,然后將其正則替換成帶有code-location屬性的開始標(biāo)簽,對應(yīng)的屬性值就是前面獲取的代碼路徑和對應(yīng)標(biāo)簽的行號柬姚。


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元素添加對應(yīng)的源碼位置屬性時拟杉,實際上采用的是相對路徑,這樣可以使得DOM元素上的屬性值更加簡潔明了量承。node_modules文件夾通常是在項目的根目錄下搬设,而插件是以npm包的形式安裝在node_modules路徑下,利用node的__dirname變量可以獲得當(dāng)前模塊的絕對路徑撕捍,因此在源碼轉(zhuǎn)換過程中就可以獲取到項目的根路徑拿穴,從而就能獲得Vue代碼文件的相對路徑。

let pathBefore = __dirname.substring(0, __dirname.search('node_modules'))
let filePath = filePath.substring(pathBefore.length) // vue代碼相對路徑

在server端執(zhí)行代碼定位命令時忧风,再將對應(yīng)的代碼相對路徑拼接成完整的絕對路徑默色。

2.4.2 外部引入組件

add-code-location雖然可以對本地的Vue文件進行代碼路徑信息的添加,但是對于外部引入或解析加載的組件目前是沒有辦法進行轉(zhuǎn)換的狮腿,例如element ui組件腿宰,實際上的代碼行信息只會添加在element ui組件的最外層。這時候client端在獲取點擊元素的代碼路徑時會做一個向上查找的處理缘厢,獲取其父節(jié)點的代碼路徑吃度,如果還是沒有,會繼續(xù)查找父節(jié)點的父節(jié)點贴硫,直到成功獲取代碼路徑椿每。

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搭建的頁面元素時,也能成功定位打開對應(yīng)代碼文件英遭。

三间护、接入方案

通過前面的介紹,想必大家對頁面元素代碼映射插件原理有了清晰的了解挖诸,接下來就介紹一下在項目中的接入方式兑牡。接入方式其實很簡單,并且可以選擇只在本地開發(fā)環(huán)境接入税灌,不用擔(dān)心對我們的生產(chǎn)環(huán)境造成影響,放心使用亿虽。

3.1 webpcak構(gòu)建項目

對于webpack構(gòu)建的項目來說菱涤,首先在構(gòu)建配置項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) { // 本地開發(fā)環(huán)境
  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構(gòu)建項目

Vite構(gòu)建項目接入該插件的方案和webpack構(gòu)建項目基本上一致粘秆,唯一不一樣的地方在于打包配置文件里引入的是兩個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()
  ]
}

四收毫、總結(jié)

以上就是對頁面元素代碼映射插件核心原理和接入方案的介紹攻走,實現(xiàn)的方式充分利用了項目代碼打包構(gòu)建的流程殷勘,實際上無論是哪個打包工具,本質(zhì)上都是對源碼文件的轉(zhuǎn)換處理昔搂,當(dāng)我們理解了打包工具的運行機制后玲销,就可以做一些自己認(rèn)為有意義的事。就拿頁面元素代碼映射插件來說摘符,使用它可以極大提升開發(fā)效率贤斜,不再需要花費時間在尋找代碼文件上,特別是頁面數(shù)和組件數(shù)比較多的項目逛裤,只需點擊頁面元素瘩绒,即可一鍵打開對應(yīng)代碼文件,精準(zhǔn)定位具體代碼行带族,無需查找锁荔,哪里不會點哪里,so easy!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蝙砌,一起剝皮案震驚了整個濱河市阳堕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌拍霜,老刑警劉巖嘱丢,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異祠饺,居然都是意外死亡越驻,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進店門道偷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缀旁,“玉大人,你說我怎么就攤上這事勺鸦〔⑽。” “怎么了?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵换途,是天一觀的道長懊渡。 經(jīng)常有香客問我,道長军拟,這世上最難降的妖魔是什么剃执? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮懈息,結(jié)果婚禮上肾档,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好怒见,可當(dāng)我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布俗慈。 她就那樣靜靜地躺著,像睡著了一般遣耍。 火紅的嫁衣襯著肌膚如雪闺阱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天配阵,我揣著相機與錄音馏颂,去河邊找鬼。 笑死棋傍,一個胖子當(dāng)著我的面吹牛救拉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播瘫拣,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼亿絮,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了麸拄?” 一聲冷哼從身側(cè)響起派昧,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拢切,沒想到半個月后蒂萎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡淮椰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年五慈,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片主穗。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡泻拦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出忽媒,到底是詐尸還是另有隱情争拐,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布晦雨,位于F島的核電站架曹,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏闹瞧。R本人自食惡果不足惜音瓷,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望夹抗。 院中可真熱鬧,春花似錦纵竖、人聲如沸漠烧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽已脓。三九已至珊楼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間度液,已是汗流浹背厕宗。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留堕担,地道東北人已慢。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像霹购,于是被迫代替她去往敵國和親佑惠。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,933評論 2 355

推薦閱讀更多精彩內(nèi)容