loader承担的是翻译官的职责,利用其弥补了让webpack只能理解JavaScript和JSON文件的问题,从而可以处理其它类型的文件,所以loader对webpack的重要性不言而喻,所以学习构建一个loader是学习webpack的必经之路。在学习编写一个loader之前,要明确一下loader的职责:其职责是单一的,只需要完成一种转换。下面将逐步阐述选择loader开发中的几个关键点并实现一个loader。
一、Loader分类
loader是一个CommonJs风格的函数,接收输入的source后可通过同步或异步的方式进行处理,然后将内容进行输出。
1.1 同步Loader
同步loader指的是同步的返回转换后的内容。由于是在Node.js这样的单线程环境,所以转换过程会阻塞整个构建,构建缓慢,不适用于耗时较长的环境中。对于同步loader,主要有两种方法返回转换后的内容:return和this.callback.
return
利用return可直接返回转换后结果。
module.exports = function(source, map, meta){
// ...
// output为处理后结果
return output;
}
this.callback
该方法相比于return更加灵活,其参数主要有四个:
this.callback(
err: Error | null,
content: string | Buffer,
sourceMap?: SourceMap,
meta?: any
);
(1)第一个参数为无法转换原内容,Webpack会返回一个Error。
(2)第二个参数即为经过转换后的内容(为输出的内容)。
(3)指与编译后代码所映射的源代码,便于调试。为了在此loader中获取该sourceMap,则需要在创建的webpack做一下配置(以js为例,babel-loader会将基础ES6语法进行转换为ES5,通过devtool可以开启source-map):
// webpack.config.js
module.exports = {
// ...
module: {
rules: [
{
test: /\.js$/,
use: [
'test-loader',// 该loader即为自己构建的loader
{
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env'
]
}
}
]
}
]
},
devtool: 'eval-source-map',
}
(4)可以是任何东西,输出该参数,即可在下一个loader中获取并使用,例如通过各loader之间共享通用的AST,加速编译时间。
利用this.callback可返回传递多参数的结果。
module.exports = function(source, map, meta) {
// 处理后获得的结果output
const output = dealOperation(source);
this.callback(null, output, map, meta);
}
1.2 异步Loader
同步loader只适合于计算量小,速度快的场景,但是对于计量量大、耗时比较长的场景(例如网络请求),使用同步loader会阻塞整个构建过程,导致构建速度变慢,采用异步loader即可避免该问题。对于异步loader,使用this.async()可以获取到callback函数,该函数参数和同步loader中this.callback参数一致。
module.exports = function(content, map, meta) {
// 获取callback函数
const callback = this.async();
// 用setTimeout模拟该异步过程
setTimeout(() => {
// 处理后获得的结果output
const output = dealOperation(source);
callback(null, output, map, meta);
}, 100)
}
二、文件转化后类型
默认情况下,资源文件经转化后都是UTF-8格式编码的字符串,但是对于图片这样的文件经过转化后是二进制格式的内容,为了让loader支持接收二进制资源,需要设置raw(以图片资源为例进行展示)
// webpack.config.js
module.exports = {
// ...
module: {
rules: [
{
test: /\.(png|jpg|gif)$/,
use: [
'url-loader',
'raw-test-loader',// 自己的loader
]
}
]
}
}
// raw-test-loader.js
module.exports = function(source, map, meta) {
// 处理输入的资源
const output = dealOperation(source);
return output;
}
// 通过该属性告诉webpack该loader是否需要二进制数据
module.exports.raw = true;
三、options选项
对于webpack配置中,loader往往有一些options参数,对于自己编写的loader中为了获取options参数,官方推荐使用loader-utils包,利用该包即可获取options中参数,然后在loader中进行处理。
const loaderUtils = require('loader-utils');
module.exports = function (source, map, meta){
// 获取options
const options = loaderUtils.getOptions(this);
const output = dealOperation(source);
return output;
}
四、是否缓存
对于转换操作需要大量的计算,非常耗时,每次重新构建会让构建过程变的非常缓慢。webpack会默认缓存所有loader的处理结果,即要处理文件和其相关依赖没发生变化就会利用其缓存(注意loader除了this.addDependency里指定的依赖外,不应该有任何外部依赖)。通过this.cacheable可控制其是否进行缓存。
module.exports = function(source, map, meta) {
// 关闭缓存
this.cacheable(false);
return source;
}
五、实现一个loader
本节是loader实战,编写了一个用于字母大小写转换的loader,利用该loader能够实现将txt文件中字母的大小写转换,其loader内容及webpack.config.js相关配置如下所示(详细代码见github上代码)
// format-letters-loader.js
const loaderUtils = require('loader-utils');
const Lowercase2Uppercase = 'L2U';
const Uppercase2Lowercase = 'U2L';
module.exports = function (source, map, meta) {
let output = '';
// 获取options
const options = loaderUtils.getOptions(this);
const { formatType } = options;
switch(formatType) {
case Lowercase2Uppercase: {
output = source.toUpperCase();
break;
}
case Uppercase2Lowercase: {
output = source.toLowerCase();
break;
}
default: {
output = source;
}
}
this.callback(null, output, map, meta);
};
// webpack.config.js
module.exports = {
// ...
module: {
rules: [
{
exclude: /\.(css|js|html|png|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'asset',
}
},
{
loader: 'format-letters-loader',
options: {
formatType: 'U2L'
}
},
]
}
]
},
// 解析loader包是设置模块如何被解析
resolveLoader: {
modules: ['./node_modules', './loader'],// 告诉 webpack 解析loader时应该搜索的目录。
},
}
欢迎大家关注公众号(回复“深入浅出Webpack”获取深入浅出Webpack的pdf版本,回复“webpack04”获取本节的思维导图”)
本文分享自微信公众号 - 执鸢者(gh_4918fcc10eab)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
来源:oschina
链接:https://my.oschina.net/u/3630372/blog/4429302