如何⾃⼰编写一个Loader
⾃己编写一个Loader的过程是比较简单的,
Loader就是⼀个函数,声明式函数,不能⽤用箭头函数
拿到源代码,作进一步的修饰处理,再返回处理后的源码就可以了
简单案例
l 创建一个替换源码中字符串的loader
//index.js
console.log("hello world");
//replaceLoader.js
module.exports = function(source) {
console.log(source, this, this.query);
return source.replace(world,'大家好')
};
//需要⽤声明式函数,因为要上到上下文的this,用到this的数据,该函数接受一个参数
l 在配置文件中使用loader
//需要使用node核心模块path来处理路径
const path = require('path');
......
module: {
rules: [
{
test: /.js$/,
use: path.resolve(__dirname, "./loader/replaceLoader.js")
}
]
},
l 如何给loader配置参数,loader如何接受参数?
² this.query
² loader-utils
²
//需要使用node核⼼心模块path来处理路径
const path = require('path');
module: {
rules: [
{
test: /.js$/,
use: path.resolve(__dirname, "./loader/replaceLoader.js")
}
]
},
//webpack.config.js
.........
module: {
rules: [
{
test: /.js$/,
use: [
{
loader: path.resolve(__dirname, "./loader/replaceLoader.js"),
options: {
name: "你好吗"
}
}
]
}
]
},
//replaceLoader.js
//方式一: this.query获取参数
module.exports = function(source) {
//this.query 通过this.query来接受配置文件传递进来的参数
//return source.replace("world", this.query.name);
return source.replace("world", options.name);
}
//方式二: loader-utils获取参数,官方推荐
const loaderUtils = require("loader-utils");//官方推荐处理loader,query的⼯具
module.exports = function(source) {
//this.query 通过this.query来接受配置文件传递进来的参数
const options = loaderUtils.getOptions(this);
return source.replace("world", options.name);
}
l this.callback :如何返回多个信息,不止是处理好的源码呢,可以使用this.callback来处理
//replaceLoader.js
const loaderUtils = require("loader-utils");//官⽅方推荐处理理loader,query的⼯工具
module.exports = function(source) {
const options = loaderUtils.getOptions(this);
const result = source.replace("world", options.name);
this.callback(null, result);
};
//this.callback(
err: Error | null,
content: string | Buffer,
sourceMap?: SourceMap,
meta?: any );
l this.async:如果loader⾥⾯有异步的事情要怎么处理理呢
我们使⽤this.async来处理,他会返回this.callback
//replaceLoader.js
const loaderUtils = require("loader-utils");//官⽅方推荐处理理loader,query的⼯工具
module.exports = function(source) {
const options = loaderUtils.getOptions(this);
const result = source.replace("world", options.name);
this.callback(null, result);
};
//this.callback(
err: Error | null,
content: string | Buffer,
sourceMap?: SourceMap,
meta?: any
);
const loaderUtils = require("loader-utils");
module.exports = function(source) {
const options = loaderUtils.getOptions(this);
setTimeout(() => {
const result = source.replace("world", options.name);
return result;
}, 1000);
};
//先⽤setTimeout处理下试试,发现会报错
我们使⽤用this.async来处理理,他会返回this.callback
const loaderUtils = require("loader-utils");
module.exports = function(source) {
const options = loaderUtils.getOptions(this);
//定义一个异步处理,告诉webpack,这个loader⾥有异步事件,在里⾯调⽤下这个异步
//callback 就是 this.callback 注意参数的使⽤
const callback = this.async();
setTimeout(() => {
const result = source.replace("world", options.name);
callback(null, result);
}, 3000);
};
l 多个loader的使⽤
//replaceLoader.js
module.exports = function(source) {
return source.replace("软谋课堂", "word");
};
//replaceLoaderAsync.js
const loaderUtils = require("loader-utils");
module.exports = function(source) {
const options = loaderUtils.getOptions(this);
//定义⼀一个异步处理理,告诉webpack,这个loader⾥里里有异步事件,在⾥里里⾯面调⽤用下这个异步
const callback = this.async();
setTimeout(() => {
const result = source.replace("world", options.name);
callback(null, result);
}, 3000); };
//webpack.config.js
module: {
rules: [
{
test: /.js$/,
use: [
path.resolve(__dirname, "./loader/replaceLoader.js"),
{
loader: path.resolve(__dirname, "./loader/replaceLoaderAsync.js"),
options: {
name: "你好吗"
}
}
]
// use: [path.resolve(__dirname, "./loader/replaceLoader.js")]
}
]
}
顺序,自下⽽上,自右到左
l 处理loader的路径问题
resolveLoader: {
modules: ["node_modules", "./loader"]
},
module: {
rules: [
{
test: /.js$/,
use: [
"replaceLoader",
{
loader: "replaceLoaderAsync",
options: {
name: "软谋课堂"
}
}
]
}
]
},
..............
参考:loader API
https://webpack.js.org/api/loaders
如何⾃己编写一个Plugins
Plugin: 开始打包,在某个时刻,帮助我们处理一些什么事情的机制 plugin要比loader稍微复杂一些,在webpack的源码中,用plugin的机制还是占有非常大的场景,可以说plugin是webpack的灵魂
l 设计模式
l 事件驱动
l 发布订阅
plugin是一个类,⾥面包含一个apply函数,接受一个参数,compiler
案例:
l 创建copyright-webpack-plugin.js
- plugin实际是一个类(构造函数),通过在plugins配置中实例化进行调用
- 它在原型对象上指定了一个apply方法,入参是compiler对象
- 指定一个事件钩子,并调用内部提供的API
- 完成操作后,调用webpack 提供的callback方法
class CopyrightWebpackPlugin {
constructor() { }
// 将 `apply` 定义为其原型方法,此方法以 compiler 作为参数
apply(compiler) { }
}
module.exports = CopyrightWebpackPlugin;
l 配置文件里使⽤
const CopyrightWebpackPlugin = require("./plugin/copyright-webpack-plugin");
plugins: [new CopyrightWebpackPlugin()]
l 如何传递参数
//webpack配置⽂文件
plugins: [
new CopyrightWebpackPlugin({
name: "你好吗"
})
]
//copyright-webpack-plugin.js
class CopyrightWebpackPlugin {
constructor(options) {
//接受参数
console.log(options);
}
apply(compiler) { }
}
module.exports = CopyrightWebpackPlugin;
l 配置plugin在什么时刻进⾏
class CopyrightWebpackPlugin {
constructor(options) {
// console.log(options);
}
apply(compiler) {
//hooks.emit 定义在某个时刻 ,
// 指定要附加到的事件钩子函数
compiler.hooks.emit.tapAsync(
"CopyrightWebpackPlugin",
(compilation, cb) => {
// 使用 webpack 提供的 plugin API 操作构建结果
compilation.assets["copyright.txt"] = {
source: function() {
return "hello copy";
},
size: function() {
return 20;
}
};
cb();
} );
//同步的写法
//compiler.hooks.compile.tap("CopyrightWebpackPlugin", compilation => {
// console.log("开始了了");
//});
}
}
module.exports = CopyrightWebpackPlugin;
参考:compiler-hooks
https://webpack.js.org/api/compiler-hooks
实现插件的背景知识
由上面的步骤可知,插件功能的实现主要依赖于compiler和complation对象,而两者都是继承自Tapable对象。它暴露三种注册监听的方法Tapable对象主要是9种钩子:
const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");
其中同步四种,异步并行两种,异步串行3种。
同步钩子进行同步操作;异步钩子中进行异步操作。
compiler和compilation中的钩子都是来自这9种钩子。钩子的工作机制类似于浏览器的事件监听。
1)生成的钩子可以注册监听事件,其中同步钩子通过tap方法监听,异步钩子通过tapAsync(+回调函数)和tapPromise(+返回promise)进行监听。
2)还可以进行拦截,通过intercept方法。
3)对于监听事件的触发,同步钩子通过call方法; 异步钩子通过callAsync方法和promise