node.js監聽文件變化

前言

隨着前端技術的飛速發展,前端開發也從原始的刀耕火種,向着工程化效率化的方向發展。在各類開發框架以外,打包編譯等技術也是層出不窮,開發體驗也是愈來愈好。例如HMR,讓咱們的更新能夠即時可見,告別了手動F5的狀況。其實現就是監聽文件變化自動調用構建過程。下面就關注下如何實現node監聽文件變化。html

場景

假定要監聽index.js,每當內容更改從新編譯。
咱們就用簡單的console來標識執行編譯。下面就是實現該功能。前端

node原生API

fs.watchFile

翻下node的文檔就會看到一個知足咱們需求的Apifs.watchFile(畢竟是文件相關的操做,很大可能就在fs模塊下面了)。node

fs.watchFile(filename[, options], listener)
複製代碼
  • filename 顯然就是文件名git

  • options 可選 對象 包含如下兩個屬性github

    • persistent 文件被監聽時進程是否繼續,默認true
    • interval 多長時間輪訓一次目標文件,默認5007毫秒
  • listener 事件回調 包含兩個參數api

    • current 當前文件stat對象
    • prev 以前文件stat對象

看完參數信息,不知道你們有沒有從其參數屬性中獲得點什麼特別的信息。特別是interval選項和listener中的回調參數。bash

監控filename對應文件,每當訪問文件時會觸發回調。框架

這裏每當訪問文件時會觸發,實際指的是每次切換以後再次進入文件,而後保存以後,不管是否作了修改都會出發回調。函數

另外輪詢事件和文件對象,是否是能夠猜想,其實現監聽的原理,固定時間輪詢文件狀態,而後將先後的狀態返回,將判斷交給使用者。
因此node也建議,若是要獲取文件修改,那麼須要根據stat對象的修改時間來進行對比,即比較 curr.mtime 和 prev.mtime。post

這樣就有點問題,咱們先看下例子,會更清晰一點。

const fs = require('fs')
const filePath = './index.js'
console.log(`正在監聽 ${filePath}`);
fs.watchFile(filePath, (cur, prv) => {
    if (filePath) {
        // 打印出修改時間
        console.log(`cur.mtime>>${cur.mtime.toLocaleString()}`)
        console.log(`prv.mtime>>${prv.mtime.toLocaleString()}`)
        // 根據修改時間判斷作下區分,以分辨是否更改
        if (cur.mtime != prv.mtime){
            console.log(`${filePath}文件發生更新`)
        }
    }
})
複製代碼

而後測試結果以下:

// 運行 
node watch1.js
// 一、訪問index.js 不作修改,而後保存
// 二、切換文件,再次訪問,不作修改,只報錯
// 三、編輯內容,並保存
複製代碼

能夠看到一、2兩步,並無實際修改內容,然而咱們並無辦法區分。只要你是切換以後再保存,修改時間戳mtime就發生變化。
另外響應時間真的很慢,畢竟是輪詢。

對於這些問題,其實官網也給了一句話:

Using fs.watch() is more efficient than fs.watchFile and fs.unwatchFile. fs.watch should be used instead of fs.watchFile and fs.unwatchFile when possible.
複製代碼

能用fs.watch的狀況就不要用watchFile了。一是效率,二是不能準確獲知修改狀態 三是隻能監聽單獨文件
對於實際開發過程當中,顯然咱們想要關注的是源文件夾的變更。

fs.watch

首先用法以下:

fs.watch(filename[, options][, listener])
複製代碼

跟fs.watchFile比較相似。

  • filename 顯然就是文件名

  • options 可選 對象或者字符串 包含如下三個屬性

    • persistent 文件被監聽時進程是否繼續,默認true
    • recursive 是否監控全部子目錄,默認false 即當前目錄,true爲全部子目錄。
    • encoding 指定傳遞給回調事件的文件名稱,默認utf8
  • listener 事件回調 包含兩個參數

    • eventType 事件類型,rename 或者 change
    • filename 當前變動文件的文件名

options若是是字符串,指的是encoding。

監聽filename對應的文件或者文件夾(recursive參數也體現出來這一特性),返回一個fs.FSWatcher對象。

該功能的實現依賴於底層操做系統的對於文件更改的通知。 因此就存在一個問題,可能不一樣平臺的實現不太相同。 以下示例1:

const fs = require('fs')
const filePath = './'    
console.log(`正在監聽 ${filePath}`);
fs.watch(filePath,(event,filename)=>{
    if (filename){
        console.log(`${filename}文件發生更新`)
    }
})
複製代碼

一個比較明顯的優點就體現出來了:響應比較及時,相比於輪詢,效率確定更高。

不過這樣修改並保存的時候回發現一樣有點問題。 直接保存,顯示兩次更新
修改文件以後,一樣顯示兩次更新(mac系統上是兩次,其餘系統可能有所差異) 這樣多是於操做系統對文件修改的事件支持有關,在保存的時候出發了不止一次。
下面聚焦於回調事件的參數,event對應事件類型,是否能夠判斷事件類型爲change呢,才執行呢,忽略空保存。

const fs = require('fs')
const filePath = './'    
console.log(`正在監聽 ${filePath}`);
fs.watch(filePath,(event,filename)=>{
    console.log(`event類型${event}`)
    if (filename && event == 'change') {
        console.log(`${filename}文件發生更新`)
    }
})
複製代碼

不過實際上,空的保存event也是change,另外不一樣平臺event的實現可能也有所不一樣。這種方式要pass掉。

校驗變動時間

顯然從上面的例子能夠看到,變動時間依然不可控。由於每次保存,node對應stat對象依然會修改。

對比文件內容

只能選擇這種方式來判斷是不是否更新。例如md5:

const fs = require('fs'),
    md5 = require('md5');
const filePath = './'    
let preveMd5 = null

console.log(`正在監聽 ${filePath}`);
fs.watch(filePath,(event,filename)=>{
    var currentMd5 = md5(fs.readFileSync(filePath + filename))
    if (currentMd5 == preveMd5) {
        return
    }
    preveMd5 = currentMd5
    console.log(`${filePath}文件發生更新`)
})
複製代碼

先保存當前文件md5值,每次文件變化時(即保存操做響應以後),每次都獲取文件的md5而後進行對比,看是否發生變化。
不過這樣能夠看到,當初次保存時,都會執行一次,由於初始值爲null的緣故。這樣能夠加個兼容,根據是否第一次保存來判斷好了。

優化

對於不一樣的操做系統,可能保存時觸發的回調不止一個(mac上到沒出現)。爲了不這種實時響應對應的頻繁觸發,能夠引入debounce函數來保證性能。

const fs = require('fs'),
    md5 = require('md5');
let preveMd5 = null,
    fsWait = false
const filePath = './'    
console.log(`正在監聽 ${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
        console.log(`${filePath}文件發生更新`)
    }
})
複製代碼

結束語

到這裏,node監聽文件的實現就結束了。作個學習筆記,來作個參考記錄。實現起來並不難,可是要實際應用的話須要考慮的方面就比較多了。仍是推薦開源框架node-watch、chokidar等,各方面實現的都比較完善。更多請轉個人博客

參考文章

node文檔
How to Watch for Files Changes in Node.js
Nodejs Monitor File Changes

相關文章
相關標籤/搜索