【手把手教程】从零开始写个webpack小工具之测试代码报警器

563 阅读9分钟
原文链接: zhuanlan.zhihu.com

前言

测试:你这页面怎么不管传什么参数显示的东西都一样啊?
前端:嗯?我这不可能有错,自测联调都通过的,肯定是后端的锅。
后端:滚粗,不要有问题就甩我身上!
前端:凶什么,那我看一下……卧槽!测试代码传上去忘删了。

看了上面的对话,相信很多前端同学在写代码的时候都经历过类似的事情。

有的时候后端的接口还没有准备好数据或者接口参数没法自然获取到,我们就只能在原本返回或者传出参数的地方写上测试数据。而如果一个项目很庞大,这些测试代码就会这里留一坨那里留一块。如果忘删了,传到了测试环境被QA测出来还算好的,更可怕的是把测试代码传到了线上环境……

所以嘛,某天我一想,为什么不想办法防患于未然呢?

现在前端项目大部分都用webpack构建,而我们只要让文件在webpack打包的过程中检查到测试代码并提示出来(甚至可以检查到就直接终止编译)就好了。

webpack有个东西叫loader,这东西用来预处理文件,简单来讲他做的事情就好像:拿到一个文件A,里面有个内容“1234”,经过loader处理一下,变成“abcd”。

loader这东西用处很广,例如有的css的loader是用来给css文件加前缀的。举例来讲,我们在css文件里写一个transform: scale(1),这个文件经过css的loader以后,输出的css文件就在原基础上多了一行-webkit-transform: scale(1)。

