<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在做前端專案時,會在各個關鍵節點列印紀錄檔,方便後續資料分析和問題排查。當紀錄檔越來越多之後,又會遇到通過紀錄檔反查程式碼所在檔案和所在行的場景,於是一個很自然的需求就出來了:
在列印紀錄檔的時候,自動注入當前檔名、行號、列號。
舉個例子,有個 logger 函數,我們在 index.js 的業務程式碼某一行新增列印邏輯:
const { logLine } = require('./utils') function getJuejinArticles() { const author = 'keliq' const level = 'LV.5' // ... 業務程式碼省略,獲取文章列表 logLine(author, level) // ... } getJuejinArticles()
正常情況下會輸出:
keliq LV.5
但是希望能夠輸出帶檔名和行號,即:
[index.js:7:3] keliq LV.5
表明當前這次列印輸出來源於 index.js 檔案中的第 7 行第 3 列程式碼,也就是 logLine 函數所在的具體位置。那如何實現這個需求呢?我的腦海中浮現了兩個思路:
因為 error 錯誤棧裡面天然帶有此類資訊,可以人工製造了一個 Error,然後捕獲它:
exports.logLine = (...args) => { try { throw new Error() } catch (e) { console.log(e.stack) } }
仔細觀察列印的結果:
Error
at logLine (/test/src/utils.js:3:11)
at getJuejinArticles (/test/src/index.js:7:3)
at Object.<anonymous> (/test/src/index.js:11:1)
at Module._compile (node:internal/modules/cjs/loader:1105:14)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1159:10)
at Module.load (node:internal/modules/cjs/loader:981:32)
at Function.Module._load (node:internal/modules/cjs/loader:822:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12)
at node:internal/main/run_main_module:17:47
第三行的內容不正是我們想要的結果嗎?只需要把這一行的字串進行格式化一下,提取出 index.js:7:3 即可:
at getJuejinArticles (/test/src/index.js:7:3)
由於程式碼結構是這樣的:
.
└── src
├── index.js
└── utils.js
只需要改成下面的程式碼即可:
exports.logLine = (...args) => { try { throw new Error() } catch (e) { const lines = e.stack.split('n') const fileLine = lines[2].split('/src/').pop().slice(0, -1) console.log(`[${fileLine}]`, ...args) } }
命令列試一試:
$ test node src/index.js [index.js:7:3] keliq LV.5
問題似乎完美解決,然而還是想的太簡單了,上述場景僅限於 node.js 環境,而在 web 環境,所有的產物都會被 webpack 打到一個或多個 js 檔案裡面,而且做了壓縮混淆處理,由於 error 是在執行時被捕獲到的 ,所以我沒根本無法拿到開發狀態下的檔名、行號和列號,如下圖所示:
那怎麼辦呢?解鈴還須繫鈴人,既然 webpack 對程式碼進行了加工處理,那就只能在預處理最開始的階段介入進來,寫一個自定義的 loader 來解析原始碼檔案,拿到檔名、行號和列號。說幹就幹,建立一個 inject-line.loader.js,寫下模板程式碼:
module.exports = function (content) { content = content.toString('utf-8') if (this.cacheable) this.cacheable() console.log(this.resourcePath) // 列印檔案路徑 console.log(content) // 列印檔案內容 return content } module.exports.raw = true
然後在 webpack.config.js 中做設定:
module.exports = { entry: './src/index.js', output: { filename: 'index.js', }, module: { rules: [ { test: /.js$/, exclude: [/node_modules/], use: [ { loader: require.resolve('./loaders/inject-line.loader'), }, ], }, ], }, }
一切準備就緒,先執行一下看看輸出:
可以看到,index.js 和 utils.js 被自定義的 inject-line.loader.js 給載入到了,通過 this.resourcePath 能夠拿到檔名稱,行號和列號的話只能通過分析 content 字串進行提取了,處理的程式碼如下:
// 拿到檔案路徑 const fileName = this.resourcePath.split('/src/').pop() // 文字內容按行處理後再拼接起來 content = content .split('n') .map((line, row) => { const re = /logLine((.*?))/g let result let newLine = '' let cursor = 0 while ((result = re.exec(line))) { const col = result.index newLine += line.slice(cursor, result.index) + `logLine('${fileName}:${row + 1}:${col + 1}', ` + result[1] + ')' cursor += col + result[0].length } newLine += line.slice(cursor) return newLine }) .join('n')
這裡面的邏輯,如果光看程式碼的話可能會雲裡霧裡,其實思路很簡單,就是下面這樣的:
這樣的話,即使程式碼經過各種壓縮轉換,也不會改變開發狀態下程式碼所在的檔名、行與列的位置了。開啟 webpack 打包後的檔案看一下:
到這裡,功能就已經開發完了,不過還有一個小小的缺陷就是 logLine 函數名是寫死的,能不能讓使用者自己定義這個函數名呢?當然可以,在 webpack 組態檔中,支援利用 options 屬性傳遞 config 設定引數:
module.exports = { entry: './src/index.js', output: { filename: 'index.js', }, module: { rules: [ { test: /.js$/, exclude: [/node_modules/], use: [ { loader: require.resolve('./loaders/inject-line.loader'), options: { config: { name: 'customLogName', }, }, }, ], }, ], }, }
然後在 inject-line.loader.js 程式碼中通過 this.query.config 拿到該設定即可,不過正規表示式也要根據這個設定動態建立,字串替換的時候也要換成該設定變數,最終程式碼如下:
module.exports = function (content) { content = content.toString('utf-8') if (this.cacheable) this.cacheable() const { name = 'logLine' } = this.query.config || {} const fileName = this.resourcePath.split('/src/').pop() content = content .split('n') .map((line, row) => { const re = new RegExp(`${name}((.*?))`, 'g') let result let newLine = '' let cursor = 0 while ((result = re.exec(line))) { const col = result.index newLine += line.slice(cursor, result.index) + `${name}('${fileName}:${row + 1}:${col + 1}', ` + result[1] + ')' cursor += col + result[0].length } newLine += line.slice(cursor) return newLine }) .join('n') return content } module.exports.raw = true
到此這篇關於如何巧用webpack在紀錄檔中記錄檔案行號的文章就介紹到這了,更多相關webpack紀錄檔記錄檔案行號內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45