loader
// 只在 test 和 文件名匹配 中使用正则表达式 // 在 include 和 exclude 中使用绝对路径数组 // 尽量避免 exclude,更倾向于使用 include
loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块 (webpack 自身只理解 JavaScript)
- 导出为函数的javascript模块
- 链式调用 把上一个loader产生的结果或资源文件放进去
加载相应的资源文件 loader webpack 根据正则表达式,来确定应该查找哪些文件,并将其提供给指定的 loader
module.rules
{ test: Condition }:匹配特定条件。一般是提供一个正则表达式或正则表达式的数组,但这不是强制的。
{ include: Condition }:匹配特定条件。一般是提供一个字符串或者字符串数组,但这不是强制的。
{ exclude: Condition }:排除特定条件。一般是提供一个字符串或字符串数组,但这不是强制的。
module.resource
{ and: [Condition] }:必须匹配数组中的所有条件
{ or: [Condition] }:匹配数组中任何一个条件
{ not: [Condition] }:必须排除这个条件
module:{
rules:[
{
test: /\.jsx?$/,//匹配条件
include: [
path.resolve(__dirname, "app")
],//test 和 include 具有相同的作用,都是必须匹配选项
exclude: [
path.resolve(__dirname, "app/demo-files")
], //不匹配选项
use:[{
loader:'babel-loader',//应该应用的 loader,解析匹配文件
options/*loader的属性*/: {
presets: ["es2015"]
},
}],
resource: { and: [ /* 条件 */ ] }, //所有条件都匹配时才匹配
resource: [/* 条件 */],
resource: {or:[/* 条件 */]},//任意条件匹配时匹配,默认是一个数组
resource: {not:[/* 条件 */]},//条件不匹配时
}
],
noParse:[
/special-library\.js$/
],
// 不解析这里的模块
}
复制代码
{test:/\.css$/,use:['style-loader','css-loader']}
复制代码
loader API
loader:一个导出为函数的 JavaScript 模块。loader runner 会调用这个函数,函数的 this 上下文将由 webpack 填充 如果是单个处理结果,可以在同步模式中直接返回。 如果有多个处理结果,则必须调用 this.callback()。、 在异步模式中,必须调用 this.async(),来指示 loader runner 等待异步结果,它会返回 this.callback() 回调函数,随后 loader 必须返回 undefined 并且调用该回调函数。
//同步loader
//1.单结果
function someSyncOperaty(){
}
function someAsyncOperaty(){
}
module.exports = function(content, map, meta){
return someSyncOperaty(content);
};
//2.多结果
module.exports = function(content, map, meta){
this.callback(null,someSyncOperaty(content),map,meta);
return;//当调用callback时,总返回undefined
};
//异步loader
// 1.单结果
module.exports = function(content, map, meta){
var callback = this.async();
someAsyncOperaty(content,function(err,result){
if(err) return callback(err);
callback(null,result,map,meta);
})
}
//多结果
momdule.exports = function(content, map, meta){
var callback = this.async();
someAsyncOperaty(content,function(err,result,sourceMaps,meta){
if(err) callback(err);
callback(null,result,sourceMaps,meta);
})
};
复制代码
-
'Raw' loader
默认情况下,资源文件会被转化为 UTF-8 字符串,然后传给 loader。通过设置 raw,loader 可以接收原始的 Buffer。
module.exports = function(content) { assert(content instanceof Buffer); return someSyncOperation(content); // 返回值也可以是一个 `Buffer` // 即使不是 raw loader 也没问题 }; module.exports.raw = true; 复制代码
-
Pitching loader
loader 总是从右到左地被调用。在实际(从右到左)执行 loader 之前,会先从左到右调用 loader 上的 pitch 方法。如果某个 loader 在 pitch 方法中给出一个结果,那么这个过程会回过身来,并跳过剩下的 loader
-
this.version loader API的版本号 目前是2
-
this.context 模块所在的目录
-
this.request 被解析出来的request字符串
-
this.callback
- 必须是 Error 或者 null
- 一个 string 或者 Buffer。
- 可选的:必须是一个可以被这个模块解析的 source map。
- 可选的:会被 webpack 忽略,可以是任何东西(例如一些元数据)。
-
this.async 告诉loader-runner 这个函数将会异步回调,返回 this.callback
-
this.data 在 pitch 阶段和正常阶段之间共享的 data 对象。
-
this.cacheable 设置是否可缓存标志的函数
调用 this.cacheable(false) 关闭loader的缓存
-
this.loaders 所有loader组成的数组 在pitch阶段可以写入
-
this.loaderIndex 当前 loader 在 loader 数组中的索引。
-
this.resource request 中的资源部分,包括 query 参数。
-
this.resourcePath 资源文件的路径。
-
this.resourceQuery 资源的query参数
-
this.target 编译的目标。从配置选项中传递过来的。
-
this.webpack 是否是由webpack编译 如果是webpack编译的 这个布尔值会被设置为真
-
this.sourceMap 生成一个 source map
-
this.emitWarning 发出警告this.emitWarning(warning)
-
this.emitError 发出一个错误
-
this.loadModule
解析给定的 request 到一个模块,应用所有配置的 loader ,并且在回调函数中传入生成的 source 、sourceMap 和 模块实例(通常是 NormalModule 的一个实例)。
loadModule(request: string, callback: function(err, source, sourceMap, module)) 复制代码
-
this.resolve 解析一个request
resolve(context: string, request: string, callback: function(err, result: string)) 复制代码
-
this.addDependency 添加一个文件作为产生 loader 结果的依赖
-
this.addContextDependency 添加一个文件夹作为产生loader结果的依赖
-
this.clearDependencies 移除loader结果所有的依赖
-
this.emitFile 产生一个文件
-
this.fs 用于访问输入文件系统的属性
如何编写一个loader
用法准则
- 简单易用。
- 使用链式传递。
- 模块化的输出。
- 确保无状态。 在不同模块转换之间不保存状态
- 使用 loader utilities。 loader-utils schema-utils
- 记录 loader 的依赖。
- 解析模块依赖关系。 根据模块的不同,可能有不同的模式指定依赖关系。1.转换成require语句 2.使用this.resolve函数解析路径
- 提取通用代码。
- 避免绝对路径。 loader-utils中stringifyRequest 方法,可以将绝对路径转化为相对路径。
- 使用 peer dependencies。 把这个包作为一个 peerDependency 引入。在 package.json 中指定所需的确定版本。
"peerDependencies": { "node-sass": "^4.0.0" } 复制代码
链式调用loader时,他们会以相反的顺序执行,看数组的写法 从右往左或从下往上
- 最后的loader最早执行 会传入原始文件资源
- 第一个 loader 最后调用,期望值是传出 JavaScript 和 source map(可选)。
- 中间的 loader 执行时,会传入前一个 loader 传出的结果。
利用 loader 可以链式调用的优势。写五个简单的 loader 实现五项任务,而不是一个 loader 实现五项任务。功能隔离使 loader 更简单
loader工具库 loader-utils
最常用的工具:getOptions:获取传递给 loader 的选项 stringifyRequest 方法,可以将绝对路径转化为相对路径。 schema-utils
loader 依赖 以来一个外部资源
this.addDependency 如果一个 loader 使用外部资源 必须声明它
模块依赖
根据模块类型,可能会有不同的模式指定依赖关系。例如在 CSS 中,使用 @import 和 url(...) 语句来声明依赖。这些依赖关系应该由模块系统解析。
两种实现方式
- 通过把它们转化成 require 语句。 css-loader @import 语句替换为 require 其他样式文件,将 url(...) 替换为 require 引用文件。
- 使用 this.resolve 函数解析路径。 less-loader .less文件中的变量和混合跟踪都必须一次编译 所以不能将所有的@import 转化为 require,因此,less-loader 将 less 编译器进行了扩展,自定义路径解析逻辑,然后通过this.resolve解析
通用代码
避免在每个模块中生成通用的代码 ,你应该在 loader 中创建一个运行时文件,并生成 require 语句以引用该共享模块。
绝对路径
不要再模板代码中插入绝对路径 ,因为当根路径变化是 绝对路径也会变化
测试
- Jest babel-jest babel-preset-env允许import export async await
//.bebelrc
{
"presets":[
[
"env",
{
"targets":{
"node":4
}
}
]
]
}
//loader.js
import { getOptions } from 'loader-utils';
export default function loader(source) {
const options = getOptions(this);
source = source.replace(/\[name\]/g, options.name);
return `export default ${ JSON.stringify(source) }`;
};
复制代码
2.使用 Node.js API 和 memory-fs 去执行 webpack 允许我们访问获取转换模块的统计数据 stats
//compiler.js
import path from 'path';
import webpack from 'webpack';
import memoryfs from 'memory-fs';
export default (fixture, options = {}) => {
const compiler = webpack({
context: __dirname,
entry: `./${fixture}`,
output: {
path: path.resolve(__dirname),
filename: 'bundle.js',
},
module: {
rules: [{
test: /\.txt$/,
use: {
loader: path.resolve(__dirname, '../src/loader.js'),
options: {
name: 'Alice'
}
}
}]
}
});
compiler.outputFileSystem = new memoryfs();
return new Promise((resolve, reject) => {
compiler.run((err, stats) => {
if (err) reject(err);
resolve(stats);
});
});
}
复制代码
- 测试
//loader.test.js
import compiler from './compiler.js';
test('Inserts name and outputs JavaScript', async () => {
const stats = await compiler('example.txt');
const output = stats.toJson().modules[0].source;
expect(output).toBe(`export default "Hey Alice!\\n"`);
});
复制代码