关于webpack的loader小教程:如何删除代码中的console

关于webpack的loader小教程:如何删除代码中的console

在开发环境中,我们经常会加入很多console.log来做代码的调试,但是我们并不希望当项目上线后,还会有打印的值,因此我们需要将这些console在上线前全部删掉。虽然webpack4中已经集成了去除console的功能,但webpack3没有这个功能,需要我们自己去处理。
在这里插入图片描述
如果有浏览过 webpack 官网的同学一定见过这张图,这是webpack官方对自己的功能描述图。webpack 能把左侧各种类型的文件(在webpack中 它们都是模块)打包为右边被通用浏览器支持的文件。

什么是 Loader ?

在这里插入图片描述
在撸一个 loader 前,我们需要先知道它到底是什么。本质上来说,loader 就是一个 node 模块,在 webpack 的定义中,loader 导出一个函数,loader 会在转换源模块(resource)的时候调用该函数。在这个函数内部,我们可以通过传入 this 上下文给 Loader API 来使用它们。

把 webpack 想像成一个工厂,loader 就是一个个身怀绝技的流水线工人,有的会处理 svg,有的会压缩 css 或者图片,有的会处理 less,有的会将 es6 转换为 es5。

回顾一下头图左边的那些模块,他们就是所谓的源模块,会被 loader 转化为右边的通用文件,因此我们也可以概括一下 loader 的功能:把源模块转换成通用模块。

下面我来展示一个loader的基础小功能,字符串转换

基本示例:

// 如果有配置项,则会挂载到this上
module.exports = function (source) { // source 为引入文件的源代码(内容)
  return source.replace('aaaaa', 'bbbbb') // 讲代码中所有的'aaaaa'转换为'bbbbb'
}

当然实际的loader并不会这么粗暴的去做转换,这里仅仅是作为一个入门案例
注意:我们删除console的loader一定不能使用正则等其他方式,因为正则删除的时候,会将匹配到的内容全部删除,可能会将我们写在字符串中的相同内容一起删掉
例:

console.log('我们想要删除的console')
alert('这是一个字符串 console') // 如果是通过正则来做删除,那么连此处的字符串中的console也会被一起删掉,这是错误的

正式开始

安装依赖

下面我们正式开始写一个可以删除代码中console的loader,首先我们需要做一些前置工作,下载我们在开发中所需要的一些依赖

  • @babel/parser ---- 帮助我们分析代码,并将代码转换为抽象语法树(AST)
  • @babel/traverse ---- 帮助我们对抽象语法树进行遍历
  • @babel/generator ---- 将抽象语法树转换回js代码 注: 这里也可以使用@babel/core,任选一个即可,只是使用上会稍有区别
  • @babel/types ---- 对具体的AST节点进行增删改查

书写测试代码

console.log('测试测试')

因为我们的主要目的是删除console,因此,在此处就不再书写过多的代码,我们直奔主题,删掉它!

loader编写

首先,我们创建一个deleteConsoleLoader.js,并将我们需要的依赖全部导入进来

const parser = require('@babel/parser') //将源代码解析成AST
const traverse = require('@babel/traverse').default  //对AST节点进行递归遍历,生成一个便于操作、转换的path对象
const generator = require('@babel/generator').default //将AST解码回js代码
const bableTypes = require('@babel/types')  //对具体的AST节点进行增删改查

module.exports = function (source) {
	const ast = parser.parse(sourceStr, { sourceType: 'module' }) //支持ES6的module方式
	console.log(ast); //打印一下我们的ast,看看他是个什么东西
	return null
}

打印结果:

Node {
  type: 'File',
    start: 0,
      end: 28,
        loc:
  SourceLocation {
    start: Position { line: 1, column: 0 },
    end: Position { line: 1, column: 28 }
  },
  errors: [],
    program:
  Node {
    type: 'Program',
      start: 0,
        end: 28,
          loc: SourceLocation { start: [Position], end: [Position] },
    sourceType: 'module',
      interpreter: null,
        body: [[Node]], // 这里就是我们的代码节点,可以看到只有一个node节点,就是刚刚我们测试代码中的console
          directives: []
  },
  comments: []
}

