美文网首页
最懒的前端多语言策略(三)

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

作者: 你的时间非常值钱 | 来源:发表于2020-08-07 19:58 被阅读0次

    之前在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启动脚本,无奈并没触发到理想的效果,希望有知道的哥们指点一下

    相关文章

      网友评论

          本文标题:最懒的前端多语言策略(三)

          本文链接:https://www.haomeiwen.com/subject/jylqdktx.html