而我们这回呢,就打算用这个loader干类似的事情:我们让js或者vue文件在经过loader的时候,检查一下里面是否含有特殊的测试代码标志(比如一行单独的// test,如果有,那么我们就修改一下这个代码文件,在里面加上一行console.warn(),这样这个文件在执行的时候就会多执行一行控制台输出,显示一些提示信息。

(所以本文也可以看做是教你从零开始写、发布、使用一个webpack loader的教程)。

如果代码里发现了一行// test,比如这样的:

那么页面在运行的时候,就会在控制台输出这类东西:

好,那么接下来正文开始。

正文

因为是从零开始,我假设你对怎么制作webpack loader并上传到npm一无所知,为了方便起见,我还假设你用的系统是mac os,再假设你的mac os上已经全局装了npm。

首先,我们要做的是新建一个项目文件夹(本文项目名和文件夹名为test-code-checker,读者需要起一个不一样的,否则不能发布到npm,会报重名错误),然后进入这个文件夹,命令行输入:

npm init

这行执行完以后,会问你一些package的信息,简单填一下或者一路回车,之后你的文件夹里就多了个package.json。

你可能会注意到其中一个问题是entry point,默认是index.js,我们这里就用默认的。

接下来我们在文件夹里创建一个index.js文件,然后写上以下代码:

module.exports = function (content, map, meta) {
    this.async()(null, content, map, meta)
}

这就是一个loader最基本的样子,content参数是传入loader的文件内容,最后执行this.async()(null, content, map, meta)里的content是经过这个loader输出以后的内容,如果什么都不处理就是原样输出。

考虑到我平时写vue的项目比较多,这个loader就先以能够支持vue和js这两种格式的文件为目标。

js文件很好处理,如果发现// test就直接在文件第一行加上一行console.warn()就好了。

vue文件有点特殊,因为console.warn()这种js代码只能写在vue文件的<script></script>标签里面(而通常vue文件里有<template><script><style>三种标签),所以需要在vue文件里找一下<script>标签,然后在他下面一行写上这个console.warn()就行了。

注意:某些vue文件里可能没有<script>标签,为方便起见,本文中我们假设最终应用教程里这个loader的vue文件都是有<script>标签的,否则将不可用。

接下来,我们要对源文件的内容进行分析,分析的目的有两个:

1、找到一行单独的// test,以及它下面的10行代码,用来在控制台里显示。

2、找到<script>,用来添加我们的console.warn()。

为了方便处理,我们将源文件的内容用换行做关键字来split并放到strArr里,这样我们就得到了一个源文件内容的数组,每一个数组元素就是源文件的一行文本:

module.exports = function (content, map, meta) {
  const strArr = content.split('\n')
  this.async()(null, content, map, meta)
}

之后我们声明一个数组testCodeArr,用来记录检测到的// test和他下面的10行代码的内容以及所在行数

接下来我们要声明一个scriptIndex变量,用来记录如果检测到了<script>标签,他的行数是多少,以便未来在它下面加上那行console.warn()。

我们还要声明两个变量:testZone和counter。在遍历源文件内容的strArr数组时,如果检测到// test关键字就把testZone变成true。当testZone变成true的之后,每遍历一个strArr内的元素就记录下他的内容以及index(代表这行代码的行数),每遍历一个strArr的内容counter就自加1,当counter === 11的时候停止,这样每当我们遇到// test的时候就会自动记录他下面的10行代码,最后代码如下:

module.exports = function (content, map, meta) {
  const strArr = content.split('\n')
  let testZone = false
  let counter = 0
  let scriptIndex = 0
  strArr.map((line, index) => {
    if (/<script>/.test(line)) {
      scriptIndex = index
    }        
    if (/^\s*\/\/ test\s*/.test(line)) {
      testZone = true
      testCodeArr.push({
          line: '// test',
          index
      })
      return
    }
    if (testZone) {
      counter++
      if (counter === 11) {
        counter = 0
        testZone = false
        return
      }
      testCodeArr.push({
        line,
        index
      })
    }
  })
  this.async()(null, content, map, meta)
}

那么好,至此我们已经有了所有的原材料,我们的testCodeArr里记录了一个文件里// test这一行以及他下面的10行代码,我们接下来只要只要在源文件里加上控制台输出语句把这些内容输出就大功告成了!

首先判断testCodeArr.length如果大于0,说明检查到了测试标志,那么就进行接下来的操作,拼接console.warn()和他的内容,并在合适的位置(vue文件就是<script>标签下面,js文件就是第一行)添加到原来的content里,最后输出出去,最终代码如下:

module.exports = function (content, map, meta) {
    const strArr = content.split('\n')
    const testCodeArr = []
    let testZone = false
    let counter = 0
    let scriptIndex = 0
    strArr.map((line, index) => {
        if (/<script>/.test(line)) {
            scriptIndex = index
        }
        if (/^\s*\/\/ test\s*/.test(line)) {
            testZone = true
            testCodeArr.push({
                line,
                index
            })
            return
        }
        if (testZone) {
            counter++
            if (counter === 11) {
                counter = 0
                testZone = false
                return
            }
            testCodeArr.push({
                line,
                index
            })
        }
    })
if (testCodeArr.length) {
    let warning = '发现测试代码标识!!!是不是有不该上传的代码?这里展示标识下面的10行代码:\n'
    testCodeArr.map(item => {
        warning += `line ${item.index + 1}\t\t ${item.line} \n`
    })
    let consoleWarn = `console.warn(${JSON.stringify(warning).replace(new RegExp('/', 'g'), '\\/')});`
    if (!scriptIndex) {
        strArr.push(consoleWarn)
    } else {
        strArr[scriptIndex] = `<script>${consoleWarn}`
    }
    content = strArr.join('\n')
}
    
    this.async()(null, content, map, meta)
}

注意:在拼接的时候你要时刻想着,你在拼的是一行js代码,而不是普通的文本!当这行console.warn(...)放回原js或者vue文件中,如果含有什么不当的信息是可能导致整个文件不能正常运行的。

(我在写的时候就遇到了一个小坑,console.warn(...)里输出的内容有个"</script>",这导致console.warn这行代码和它上面的<script>呼应了起来,最终输出的代码看着如下:

<script>

console.warn("...</script>")

....

</script>

而这使得最下面的原本对应的</script>失效了,所以这里要在拼接warn的内容里给所有的/替换成\\/。意思是,最终输出的/前面会带个转义符,转义后面的/,这样<\/script>这类标签在执行的时候将只作为普通的文本去执行而不是闭合标签)

至此,写代码的部分就已经结束了,接下来我们要把这个loader发布到npm上,之后你就可以方便的使用他了。

哦,对了,别忘了在项目里创建个README.md文件并写上这个loader的介绍和基本用法是什么,这样你的loader发布到npm上以后,别人在npm官网这个插件的页面里就能看到相关的介绍和用法。

接下来,你得在npm官网注册个账号,注册完以后,回到项目文件夹,输入:

npm adduser

输入的刚才注册的用户名,密码和邮箱。

成功的之后,命令行里应该提示你已经登录上了,之后你只需要输入:

npm publish

就完成发布了,发布完以后过一会儿(时长不定),到npm官网去搜自己的项目名,就可以看到了:

如果以后有任何更改想要发布,一定要记得在package.json中更改version版本号,否则是不能发布成功的。

试用一下

发布完以后我们当然要第一时间试验一下自己的小工具好不好用。

这里我们直接用vue-cli建一个vue项目来试验,首先全局装一下vue-cli:

npm install -g @vue/cli

然后用它创建个webpack模板项目:

vue init webpack my-project

进入到这个my-project项目文件夹,在命令行用npm安装我们刚发布的插件:

npm install test-code-checker --save-dev

安装完成以后,我们找到项目文件夹里的build文件夹,在里面有个webpack.base.conf.js文件,这个文件里你应该可以找到一个叫module的对象,里面有个叫rules的数组,大概是这个样:

这里面就都是loader了,如果test对应的正则表达式命中了这个文件名,就应用这个loader,那么我们只需要照葫芦画瓢,弄个vue的和js的应用我们的loader就行了,但因为已经有针对vue和js文件的loader了,所以我们要写成链式调用的形式,就像这样:

注意:loader的顺序很关键,因为loader是逆向链式的,也就是说后面的loader的结果会作为前面loader的源content传入,那么显然的,我们这个loader肯定是希望拿到第一手的vue和js文件,而不是处理过以后的文件,否则// test这种注释型标志就可能被其他的loader过滤掉了。

好了,接下来我们只需要在我们想试验的地方加上一行// test就行了,比如项目中原本就有的main.js,HelloWorld.vue,或者我们可以在项目里建一个js文件,比如叫util.js,并在里面写一个简单的函数,比如:

module.exports = function a() {
  // test
  console.log('我是a!')
}

然后在其他文件里import这个函数,即便不调用它,因为他被import了,webpack也会编译这个这个js文件,那么loader就会起效果: