之前在umi項(xiàng)目下弄了個umi-plugin-gtj欣范,原理是在umi的生命周期onDevCompileDone下執(zhí)行輸出json邏輯,但發(fā)現(xiàn)有非常多的地方可以優(yōu)化
回顧:
最懶的前端多語言策略(一)
最懶的前端多語言策略(二)
可見的不足
發(fā)現(xiàn)每次編譯都會執(zhí)行run(邏輯入口) -> fileDisplay(遞歸目錄)
很容易發(fā)現(xiàn)只要有小小改動,都會走遞歸邏輯咱圆,這樣肯定不行
嘗試解決
1.于是我想在umi本身提供的onDevCompileDone里頭看看有沒返回,還真實(shí)看不出哪個是,剛提了個issue榛做,估計也不會回的痊末,先放棄在onDevCompileDone里做的想法
2.于是我打算在onStart里搞蚕苇,onStart是只執(zhí)行一次,onDevCompileDone思路又先放棄凿叠,那能在里面寫自定義監(jiān)聽邏輯了
fs.watch
簡單的watch邏輯
fs.watch(entry, {
recursive: true, // 遞歸
}涩笤, (event, filename) => {
console.log(`${filename}文件發(fā)生更新`)
// 執(zhí)行邏輯
run(`${entry}/${filename}`)
})
眾所周知,watch回調(diào)是非常敏感的盒件,即使只修改一個字母蹬碧,可能也會觸發(fā)4次回調(diào),這樣肯定不行炒刁,我們可以判斷只在event為'change'時做邏輯
if(event 恩沽!== ‘change')return
然而我發(fā)現(xiàn)我只在文件復(fù)制粘貼,什么內(nèi)容沒變都會觸發(fā)watch翔始,我覺得是修改時間也會影響罗心,于是得直接判斷文件內(nèi)容,上md5
const md5 = require('md5')
let preveMd5 = null
...
fs.watch(entry, {
recursive: true, // 遞歸
}城瞎, (event, filename) => {
if (event !== 'change') return
if(!filename) return
let currentMd5 = md5(fs.readFileSync(`${entry}/${filename}`))
if (currentMd5 == preveMd5) {
return
}
preveMd5 = currentMd5
console.log(`${filename}文件發(fā)生更新`)
// 執(zhí)行邏輯
run(`${entry}/${filename}`)
})
完成渤闷,只變動一次,但對于打代碼極快的我脖镀,幾秒操作可能執(zhí)行很多次run飒箭,于是上debounce
let timer = null
fs.watch(entry, {
recursive: true, // 遞歸
}, (event, filename) => {
if (event !== 'change') return
if(!filename) return
if(timer) return
timer = setTimoue(() => timer = null, 1000)
let currentMd5 = md5(fs.readFileSync(`${entry}/${filename}`))
if (currentMd5 == preveMd5) {
return
}
preveMd5 = currentMd5
console.log(`${filename}文件發(fā)生更新`)
// 執(zhí)行邏輯
run(`${entry}/${filename}`)
})
自定義watch邏輯完成!
生成邏輯改動
因?yàn)檫壿嫃膐nDevCompileDone轉(zhuǎn)移到onStart认然,同時我想將其單獨(dú)
封裝补憾,于是改主體邏輯
const createCode = ({
entry,
output,
increment,
word,
}) = > {
...
run() // 第一次執(zhí)行時調(diào)用
return {
run // 將run返回,方便多次調(diào)用
}
}
在onStart里加上
api.onStart(() => {
...
const { run } = createCode({
entry,
output,
increment,
word,
})
...
fs.watch...
})
細(xì)節(jié)改動
因?yàn)閣atch已經(jīng)告訴我們是什么文件變化了卷员,我們不用執(zhí)行fileDisplay(遞歸文件夾)盈匾,只需直接走readFileToObj(寫入邏輯)就好了,run和readFileToObj也需要改動
// 開始邏輯, 有filename參數(shù)直接去寫入json邏輯
function run(filename) {
...
then(value => {
if(filename){
readFileToObj(filename, value, null, true)
}
// 要不就走遞歸毕骡,全部文件訪問
fileDisplay(filePath, value, function (value) {
console.log('finish:', Date.now() - startTime)
})
})
}
// 寫入邏輯削饵,多了個alone參數(shù),直接寫入
function readFileToObj(fReadName, value, callback, alone = false) {
...
objReadline.on('close', () => {
// 文件都讀過了未巫,寫進(jìn)生成文件
if (--readNum === 0 || alone) {
let result = JSON.stringify(obj, null, 2)
fs.writeFile(output, result, err => {
if (err) {
console.warn(err)
}
})
callback && callback()
}
})
}
完成窿撬!基本沒毛病,貼上完整代碼
const fs = require('fs')
const md5 = require('md5')
const readline = require('readline')
const path = require('path')
let preveMd5 = null
const createCode = ({
entry,
output,
increment,
word
}) => {
const obj = {}
const separator = `${word}[` // 分隔符
const suffix = ['.js', '.jsx'] // 后綴白名單
let readNum = 0 // 計數(shù)還剩未讀的文件數(shù)
console.log('-----start-----')
// 寫入邏輯叙凡,多了個alone參數(shù)劈伴,直接寫入
function readFileToObj(fReadName, value, callback, alone = false) {
var fRead = fs.createReadStream(fReadName)
var objReadline = readline.createInterface({
input: fRead,
});
objReadline.on('line', line => {
// 注釋的忽略
if (line.includes('//') || line.includes('*')) {
return
}
if (line) {
const arr = line.split(separator)
if (arr.length > 1) {
const bb = arr.slice(1)
for (let i in bb) {
const v0 = bb[i].split(']')[0]
const v = v0.substr(1, v0.length - 2)
if (!v) {
// 空輸出提示
console.warn(`空行為:${line}`)
continue
}
// 增量就不覆蓋了
if (increment && value && value[v]) {
obj[v] = value[v]
} else {
obj[v] = v
}
}
}
}
})
objReadline.on('close', () => {
// 文件都讀過了,寫進(jìn)生成文件
if (--readNum === 0 || alone) {
let result = JSON.stringify(obj, null, 2)
fs.writeFile(output, result, err => {
if (err) {
console.warn(err)
}
})
callback && callback()
}
})
}
const filePath = path.resolve(entry)
// 遞歸執(zhí)行握爷,直到判斷是文件就執(zhí)行readFileToObj
function fileDisplay(filePath, value, callback) {
fs.readdir(filePath, (err, files) => {
let count = 0
function checkEnd() {
if (++count === files.length && callback) {
callback()
}
}
if (err) {
console.warn(err)
} else {
files.forEach(filename => {
var fileDir = path.join(filePath, filename)
fs.stat(fileDir, (err2, status) => {
if (err2) {
console.warn(err2)
} else {
if (status.isDirectory()) {
return fileDisplay(fileDir, value, checkEnd)
}
else if (status.isFile()) {
// 后綴不符合的跳過跛璧,并計數(shù)加一
if (suffix.includes(path.extname(fileDir))) {
readNum++
readFileToObj(fileDir, value)
}
}
checkEnd()
}
})
})
}
})
}
// 開始邏輯, 有filename參數(shù)直接去寫入json邏輯
function run(filename) {
new Promise((resolve, reject) => {
fs.exists(output, exists => {
// 存在且增量生成
if (exists && increment) {
console.log('增量更新')
fs.readFile(output, 'utf-8', (err, data) => {
if (err) {
console.warn(err)
} else {
try {
// 舊文件已存在的json
const json = JSON.parse(data)
resolve(json)
} catch (e) {
// 翻車
console.warn(e)
}
}
})
} else {
console.log('全量更新')
resolve()
}
})
}).then(value => {
let startTime = Date.now()
if(filename) {
readFileToObj(filename, value, null, true)
return
}
// 要不就走遞歸严里,全部文件訪問
fileDisplay(filePath, value, function (value) {
console.log('finish:', Date.now() - startTime)
})
})
}
run()
return {
run
}
}
export default (api, {
entry = './src',
output = './lang.json',
increment = true,
word = 'lang'
} = {}) => {
api.onStart(() => {
if (!output) {
throw new Error('output必填')
}
const { run } = createCode({
entry,
output,
increment,
word
})
let timer = null
fs.watch(entry, {
recursive: true
}, (event, filename) => {
if (event !== 'change') return
if(!filename) return
if(filename.indexOf('.umi') > -1) return
if(timer) return
timer = setTimeout(() => timer = null, 1000)
let currentMd5 = md5(fs.readFileSync(`${entry}/${filename}`))
if (currentMd5 == preveMd5) {
return
}
preveMd5 = currentMd5
console.log(`${filename}文件發(fā)生更新`)
run(`${entry}/${filename}`)
});
})
api.onDevCompileDone(({ stats }) => {
});
};
改進(jìn)后對比
遞歸只在onStart執(zhí)行了一次,后續(xù)開發(fā)只根據(jù)變動文件做增量更新追城,大大減少了沒必要的訪問成本
思考
原本是想將watch邏輯放在外面腳本刹碾,在onStart利用child_process啟動腳本,無奈并沒觸發(fā)到理想的效果座柱,希望有知道的哥們指點(diǎn)一下