最懒的前端多语言策略(三)

之前在umi项目下弄了个umi-plugin-gtj,原理是在umi的生命周期onDevCompileDone下执行输出json逻辑,但发现有非常多的地方可以优化
回顾:
最懒的前端多语言策略(一)
最懒的前端多语言策略(二)

可见的不足

发现每次编译都会执行run(逻辑入口) -> fileDisplay(递归目录)
很容易发现只要有小小改动,都会走递归逻辑,这样肯定不行

旧逻辑的不足

尝试解决

1.于是我想在umi本身提供的onDevCompileDone里头看看有没返回,还真实看不出哪个是,刚提了个issue,估计也不会回的,先放弃在onDevCompileDone里做的想法


onDevCompileDone回调参数

2.于是我打算在onStart里搞,onStart是只执行一次,onDevCompileDone思路又先放弃,那能在里面写自定义监听逻辑了

fs.watch

简单的watch逻辑

fs.watch(entry, {
  recursive: true, // 递归
}, (event, filename) => {
  console.log(`${filename}文件发生更新`)
 // 执行逻辑
 run(`${entry}/${filename}`)
})

众所周知,watch回调是非常敏感的,即使只修改一个字母,可能也会触发4次回调,这样肯定不行,我们可以判断只在event为'change'时做逻辑

if(event !== ‘change')return

然而我发现我只在文件复制粘贴,什么内容没变都会触发watch,我觉得是修改时间也会影响,于是得直接判断文件内容,上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}文件发生更新`)
  // 执行逻辑
  run(`${entry}/${filename}`)
})

完成,只变动一次,但对于打代码极快的我,几秒操作可能执行很多次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}文件发生更新`)
  // 执行逻辑
  run(`${entry}/${filename}`)
})

自定义watch逻辑完成!

生成逻辑改动

因为逻辑从onDevCompileDone转移到onStart,同时我想将其单独
封装,于是改主体逻辑

const createCode = ({
   entry,
   output,
   increment,
   word,
}) = > {
  ...
  run()  // 第一次执行时调用
  return {
    run // 将run返回,方便多次调用
  }
}

在onStart里加上

api.onStart(() => {
  ...
  const { run } = createCode({
    entry,
    output,
    increment,
    word,
  })
  ...
  fs.watch...
})

细节改动

因为watch已经告诉我们是什么文件变化了,我们不用执行fileDisplay(递归文件夹),只需直接走readFileToObj(写入逻辑)就好了,run和readFileToObj也需要改动

// 开始逻辑, 有filename参数直接去写入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参数,直接写入
function readFileToObj(fReadName, value, callback, alone = false) {
    ...
     objReadline.on('close', () => {
      // 文件都读过了,写进生成文件
      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 // 计数还剩未读的文件数
  console.log('-----start-----')

  // 写入逻辑,多了个alone参数,直接写入
  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', () => {
      // 文件都读过了,写进生成文件
      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)
  
  // 递归执行,直到判断是文件就执行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()) {
                // 后缀不符合的跳过,并计数加一
                if (suffix.includes(path.extname(fileDir))) {               
                  readNum++
                  readFileToObj(fileDir, value)
                }
              }
              checkEnd()
            }
          })
        })
      }
    })
  }
  
  
  // 开始逻辑, 有filename参数直接去写入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}文件发生更新`)
      run(`${entry}/${filename}`)
    });
   
  })
 
  api.onDevCompileDone(({ stats }) => {
    
  });
};

改进后对比

递归只在onStart执行了一次,后续开发只根据变动文件做增量更新,大大减少了没必要的访问成本

思考

原本是想将watch逻辑放在外面脚本,在onStart利用child_process启动脚本,无奈并没触发到理想的效果,希望有知道的哥们指点一下

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 207,113评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,644评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,340评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,449评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,445评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,166评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,442评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,105评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,601评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,066评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,161评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,792评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,351评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,352评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,584评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,618评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,916评论 2 344