最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 从被测试大佬鄙视出发,封装一个wepback插件

    正文概述 掘金(菜菜驴)   2020-12-13   375

    从被测试大佬鄙视出发,封装一个wepback插件

    前言

    前段时间上线活动,测试大佬经常找我,我说发生肾么事了?
    原来是出现了好几次前端 mock 数据带到测试环境的问题,使得测试大佬仇恨值++,差点要来给我一套化挥发闪电五连鞭.
    每次build的时候,心里都很慌,生怕手写的mock数据带到测试环境.
    我寻思,作为一个程序猿,这种事我应该耗子尾汁,不应该每次都手动的去检查这个问题,所以也一直在思考怎么去解决这个问题. 经过多方面咨询和自己的思考,最终得到两个比较好的思路:

    1. 通过严谨,高度的封装,将所有的请求封装在一起,然后统一在请求层面去配置mock=true ,使用mock接口,build的时候,强制所有的接口都自动使用正式数据.

    2. mock数据处,手动增加约定的特殊注释,然后写一个webpack插件在build的时候去检测是否存在这种注释,如果存在,则直接抛出异常禁止打包.

    两者对比下来,其实方法一更好些,因为不需要额外去搞webpack插件,而且代码写起来很好看.
    但是我还是采用的方法二.因为我这边业务不太合适集中把接口封装起来.而且我还有点小私心,想熟悉下wepback的插件开发.

    理解webpack工作流程

    翻了很多资料,搞懂了webpack的大致的工作流程,分享下比较精髓的一段话:

    下面这段引用自Webpack 从零入门到工程化实战 26章

    初步尝试

    通过上边的理解,我大概有了思路,我要做的事是在build前拿到由代码组成的字符串,然后对这个字符串通过正则进行遍历,如果查出了约定好的特殊mock注释,则抛出警告打断build,于是写出以下代码:

    //webpack.config.js
    // 使用插件
    const WebpackMocWarnkPlugin = require('webpack-mock-warn')
    module.exports = {
      // ...
      plugins: [
        new WebpackMocWarnkPlugin(),
      ],
    }
    // webpackMocWarnkPlugin/index.js 插件代码
    
    module.exports = class WebpackMockWarnPlugin {
      apply(compiler) {
        compiler.plugin('emit', (compilation, callback) => {
          // compilation.chunks存放着所有的代码块
          compilation.chunks.forEach((chucnk) => {
            const chunkCode = chunk.entryModule._source._value
            //.... 对chunkCode进行遍历匹配
          })
        })
      }
    }
    

    但是这样写发现webpack警告说compiler.plugin这个api已经不建议使用,需要更换api了.

    再次尝试

    上边webpack工作流程中已经说了,现在(webpack4)应该先去使用compiler合适的钩子,拿到compilation,然后再去用compilation中合适的钩子去拿到代码块,钩子这块还挺绕的,看了好久资料,最后还是求助大佬才解决了疑惑.于是又有写出了以下代码:

    // webpackMocWarnkPlugin/index.js 
    
    module.exports = class WebpackMockWarnPlugin {
      apply(compiler) {
    
        // compilation 这个钩子表示compilation创建成功之后的回调,参数就是热乎乎的compilation
        compiler.hooks.compilation.tap('webpackMockWarnPlugin', (compilation) => {
          compilation.hooks.afterChunks.tap('af', (chunks) => {
            this.testChunk(chunks) // 遍历处理chunks的代码
          })
        })
        // 完成编译和封存编译产出之后的回调
        compiler.hooks.afterCompile.tap('afcompile', this.throwWarn) //如果发现存在特殊标记则抛出错误
      }
    }
    

    解释下上边使用的compiler.hooks.afterCompile,因为可能会存在多个特殊标记,所以这里需要等所有的代码块都处理完,才能拿到所有的结果,然后在这个钩子里去判断,一起抛出

    进行优化

    主要问题大致解决了,剩余一些边边角角,尽量的去优化一下,贴出最后实现的代码:

    // webpackMocWarnkPlugin/index.js 
    const { red, cyan, yellow } = require('colorette') //这个插件是用来在控制台上输出多种颜色代码的,这个也是看wepbakc-cli的源码发现的 还是挺好玩的
    const wanrns = []
    module.exports = class WebpackMockWarnPlugin {
    
      // 这里允许使用者去自定义特殊标记 或者直接去自己写正则
      constructor(options = {}) {
        this.mockReg = options.mockReg
        this.mockFlag = options.mockFlag || 'mock'
      }
      apply(compiler) {
        compiler.hooks.compilation.tap('webpackMockWarnPlugin', (compilation) => {
          compilation.hooks.afterChunks.tap('af', (chunks) => {
            this.testChunk(chunks)
          })
        })
        compiler.hooks.afterCompile.tap('afcompile', this.throwWarn)
      }
      testChunk(chunks){
        chunks.forEach((chunk) => {
          const reg =
            this.mockReg ||
            new RegExp(`(\/\*)\s*${this.mockFlag}|\/\/ *${this.mockFlag}`, 'g')
          const chunkCode = chunk.entryModule._source._value
          // 先生成行数数组.每个元素的index表示当前行数,每个元素的index表示所在行,内容是所在的index
          const rows = [0]
          for (let i = 0; i < chunkCode.length; i++) {
            if (chunkCode[i] === '\n') rows.push(i)
          }
          var regExec = null
          while ((regExec = reg.exec(chunkCode)) !== null) {
            // 若匹配到mock,则取出行数 / 上一行,下三行之内的代码方便检阅
            if (regExec[0]) {
              // 循环遍历所在行数
              for (let index in rows) {
                if (rows[index] >= regExec.index) {
                  let content = ''
                  let contentWrap = 5
                  let endIndex = chunkCode.length
                  let startIndex = 0
                  for (let i = regExec.index; i >= 0; i--) {
                    if (chunkCode[i] === '\n') {
                      startIndex = i
                      break
                    }
                  }
                  for (let i = regExec.index; i <= chunkCode.length - 1; i++) {
                    if (contentWrap === 0) {
                      endIndex = i
                      break
                    }
                    if (chunkCode[i] === '\n') {
                      contentWrap--
                    }
                  }
                  content = chunkCode.slice(startIndex, endIndex)
                  wanrns.push({
                    row: index || 1,
                    path: chunk.entryModule._source._name,
                    content,
                  })
                  break
                }
              }
            }
          }
        })
      }
      throwWarn(){
        if (wanrns.length !== 0) {
          let errorStr = `${red(
            `检测到存在${wanrns.length}处mock数据,请删除后再次尝试操作:
            如果你想使用自定义捕获mock标记,请配置 mockFlag , 也可以配置 mockReg 自定义捕获mock正则
            `
          )}\n`
          for (let el of wanrns) {
            errorStr += yellow(
              `path: ${el.path}\nrow: ${el.row}\ncontent: ${cyan(el.content)}\n\n`
            )       
          }
          console.error(red(errorStr))
          process.exit(1);//结束进程抛出错误
        }
      }
    }
    
    

    发布至npm

    修改下pagejson:

    {
      "name": "xxx",
      "version": "1.0.0",
      "description": "When build, it detects the mock data and throws a warning",
      "main": "./src/index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "publishConfig": {
        "registry": "http://registry.npmjs.org"
      },
      "keywords": [
        "webpack",
        "mock"
      ],
      "author": "xxx <xxxx.com>",
      "license": "ISC",
      "dependencies": {
        "colorette": "^1.2.1",
        "webpack-cli": "^4.2.0"
      }
    }
    

    再编辑下readme文件
    npm publish
    大功告成!

    结语

    其实到最后,还遗留了2个问题:

    1. 现在最后是调用nodeprocess.exit(1)直接在命令行抛出错误,这很不优雅,很难被称为warn,我其实理想的是直接终止掉webpack的打包而不抛出错误,但是没找到好的解决方案.
    2. html/css注释进行适配,这个暂时没有需求,所以没有费力去搞,而且中间会涉及到loader转义后的html/css,而不是编译前的,应该也要费一番力气.

    这个小东西也花费了我好几天的摸鱼时间,开发过程中遇到的问题,比上边描述的要多得多.

    我个人研究东西总是要有一个源头,就是说我要先知道我想干嘛,然后再去由这个进一步去研究.如果直接去学习某种技术,总是会迷失.从业务问题出发,到开发webpack插件去解决问题.这种体验还是挺好的.

    当然,以小见大,理解了webpack的工作流程,也算有所收获.

    深刻感觉到了,提出一个好问题,或许比解决这个问题更重要.

    我是菜菜驴,江湖人称驴渣.感谢你阅读我的分享!


    起源地下载网 » 从被测试大佬鄙视出发,封装一个wepback插件

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元