手写Loader
Loader 本质上是导出函数的 JavaScript 模块,而该模块导出的函数被称为 Normal Loader,在开发 Loader时,我们可以在导出的函数上添加一个 pitch 属性,它的值也是一个函数。该函数被称为 PitchingLoader。Loader执行时会先执行Pitching Loader再执行Normal Loader, Pitching Loade的执行顺序是 从左到右,而 Normal Loader 的执行顺序是 从右到左。
借用他人示意图如下:
开发之前需要明确的点
- 首先明确loader默认导出一个函数,接受匹配到的文件资源字符串和SourceMap
- 处理异步loader时,需要通过loader本身提供的回调函数callback
- 加载本地loader的方法是通过webpack提供的resolveLoader属性,来告诉webpack从哪里解析本地loader
- 可以通过工具loader-utils的getOptions方法获得参数,通过schema-utils的validate方法来检验获得的参数是否符合规则
const { getOptions } = require("loader-utils");
const { validate } = require("schema-utils");
const schema = require("./schema.json");
module.exports = function (source, map) {
const options = getOptions(this);
const configuration = { name: "Loader Name"};
validate(schema, options, configuration);
//省略其他代码
}
手写Plugin
在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过Webpack提供的API改变输出结果。plugin的本质是类;我们在定义plugin时,其实是在定义一个类。
//plugins/MyPlugin.js
class MyPlugin {
constructor(options) {
console.log("Plugin被创建了");
console.log(options);
this.options = options;
}
apply (compiler) {
//注册完成的钩子
compiler.hooks.done.tap("MyPlugin", (compilation) => {
console.log("compilation done");
});
}
}
//webpack.config.js
module.exports = {
plugins: [
new MyPlugin({ title: 'MyPlugin' })
],
}
我们在构建插件时就能通过options获取配置信息,对插件做一些初始化的工作。在构造函数中我们发现多了一个apply函数,它会在webpack运行时被调用,并且注入compiler对象;其工作流程如下
- webpack启动,执行new myPlugin(options),初始化插件并获取实例
- 初始化complier对象,调用myPlugin.apply(complier)给插件传入complier对象
- 插件实例获取complier,通过complier监听webpack广播的事件,通过complier对象操作webpack
这里又有一个compilation对象,它和上面提到的compiler对象都是Plugin和webpack之间的桥梁:
- compiler对象包含了 Webpack 环境所有的的配置信息。这个对象在启动 webpack
时被一次性建立,并配置好所有可操作的设置,包括 options,loader 和 plugin。当在 webpack
环境中应用一个插件时,插件将收到此 compiler 对象的引用。可以使用它来访问 webpack 的主环境。 - compilation对象包含了当前的模块资源、编译生成资源、变化的文件等。当运行webpack
开发环境中间件时,每当检测到一个文件变化,就会创建一个新的 compilation,从而生成一组新的编译资源。compilation
对象也提供了很多关键时机的回调,以供插件做自定义处理时选择使用。
了解了compiler和compilation的区别,我们就来尝试一个简单的示例插件,在打包目录生成一个filelist.md文件,文件的内容是将所有构建生成文件展示在一个列表中:
class FileListPlugin {
apply(compiler){
compiler.hooks.emit.tapAsync('FileListPlugin', (compilation, callback)=>{
var filelist = 'In this build:\n\n';
// 遍历所有编译过的资源文件,
// 对于每个文件名称,都添加一行内容。
for (var filename in compilation.assets) {
filelist += '- ' + filename + '\n';
}
// 将这个列表作为一个新的文件资源,插入到 webpack 构建中:
compilation.assets['filelist.md'] = {
source: function() {
return filelist;
},
size: function() {
return filelist.length;
}
};
callback();
})
}
}
module.exports = FileListPlugin
我们这里用到了assets对象,它是所有构建文件的一个输出对象,打印出来大概长这样:
{
'main.bundle.js': { source: [Function: source], size: [Function: size] },
'index.html': { source: [Function: source], size: [Function: size] }
}
我们手动加入一个filelist.md文件的输出;打包后我们在dist文件夹中会发现多了这个文件:
In this build:
- main.bundle.js
- index.html