只需幾行代碼,手寫實(shí)現(xiàn)【文件監(jiān)聽和自動(dòng)重啟】
前言
寫這篇文章的原因是因?yàn)?好奇于平時(shí) 在vscode或者其他編譯器寫代碼的時(shí)候,會(huì)在我修改代碼后重新打包,當(dāng)然我知道是webpack的功能,那它是怎么知道我修改了代碼的,我覺得肯定有個(gè)東西在監(jiān)聽,于是就研究了億下下。
手寫實(shí)現(xiàn)
稍微寫過一點(diǎn) node 應(yīng)用的同學(xué)應(yīng)該都有類似的經(jīng)歷,編寫一段代碼,運(yùn)行一個(gè) http server,保存為 js 文件,例如 app.js ,
然后執(zhí)行命令
node app.js
此時(shí) node 就會(huì)執(zhí)行我們剛才書寫的代碼。此時(shí)一切看起來都很美好,但是隨著開發(fā)繼續(xù),一些問題也會(huì)逐漸顯現(xiàn)出來。
每過一段時(shí)間,我們都想看下剛才書寫都代碼是否能夠正常運(yùn)行,那么每次保存代碼以后,我們都不得不 ctrl + C 結(jié)束掉剛才的 server,然后重新運(yùn)行這段腳本。于是乎一些能夠監(jiān)聽文件變化并支持自動(dòng)重啟的工具就出現(xiàn)在了我們的視線中。比如 hotnode,supervisor 等等。這些工具能夠很好的節(jié)約開發(fā)者在重復(fù)操作上花費(fèi)的時(shí)間,能夠把經(jīng)歷更加專注在其他方面。
下面說一下如何實(shí)現(xiàn)一個(gè)極簡(jiǎn)的工具。
文件監(jiān)聽
fs.watch()
fs,F(xiàn)ile System 的縮寫,利用這個(gè) API,我們可以對(duì)文件或者目錄進(jìn)行監(jiān)聽。當(dāng)監(jiān)聽到文件變化后,執(zhí)行預(yù)設(shè)的回調(diào)函數(shù)。
fs.watch("app.js", (event, filename) => {
// 文件變化時(shí)觸發(fā)的回調(diào)函、
});
細(xì)心的小伙伴可能已經(jīng)想到了,文件發(fā)生變化,我再去執(zhí)行一個(gè)什么函數(shù),把a(bǔ)pp.js再重新執(zhí)行一下,是不是就可以了?
你說的沒錯(cuò)。
自動(dòng)重啟
API: child_process.spawn
我們使用 spawn 開啟一個(gè)新的進(jìn)程,然后在在這個(gè)進(jìn)程中執(zhí)行 js 腳本即可,話不多說,上栗子:
const { spawn } = require("child_process");
let process = null;
function startProcess() {
// 執(zhí)行js腳本
process = spawn("node", ["app.js"]);
// 打印輸出到控制臺(tái)
process.stdout.on("data", data => {
console.log(data.toString());
});
}
// 有了start,還要有個(gè)restart
function restartProcess() {
if (process !== null) {
try {
// 清除進(jìn)程,
process.kill("SIGKILL");
} catch (error) {
console.log("Exception: " + error.message, "bad");
}
}
// 然后這里再次啟動(dòng),可以說很像清除定時(shí)器的操作了~
startProcess();
}
最后,我們把restartProcess放到fs.watch的回調(diào)函數(shù)里,再讓startProcess和fs.watch順序執(zhí)行就好了。像這樣:
fs.watch("app.js", (event, filename) => {
restartProcess();
});
startProcess();
如果你想寫一個(gè)放心能用的工具,還是有很多東西要解決的,比如即使沒有修改文件內(nèi)容,單純的按下保存,也會(huì)觸發(fā) watch 函數(shù)的回調(diào)。這需要采取一定的策略去判斷保存前后文件內(nèi)容是否有變動(dòng)。
有興趣的同學(xué)也可以去看下另一個(gè)庫(kù)的代碼,watch,鏈接:https://link.juejin.cn/?target=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2Fwatch
相關(guān)優(yōu)化與思考
這樣修改并保存的時(shí)候回發(fā)現(xiàn)一樣有點(diǎn)問題。直接保存,顯示兩次更新
修改文件以后,一樣顯示兩次更新(mac系統(tǒng)上是兩次,其余系統(tǒng)可能有所差異) 這樣多是于操做系統(tǒng)對(duì)文件修改的事件支持有關(guān),在保存的時(shí)候出發(fā)了不止一次。
下面聚焦于回調(diào)事件的參數(shù),event對(duì)應(yīng)事件類型,是否能夠判斷事件類型為change呢,才執(zhí)行呢,忽略空保存。
fs.watch("app.js", (event, filename) => {
if (filename && event == 'change') {
restartProcess();
}
});
startProcess();
不過實(shí)際上,空的保存event也是change,另外不一樣平臺(tái)event的實(shí)現(xiàn)可能也有所不一樣。這種方式要pass掉。
顯然從上面的例子能夠看到,變動(dòng)時(shí)間依然不可控。由于每次保存,node對(duì)應(yīng)stat對(duì)象依然會(huì)修改。
對(duì)比文件內(nèi)容
只能選擇這種方式來判斷是不是否更新。例如md5:
const fs = require('fs'),
md5 = require('md5');
const filePath = './'
let preveMd5 = null
fs.watch("app.js", (event, filename) => {
var currentMd5 = md5(fs.readFileSync(filePath + filename))
if (currentMd5 == preveMd5) {
return
}
preveMd5 = currentMd5
restartProcess();
});
startProcess();
沒錯(cuò),也可以說是判斷文件的hash值。
先保存當(dāng)前文件md5值,每次文件變化時(shí)(即保存操做響應(yīng)以后),每次都獲取文件的md5而后進(jìn)行對(duì)比,看是否發(fā)生變化。
不過這樣能夠看到,當(dāng)初次保存時(shí),都會(huì)執(zhí)行一次,由于初始值為null的緣故。這樣能夠加個(gè)兼容,根據(jù)是否第一次保存來判斷好了。
對(duì)于不一樣的操做系統(tǒng),可能保存時(shí)觸發(fā)的回調(diào)不止一個(gè)(mac上到?jīng)]出現(xiàn))。為了不這種實(shí)時(shí)響應(yīng)對(duì)應(yīng)的頻繁觸發(fā),能夠引入debounce函數(shù)來保證性能。
const fs = require('fs'),
md5 = require('md5');
let preveMd5 = null,
fsWait = false
const filePath = './'
fs.watch(filePath,(event,filename)=>{
if (filename){
if (fsWait) return;
fsWait = setTimeout(() => {
fsWait = false;
}, 100)
var currentMd5 = md5(fs.readFileSync(filePath + filename))
if (currentMd5 == preveMd5){
return
}
preveMd5 = currentMd5
restartProcess();
}
})
startProcess();
結(jié)束
謝謝大家觀看。是不是忽然明白了文件是怎么被監(jiān)聽的了呢
歡迎關(guān)注《前端陽(yáng)光》,加入技術(shù)交流群,加入內(nèi)推群
相關(guān)文獻(xiàn)
https://juejin.cn/post/6844903893785116680
https://www.shangmayuan.com/a/07ce3c507d7a4c478e37f1f0.html