我们继续通过console.log(ast.program.body)把这个body打印出来,看看它的详细内容

Node {
  type: 'ExpressionStatement', // 代表是一个表达式语句,我们的console.log()
    start: 0,
      end: 28,
        loc: SourceLocation { start: [Position], end: [Position] },
  expression:
  Node {
    type: 'CallExpression', // 这里是它的具体类型
      start: 0,
        end: 28,
          loc: [SourceLocation],
            callee: [Node],
              arguments: [Array]
  }
}

那么下面就好办了,通过@babel/traverse,我们可以很轻松的遍历AST,同时,它还为我们提供了很方便的获取某种类型节点的方法,下面放上代码

const parser = require('@babel/parser') //将源代码解析成AST
const traverse = require('@babel/traverse').default  //对AST节点进行递归遍历,生成一个便于操作、转换的path对象
const generator = require('@babel/generator').default //将AST解码生成js代码
const bableTypes = require('@babel/types')  //对具体的AST节点进行增删改查

module.exports = function (source) {
	const ast = parser.parse(source, { sourceType: 'module' })
	traverse(ast, { // 对ast进行遍历
    	CallExpression (path) { // 如果节点类型为CallExpression ,则会执行此函数,我们的console的type就是这个
			console.log(path.node.callee) // 在这里,我们打印path.node.callee
    	}
  })
}  

结果:

{
  type: 'MemberExpression', // 这是整个console.log的类型
    start: 0,
      end: 11,
        loc:
  SourceLocation {
    start: Position { line: 1, column: 0 },
    end: Position { line: 1, column: 11 }
  },
  object:
  Node {
    type: 'Identifier', // 这里是console和log的类型
      start: 0,
        end: 7,
          loc:
    SourceLocation { start: [Position], end: [Position], identifierName: 'console' },
    name: 'console' // 这里可以看到,是console.log的第一部分,log在下面的第二部分
  },
  property:
  Node {
    type: 'Identifier',
      start: 8,
        end: 11,
          loc:
    SourceLocation { start: [Position], end: [Position], identifierName: 'log' },
    name: 'log'
  },
  computed: false
}

有了以上的铺垫,我们就可以完成最后一步了,在上面那些步骤完成后,我们就获得了删除console所需要的所有条件
接下来,我们只需要将console所对应的类型及name作为删除的条件,就可以将代码中所有的console.log删除掉
直接上代码

const parser = require('@babel/parser') //将源代码解析成AST
const traverse = require('@babel/traverse').default  //对AST节点进行递归遍历,生成一个便于操作、转换的path对象
const generator = require('@babel/generator').default //将AST解码生成js代码
const bableTypes = require('@babel/types')  //对具体的AST节点进行增删改查

module.exports = function (source) {
  const ast = parser.parse(sourceStr, { sourceType: 'module' })
  traverse(ast, {
    CallExpression (path) {
      // 删除console
      // 使用bableTypes 来对node节点的类型做判断,如果节点的整体类型为MemberExpression,并且子节点object的类型为Identifier,同时节点中的name又为console
      if (bableTypes.isMemberExpression(path.node.callee) && bableTypes.isIdentifier(path.node.callee.object, { name: "console" })) {
        path.remove() // 那么将这个节点删除掉
      }
    }
  })
  let output = generator(ast, {}); // 通过@babel/generator将AST重新解码回js
  return output.code // 最后将解码好的代码返回,给下一个loader使用
}

loader开发完成

那么一个简单的删除console的loader就开发完成了,为了验证,我们可以对比使用loader前后打包代码的区别
使用前:

/*! no static exports found */
/***/ (function(module, exports) {

eval("console.log('测试测试');\n\n//# sourceURL=webpack:///./src/index.js?");

/***/ })

/******/ });

使用后:

/***/ (function(module, exports) {

eval("\n\n//# sourceURL=webpack:///./src/index.js?");

/***/ })

/******/ });

可以看到console.log已经被删除掉了,那么我们的loader就完成了

发布了2 篇原创文章 · 获赞 25 · 访问量 220

猜你喜欢

转载自blog.csdn.net/qq_34790741/article/details/104405810