手撸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
相关文章
相关标签/搜索