前言
我们在使用webpack配置项目时会使用过各种各样的loader和plugin,例如less-loader、file-loader、html-webpack-plugin、clean-webpack-plugin等等。但是我们是否想过这些loader或plugin是如何编写的呢?今天我们就来了解一下如何实现的loader和plugin.也方便我们以后编写自己的loader或者plugin
什么是loader
loader是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中
- 处理一个文件可以使用多个loader,loader的执行顺序是和本身的顺序是相反的,即最后一个loader最先执行,第一个loader最后执行。
- 第一个执行的loader接收源文件内容作为参数,其他loader接收前一个执行的loader的返回值作为参数。最后执行的loader会返回此模块的JavaScript源码
其实loader简单来说就是一个函数,通过一个函数参数接受到源代码,并在函数内部对源代码作出变更,并最终返回源代码
编写loader
我们就来实现一个可以接受txt文件并对其进行首字母大写的loader,因为这个没啥用,我们就单纯的用作了解laoder编写的过程。
第一步:新建项目并配置好webpack
npm init
npm install -D webpack webpack-cli
2
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'
}
}
2
3
4
5
6
7
8
9
10
11
12
src文件夹下index.js与index.txt
// index.js
import './index.txt';
// index.txt
2
3
第二步:编写loader
新建一个loader文件夹存放自己编写的loader,这里我们新建一个uppercaseLoader.js文件。
module.exports = function (source) {
// 这里的source代表的是源代码
}
2
3
我们是要将txt中字符串进行首字符大写的转换,接下来直接完善uppercaseLoader.js即可
module.exports = function(source) {
return result = source.charAt(0).toUpperCase() + source.slice(1)
}
2
3
第三步:使用loader
要使用我们自己编写的loader,不是像其他的loader一样直接在module里面配置就行了,还需要配合resolveLoader来使用。这样配置的意义是在module中只用写loader名称,webpack会先到node_modules里面找,找不到就去当前目录下的loaders中去找。
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'
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package.json添加如下配置: "script: { "build": "webpack"}"
接下来运行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'
}
},
]
}]
2
3
4
5
6
7
8
9
10
11
12
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
}
2
3
4
5
6
7
8
有时候我们不止要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);
}
2
3
4
如果想要在函数内部做异步处理那么可以使用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);
}
2
3
4
5
6
7
8
9
10
11
12
编写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'
}
}
2
3
4
5
6
7
8
9
10
11
第二步:编写plugin
plugin因为是一个类,那么他的基本结构如下:
// addMdWebpackPlugin.js
class addMdWebpackPlugin {
constructor(options){
console.log(options) // options是插件传入的参数
}
apply(compiler) { // compiler是webpack的实例}
}
module.exports = addMdWebpackPlugin;
2
3
4
5
6
7
8
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;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
更多关于compiler的知识请看这里https://webpack.js.org/api/compiler-hooks/
第三步:使用plugin
plugin写好之后,需要webpack中引入并使用插件
const addMdWebpackPlugin = require('./addMdWebpackPlugin');
module.exports = {
...// 省略
plugins: [
new addMdWebpackPlugin()
],
}
2
3
4
5
6
7
执行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的情况下,不至于无从下手。
还是那句话,学习中分享,分享中学习,欢迎关注无畏前端!!