手擼loader和plugin全解析

前言

咱們在使用webpack配置項目時會使用過各類各樣的loader和plugin,例如less-loader、file-loader、html-webpack-plugin、clean-webpack-plugin等等。可是咱們是否想過這些loader或plugin是如何編寫的呢?今天咱們就來了解一下如何實現的loader和plugin.也方便咱們之後編寫本身的loader或者pluginjavascript

什麼是loader

loader是文件加載器,可以加載資源文件,並對這些文件進行一些處理,諸如編譯、壓縮等,最終一塊兒打包到指定的文件中html

  • 處理一個文件可使用多個loader,loader的執行順序是和自己的順序是相反的,即最後一個loader最早執行,第一個loader最後執行。
  • 第一個執行的loader接收源文件內容做爲參數,其餘loader接收前一個執行的loader的返回值做爲參數。最後執行的loader會返回此模塊的JavaScript源碼

其實loader簡單來講就是一個函數,經過一個函數參數接受到源代碼,並在函數內部對源代碼做出變動,並最終返回源代碼前端

編寫loader

咱們就來實現一個能夠接受txt文件並對其進行首字母大寫的loader,由於這個沒啥用,咱們就單純的用做了解laoder編寫的過程。
第一步:新建項目並配置好webpackjava

npm init
npm install -D webpack webpack-cli
複製代碼

webpack.config.jsnode

const path = require('path');

module.exports = {
	mode: 'development',
	entry: {
		main: './src/index.js'
	},
	output: {
		path: path.resolve(__dirname, 'dist'),
		filename: '[name].js'
	}
}
複製代碼

src文件夾下index.js與index.txtwebpack

// index.js
import './index.txt';
// index.txt
複製代碼

第二步:編寫loader
新建一個loader文件夾存放本身編寫的loader,這裏咱們新建一個uppercaseLoader.js文件。web

module.exports = function (source) { 
    // 這裏的source表明的是源代碼
}
複製代碼

咱們是要將txt中字符串進行首字符大寫的轉換,接下來直接完善uppercaseLoader.js便可npm

module.exports = function(source) {
	return result = source.charAt(0).toUpperCase() + source.slice(1)
}
複製代碼

第三步:使用loader
要使用咱們本身編寫的loader,不是像其餘的loader同樣直接在module裏面配置就好了,還須要配合resolveLoader來使用。這樣配置的意義是在module中只用寫loader名稱,webpack會先到node_modules裏面找,找不到就去當前目錄下的loaders中去找。json

const path = require('path');
module.exports = {
	mode: 'development',
	entry: {
		main: './src/index.js'
	},
	resolveLoader: {
		modules: ['node_modules', './loaders']
	},
	module: {
		rules: [{
			test: /\.txt$/, // 專門處理txt文件
			use: [
				{
					loader: 'uppercaseLoader',
				},
			]
		}]
	},
	output: {
		path: path.resolve(__dirname, 'dist'),
		filename: '[name].js'
	}
}
複製代碼

package.json添加以下配置:  "script: { "build": "webpack"}"api

接下來運行npm run build,會生產一個dist文件,在dist文件下main.js中你會看到已經對txt文件下的字符串進行了首字母大寫轉換。

第四步:loader參數配置
loader一般還可使用參數,可使用loader-utils來讀取loader的參數,例如咱們如今只對首字母爲a的字符串進行大寫轉換,咱們能夠這麼作。

npm install --save-dev loader-utils
複製代碼

改寫webpack.config.js

...		
rules: [{
			test: /\.txt$/,
			use: [
				{
					loader: 'uppercaseLoader',
					options: {
						initial: 'a'
					}
				},
			]
		}]
複製代碼

uppercaseLoader.js

const loaderUtils = require('loader-utils');
module.exports = function(source) {
	const options = loaderUtils.getOptions(this); // 讀取配置
	if (options.initial === source.charAt(0)) {
		return result = source.charAt(0).toUpperCase() + source.slice(1)
	}
	return source
}
複製代碼

有時候咱們不止要return一個resource,若是想要返回err, 處理後源代碼,source,或者其餘內容,那麼可使用this.callback.

this.callback(  err: Error | null,  content: string | Buffer,  sourceMap?: SourceMap,  meta?: any );

module.exports = function (source) { 
   // ...
   this.callback(null, result);
}
複製代碼

