最懶的前端多語言策略(三)

之前在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里做的想法


onDevCompileDone回調(diào)參數(shù)

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)一下

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末迷帜,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子色洞,更是在濱河造成了極大的恐慌戏锹,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锋玲,死亡現(xiàn)場離奇詭異景用,居然都是意外死亡涵叮,警方通過查閱死者的電腦和手機(jī)惭蹂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來割粮,“玉大人盾碗,你說我怎么就攤上這事∫ㄆ埃” “怎么了廷雅?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長京髓。 經(jīng)常有香客問我航缀,道長,這世上最難降的妖魔是什么堰怨? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任芥玉,我火速辦了婚禮,結(jié)果婚禮上备图,老公的妹妹穿的比我還像新娘灿巧。我一直安慰自己,他們只是感情好揽涮,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布抠藕。 她就那樣靜靜地躺著,像睡著了一般蒋困。 火紅的嫁衣襯著肌膚如雪盾似。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天雪标,我揣著相機(jī)與錄音零院,去河邊找鬼购岗。 笑死,一個胖子當(dāng)著我的面吹牛门粪,可吹牛的內(nèi)容都是我干的喊积。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼玄妈,長吁一口氣:“原來是場噩夢啊……” “哼乾吻!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起拟蜻,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤绎签,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后酝锅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體诡必,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年搔扁,在試婚紗的時候發(fā)現(xiàn)自己被綠了爸舒。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡稿蹲,死狀恐怖扭勉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情苛聘,我是刑警寧澤涂炎,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站设哗,受9級特大地震影響唱捣,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜网梢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一震缭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧澎粟,春花似錦蛀序、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至啸盏,卻和暖如春重贺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工气笙, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留次企,地道東北人。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓潜圃,卻偏偏與公主長得像缸棵,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子谭期,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評論 2 355