關於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就完成了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章