公司有一個(gè)數(shù)據(jù)BI平臺(tái)羡藐,它的起源是一個(gè)開源型的項(xiàng)目DataEase(一個(gè)開源數(shù)據(jù)可視化分析工具,幫助用戶通過拖拽交互做儀表搭建和視圖制作悯许、分享的低代碼平臺(tái)仆嗦。
關(guān)于DataEase:
官網(wǎng)地址:https://dataease.io/index.html
github代碼地址:https://github.com/dataease/dataease/
一、問題背景
在接手這個(gè)項(xiàng)目的時(shí)候先壕,它距離第一次代碼提交已經(jīng)有22個(gè)月瘩扼,代碼量共計(jì)約16w行代碼左右谆甜。而因?yàn)槭腔陂_源代碼庫加以修改的,很多文件的最近一次更新甚至還是在22個(gè)月前(也就是首次提交的時(shí)候)邢隧,很多代碼的存在可能隨著某些功能被放棄店印,也已經(jīng)不再被需要了,但是前面的維護(hù)同學(xué)也沒有勇于做必要的刪除倒慧。
對(duì)于我這個(gè)現(xiàn)任的代碼維護(hù)者,就經(jīng)常遇到看到某一個(gè)功能點(diǎn)包券,依據(jù)代碼debug后到某個(gè)組件上纫谅,再查看組件的調(diào)用時(shí)候有N個(gè)調(diào)用方。但是評(píng)估改動(dòng)的影響面和工作量后溅固,一個(gè)個(gè)找出來卻發(fā)現(xiàn)很多個(gè)調(diào)用方根本早就是被廢棄了付秕,屬于不該存在的調(diào)用方。多次這樣的情況發(fā)生之后侍郭,這就很困擾我的開發(fā)效率询吴。所以決心好好整治一下這些被廢棄的,不該存在的冗余代碼亮元。
二猛计、方案思考
想找出哪些組件(或者文件)是無用的,最先想到的肯定是構(gòu)建編譯時(shí)候爆捞,構(gòu)建工具可以基于ES module的靜態(tài)分析對(duì)冗余節(jié)點(diǎn)做Tree shaking的機(jī)制奉瘤。那么我們是不是可以從結(jié)果出發(fā)來倒推,把被Tree shaking掉的節(jié)點(diǎn)文件都找出來呢煮甥?
Tree shaking 是一個(gè)通常用于描述移除 JavaScript 上下文中的未引用代碼 (dead-code) 行為的術(shù)語盗温。
它依賴于 ES2015 中的 import 和 export 語句,用來檢測(cè)代碼模塊是否被導(dǎo)出成肘、導(dǎo)入卖局,且被 JavaScript 文件使用。
在現(xiàn)代 JavaScript 應(yīng)用程序中双霍,我們使用模塊打包 (如webpack或Rollup) 將多個(gè) JavaScript 文件打包為單個(gè)文件時(shí)自動(dòng)刪除未引用的代碼砚偶。這對(duì)于準(zhǔn)備預(yù)備發(fā)布代碼的工作非常重要,這樣可以使最終文件具有簡(jiǎn)潔的結(jié)構(gòu)和最小化大小店煞。
以webpack為例蟹演,能做編譯構(gòu)建后的資源分析的插件有webpack-bundle-analyzer , @statoscope/webpack-plugin等。
三顷蟀、實(shí)現(xiàn)方案
1酒请、拿到工程里所有待檢測(cè)的文件
我這里是直接通過shell腳本的方式得到src下所有的.js, .ts, .vue文件的文件名,然后輸出到output.txt文件鸣个。也有同學(xué)提到羞反,可以直接fast-glob這個(gè)包來做文件名收集布朦,當(dāng)然也是可以的。用你喜歡的就好昼窗。
# 進(jìn)入到src文件夾下
cd src
# 找到所有的.js, .ts, .vue文件的文件名是趴,輸出到src/output.txt文件
find . -type f ( -name "*.js" -o -name "*.ts" -o -name "*.vue" ) -exec echo {} ; > output.txt
下面這是得到的.js, .ts, .vue所有文件的文件路徑(示例):
App.vue
websocket/index.js
plugins/Blob.js
layout/index.vue
layout/mixin/ResizeHandler.js
layout/components/Sidebar/index.vue
…………
# 共計(jì)706個(gè)文件
修改一下上面的shell腳本,就可以獲取到所有的圖片文件(這里主要是:.png, .jpeg, .jpg, .svg文件)澄惊,腳本文件:
# 進(jìn)入到src文件夾下
cd src
# 找到所有的.js, .ts, .vue文件的文件名唆途,輸出到src/output.txt文件
find . -type f ( -name "*.png" -o -name "*.jpeg" -o -name "*.jpg" -o -name "*.svg" ) -exec echo {} ; > outputImage.txt
得到的圖片類文件的文件outputImage.txt:
components/canvas/assets/title.jpg
components/canvas/assets/bg-kj-1.jpg
components/canvas/assets/iconfont/iconfont.svg
icons/svg/task.svg
icons/svg/arrow-down.svg
icons/svg/system.svg
icons/svg/web-msg.svg
icons/svg/eye-open.svg
……
# 共計(jì)191個(gè)文件
二、選擇一個(gè)靜態(tài)資源編譯工具做Tree-shaking檢測(cè)
1掸驱、Webpack-bundle-analyzer
Webpack-bundle-analyzer的使用比較廣泛肛搬,github的star數(shù)也相當(dāng)多。我這里是基于Vue-cli v5的vue項(xiàng)目毕贼,本身是已經(jīng)集成了Webpack-bundle-analyzer插件温赔,只需要在構(gòu)建的時(shí)候在命令后加上--report
就可以在構(gòu)建的時(shí)候同時(shí)產(chǎn)出report.html文件,打開后如下圖所示:
[圖片上傳失敗...(image-ebf446-1713856065422)]
這里可以看到構(gòu)建后的產(chǎn)出的靜態(tài)資源文件鬼癣,以及每個(gè)資源包的構(gòu)成分別是哪些依賴包或者業(yè)務(wù)文件陶贼。
React工程的話可以自行參考webpack官網(wǎng)來進(jìn)行配置:https://webpack.js.org/api/cli/#analyzing-bundle
2、 @statoscope/webpack-plugin
@statoscope/webpack-plugin的功能類似于webpack-bundle-analyzer待秃,雖然star數(shù)稍微低一點(diǎn)拜秧,但是功能也很強(qiáng)。配置用法可以自行參考其github地址:https://github.com/statoscope/statoscope/tree/master/packages/webpack-plugin
我這里的配置僅代表我本身锥余,畢竟我這只是個(gè)vue-cli項(xiàng)目的vue.config.js的配置:
if (process.env.VUE_APP_RUN_ENV !== 'development') {
plugins = plugins.concat([
new BundleStatsWebpackPlugin({
json: false,
html: true,
outDir: './reports',
}),
new StatoscopeWebpackPlugin({
saveReportTo: path.resolve(__dirname, 'dist/reports/statoscope-report.html'),
saveStatsTo: path.resolve(__dirname, 'dist/reports/statoscope-report.json'),
normalizeStats: false,
saveOnlyStats: false,
disableReportCompression: false,
statsOptions: {},
watchMode: false,
name: 'some-name',
open: false,
compressor: 'gzip',
reports: [],
extensions: [],
}),
])
}
來看一下編譯生產(chǎn)的report/statoscope-report.html文件:
[圖片上傳失敗...(image-bcc8d9-1713856065422)]
它有一個(gè)EntryPoints入口和Modules入口腹纳,前者是基本入口分析都引用了誰,后者是反向的根據(jù)模塊來分析它被誰調(diào)用了驱犹。
基于這些我就可以分析到底哪些文件是有出現(xiàn)在構(gòu)建的結(jié)果里的嘲恍。尤其是 @statoscope/webpack-plugin的構(gòu)建結(jié)果,可以看到除了report/statoscope-report.html雄驹,還生成了一個(gè)report/statoscope-report.json文件佃牛。這里正式描述依賴關(guān)系的文件,這個(gè)很重要医舆。是下面要用到的文件俘侠。
三、檢測(cè)未被引用的文件
下面就是利用這些文件路徑來檢測(cè)哪些是未被引用過的蔬将,附檢測(cè)的腳本代碼:
const fs = require('fs')
const readline = require('readline')
const filePath = './output.txt' // 替換為你的文本文件路徑
const readStream = fs.createReadStream(filePath)
const rl = readline.createInterface({
input: readStream,
crlfDelay: Infinity, // 處理Windows的換行符
})
const lines = []
rl.on('line', (line) => {
// 每行的內(nèi)容將會(huì)觸發(fā)這個(gè)回調(diào)
lines.push(line)
})
rl.on('close', () => {
// 文件讀取完畢
console.log('文件讀取完畢') // 輸出包含每行字符串的數(shù)組
})
const largeFilePath = '../dist/reports/statoscope-report.json' // 替換為你的大文件路徑
// const targetString = 'yourTargetString' // 替換為你要查找的字符串
const readStream2 = fs.createReadStream(largeFilePath)
const rl2 = readline.createInterface({
input: readStream2,
crlfDelay: Infinity, // 處理Windows的換行符
})
const foundStrings = new Set()
rl2.on('line', (line) => {
// 在每一行中查找目標(biāo)字符串
lines.forEach((targetString) => {
if (!line.includes(targetString)) {
foundStrings.add(targetString)
}
})
})
rl2.on('close', () => {
// 文件讀取完畢爷速,輸出所有找到的字符串
const resultArray = Array.from(foundStrings)
console.log('check end')
const resultPath = './NoReferenceResult.txt'
// 將字符串寫入文件
fs.writeFile(resultPath, resultArray.join('\n'), (err) => {
if (err) {
console.error('Error writing to file:', err)
} else {
console.log('File written successfully:', resultPath)
}
})
})
這里檢測(cè)結(jié)果的產(chǎn)物是一個(gè)NoReferenceResult.txt文件,這里直接貼一下結(jié)果:
components/TreeSelector/index.vue
components/SizeSelect/index.vue
components/LangSelect/index.vue
components/Notification/index.vue
components/GridButton/index.vue
components/business/complex-table/index.vue
……
# 共計(jì)135個(gè)文件
這里未被引用的文件合計(jì)有2w多行代碼霞怀,差不多占據(jù)了總代碼行數(shù)的14%左右的代碼量了惫东。
同樣的還有未被引用過的圖片:
components/canvas/assets/iconfont/iconfont.svg
icons/svg/table-pivot.png
assets/theme-dark.png
assets/theme-custom.png
assets/panel/show_all.png
assets/drag-indicator.png
assets/banner.png
assets/DataEase-black.png
assets/fill_radio.png
assets/deV.png
assets/theme-default.png
assets/DataEase-color.png
assets/blue_1.svg
assets/login-desc.png
assets/template.png
assets/avatar.jpeg
……
# 30多個(gè)未被引用過的圖片文件
四、后續(xù)
第一次檢測(cè)出這么多文件的時(shí)候,我是及其震驚的廉沮。竟然有這么多無用的代碼颓遏,2w多行啊V褪薄叁幢!當(dāng)時(shí)刪除的時(shí)候還是對(duì)著這些文件名一個(gè)個(gè)在代碼里做了搜索的,確實(shí)是一處引用都沒有坪稽,才敢放心刪除曼玩。刪除后又灰度了兩周才敢推全量上線的,但事實(shí)證明:屁事沒有啊窒百,干就完了兄弟們演训。
親身經(jīng)歷證明,刪代碼可比寫代碼解壓太多了哇~1戳!