若是想要在函數內部作異步處理那麼可使用this.async()

module.exports = function (source) { 
	const options = loaderUtils.getOptions(this);
  const callback = this.async(); // 聲明一下內部有異步操做
	setTimeout(() => {
	  let result = source
	  if (options.initial === source.charAt(0)) {
		   return result = source.charAt(0).toUpperCase() + source.slice(1)
	  }
		callback(null, result);
	}, 1000);
}

複製代碼

編寫loader須要注意的是不要使用箭頭函數,會致使this指向錯誤

什麼是plugin

plugin是一個類,使用時plugins(插件包)中單獨配置,每一項是一個 plugin 的實例,參數都經過構造函數傳入。在 Webpack 運行的生命週期中會廣播出許多事件,Plugin 能夠監聽這些事件,在合適的時機經過 Webpack 提供的 API 改變輸出結果,plugin讓 webpack 具備更多的靈活性,提高開發效率.

編寫plugin

咱們來實現一個向打包文件中添加一個md說明文檔功能的addMdWebpackPlugin
第一步:新建項目並配置好webpack
webpack.config.js

const path = require('path');
module.exports = {
	mode: 'development',
	entry: {
		main: './src/index.js'
	},
	output: {
		path: path.resolve(__dirname, 'dist'),
		filename: '[name].js'
	}
}
複製代碼

第二步:編寫plugin
plugin由於是一個類,那麼他的基本結構以下:

// addMdWebpackPlugin.js
class addMdWebpackPlugin {
	constructor(options){
		console.log(options) // options是插件傳入的參數
	}
	apply(compiler) { // compiler是webpack的實例}
}
module.exports = addMdWebpackPlugin;
複製代碼

compiler是在 Webpack 啓動時候被實例化的,做爲webpack的實例compiler包含了Webpack 環境全部的的配置信息,包含 options,loaders,plugins 這些信息。

結構寫好以後接下來完善plugin,由於咱們要在打包的文件中添加一個md文檔,那麼咱們就要用到compiler的hooks,由於hooks表明着鉤子函數,是webpack在執行過程當中必經的過程,在hooks裏面又定義了一些具體的時刻值。咱們這裏用到的是emit時刻,官網這樣描述emmit:Executed right before emitting assets to output dir.意思是在打包輸出dist以前執行。而且要注意的是emit是一個異步的時刻值。那麼咱們能夠這樣來寫:

class addMdWebpackPlugin {
	constructor(options){
		console.log(options) // options是插件傳入的參數
	}
	apply(compiler) { // compiler是webpack的實例
    compiler.hooks.emit.tapAsync('addMdWebpackPlugin', (compilation, cb) => {
			compilation.assets['describe.md']= {  // compilation.assets是打包生成的文件,能夠向其中添加內容
				source: function() {
					return 'hello word'
				},
				size: function() {
					return 21;
				}
			};
			cb();
		})
  }
}
module.exports = addMdWebpackPlugin;		
複製代碼

更多關於compiler的知識請看這裏webpack.js.org/api/compile…

第三步:使用plugin
plugin寫好以後,須要webpack中引入並使用插件

const addMdWebpackPlugin = require('./addMdWebpackPlugin');
module.exports = {
  ...// 省略
	plugins: [
		new addMdWebpackPlugin()
	],
}
複製代碼

執行npm run build,便可在打包完成的dist目錄中看到一個md文檔。這個過程進行了以下步驟:

  • wbepack啓動後讀取配置,運行new addMdWebpackPlugin()實例了一個addMdWebpackPlugin
  • 接着調用addMdWebpackPlugin.apply(compiler) 給插件實例傳入 compiler對象
  • 再經過compiler的鉤子函數hooks來拿到webpack執行的各個時刻,並在相應的時刻對進行某些操做

總結

經過上述的兩個例子,儘管兩個例子沒有什麼大的用處,編寫都也很是簡單,可是我也學習到了應該如何編寫loader和plugin,但願也能夠打開你們編寫loader和plugin的大門。webpack博大精深,若是想要寫出更加有效和實用的loader或plugin,咱們仍是須要不斷的學習。但這也讓咱們在之後遇到須要編寫本身的loader或者plugin的狀況下,不至於無從下手。 
仍是那句話,學習中分享,分享中學習,歡迎關注無畏前端!!

image.png
相關文章
相關標籤/搜索