目录
loader 执行顺序
分类
- pre: 前置 loader
- normal: 普通 loader
- inline: 内联 loader
- post: 后置 loader
执行顺序
4 类 loader 的执行优级为:pre > normal > inline > post
。
相同优先级的 loader 执行顺序为:从右到左,从下到上。
例如:
// 此时loader执行顺序:loader3 - loader2 - loader1
module: {
rules: [
{
test: /\.js$/,
loader: "loader1",
},
{
test: /\.js$/,
loader: "loader2",
},
{
test: /\.js$/,
loader: "loader3",
},
],
},
// 此时loader执行顺序:loader1 - loader2 - loader3
module: {
rules: [
{
enforce: "pre",
test: /\.js$/,
loader: "loader1",
},
{
// 没有enforce就是normal
test: /\.js$/,
loader: "loader2",
},
{
enforce: "post",
test: /\.js$/,
loader: "loader3",
},
],
},
开发一个 loader
1. 最简单的 loader
// loaders/loader1.js
module.exports = function loader1(content) {
console.log("hello loader");
return content;
};
它接受要处理的源码作为参数,输出转换后的 js 代码。
2. loader 接受的参数
- content 源文件的内容
- map SourceMap 数据
- meta 数据,可以是任何内容
loader 的使用方式
- 配置方式:在 webpack.config.js 文件中指定 loader。(pre、normal、post loader)
- inline loader
- 用法:import Styles from ‘style-loader!css-loader?modules!./styles.css’;
- 含义:
- 使用 css-loader 和 style-loader 处理 styles.css 文件
- 通过 ! 将资源中的 loader 分开
inline loader 可以通过添加不同前缀,跳过其他类型 loader。 !
跳过 normal loader。
import Styles from ‘!style-loader!css-loader?modules!./styles.css’;-!
跳过 pre 和 normal loader。
import Styles from ‘-!style-loader!css-loader?modules!./styles.css’;!!
跳过 pre、 normal 和 post loader。
import Styles from ‘!!style-loader!css-loader?modules!./styles.css’;
loader 分类
1. 同步 loader
module.exports = function (content, map, meta) {
return content;
};
this.callback
方法则更灵活,因为它允许传递多个参数,而不仅仅是 content
。
module.exports = function (content, map, meta) {
// 传递map,让source-map不中断
// 传递meta,让下一个loader接收到其他参数
this.callback(null, content, map, meta);
return; // 当调用 callback() 函数时,总是返回 undefined
};
2. 异步 loader
module.exports = function (content, map, meta) {
const callback = this.async();
// 进行异步操作
setTimeout(() => {
callback(null, result, map, meta);
}, 1000);
};
由于同步计算过于耗时,在 Node.js 这样的单线程环境下进行此操作并不是好的方案,我们建议尽可能地使你的 loader 异步化。但如果计算量很小,同步 loader 也是可以的。
Raw Loader
默认情况下,资源文件会被转化为 UTF-8 字符串,然后传给 loader。通过设置 raw 为 true,loader 可以接收原始的 Buffer。
module.exports = function (content) {
// content是一个Buffer数据
return content;
};
module.exports.raw = true; // 开启 Raw Loader
Pitching Loader
module.exports = function (content) {
return content;
};
module.exports.pitch = function (remainingRequest, precedingRequest, data) {
console.log("do somethings");
};
webpack 会先从左到右执行 loader 链中的每个 loader 上的 pitch 方法(如果有),然后再从右到左执行 loader 链中的每个 loader 上的普通 loader 方法。
在这个过程中如果任何 pitch 有返回值,则 loader 链被阻断。webpack 会跳过后面所有的的 pitch 和 loader,直接进入上一个 loader 。
loader API
方法名 | 含义 | 用法 |
---|---|---|
this.async | 异步回调 loader。返回 this.callback | const callback = this.async() |
this.callback | 可以同步或者异步调用的并返回多个结果的函数 | this.callback(err, content, sourceMap?, meta?) |
this.getOptions(schema) | 获取 loader 的 options | this.getOptions(schema) |
this.emitFile | 产生一个文件 | this.emitFile(name, content, sourceMap) |
this.utils.contextify | 返回一个相对路径 | this.utils.contextify(context, request) |
this.utils.absolutify | 返回一个绝对路径 | this.utils.absolutify(context, request) |
三种本地开发测试 loader
的方法
1. 匹配(test)单个 loader
你可以通过在 rule 对象使用 path.resolve
指定一个本地文件:
webpack.config.js
const path = require('path');
module.exports = {
//...
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: path.resolve('path/to/loader.js'),
options: {
/* ... */
},
},
],
},
],
},
};
2. 匹配(test)多个 loaders
你可以使用 resolveLoader.modules
配置,webpack 将会从这些目录中搜索这些 loaders。例如,如果你的项目中有一个 /loaders
本地目录:
webpack.config.js
const path = require('path');
module.exports = {
//...
resolveLoader: {
modules: ['node_modules', path.resolve(__dirname, 'loaders')],
},
};
3. npm link
如果你已经为 loader 创建了独立的库和包,你可以使用 npm link
来将其链接到你要测试的项目。
参数
1. 当一个 loader 在资源中使用,这个 loader 只能传入一个参数 - 一个包含资源文件内容
字符串
2. loader 会返回一个或者两个值。第一个值的类型是 JavaScript 代码的
字符串
或者buffer
3. loader 可以被链式调用意味着不一定要输出 JavaScript。只要下一个 loader 可以处理这个输出,这个 loader 就可以返回任意类型的模块(对外开源的loader一定要返回
字符串
或者buffer
)
项目目录
让我们实现一个简易的raw-loader
,我们将其命名为 my-raw-loader
。这个loader的功能就是支持javascript
引用文件原始文本内容
├── loaders # loader目录
├── src # 业务代码
│ │── happy-new-year.txt
│ │── index.html
│ └── index.js
├── .gitignore
├── package.json
├── package-lock.json
└── webpack.config.js # webpack 配置文件
搭建项目
mkdir my-loader
cd my-loader
npm init -y
npm i -D webpack webpack-cli html-webpack-plugin webpack-dev-server loader-utils
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
module.exports = {
mode: 'development',
plugins: [
new HtmlWebpackPlugin({
title: '自定义 webpack loader',
template: './src/index.html',
}),
],
};
src/index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
</body>
</html>
src/index.js
import text from './happy-new-year.txt';
const textDom = document.createElement('p');
textDom.style.cssText = 'width: 200px;height: 200px;background-color: pink;';
textDom.innerText = text;
document.body.appendChild(textDom);
src/happy-new-year.txt
🎉🎉🎆🎆🧨🧨
新年快乐!大吉大利!
🎉🎉🎆🎆🧨🧨
执行 npx webpack-dev-server
,会发现编译报错了
那么下面我们就实现 my-raw-loader
来抛砖引玉!
my-raw-loader
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
module.exports = {
mode: 'development',
module: {
rules: [
{
test: /.txt$/,
use: [
{
loader: path.resolve(__dirname, 'loaders/my-raw-loader'),
options: {
esModule: true,
},
},
],
},
],
},
plugins: [
new HtmlWebpackPlugin({
title: '自定义 webpack loader',
template: './src/index.html',
}),
],
};
loaders/my-raw-loader.js
function myRawLoader(source) {
console.log('source', source);
}
module.exports = myRawLoader;
执行 npx webpack-dev-server
可以看到打印结果,这个参数是一个字符串
简单实现一个 loader
修改 loaders/my-raw-loader.js
function myRawLoader(source) {
// 提取给定的 loader 选项,
// 从 webpack 5 开始,this.getOptions 可以获取到 loader 上下文对象。它用来替代来自 loader-utils 中的 getOptions 方法。
const { esModule } = this.getOptions();
console.log('esModule:', esModule);
// 这里一定要返回字符串或者 buffer
if (!esModule) {
return `module.exports = ${JSON.stringify(source)}`;
}
return `export default ${JSON.stringify(source)}`;
}
module.exports = myRawLoader;
执行 npx webpack-dev-server
可以看到通过 this.getOptions()
获取到了当前 loader 的配置,并且编译未报错,访问 http://localhost:8080/ 页面得偿所愿!成功读取并渲染了原始文本内容。
schema-utils
schema-utils
由webpack 官方提供, 它配合 loader-utils
,用于保证 loader 选项,进行与 JSON Schema 结构一致的校验
const { validate } = require('schema-utils');
const schema = {
type: 'object',
properties: {
esModule: {
type: 'boolean',
}
},
"additionalProperties": false // 是否允许不存在的选项传入
};
function myRawLoader(source) {
const options = this.getOptions();
validate(schema, options, {
name: 'my-raw-loader',
baseDataPath: 'options',
});
// 提取给定的 loader 选项,
// 从 webpack 5 开始,this.getOptions 可以获取到 loader 上下文对象。它用来替代来自 loader-utils 中的 getOptions 方法。
console.log('esModule:', options.esModule);
// 这里一定要返回字符串或者 buffer
if (!options.esModule) {
return `module.exports = ${JSON.stringify(source)}`;
}
return `export default ${JSON.stringify(source)}`;
}
module.exports = myRawLoader;
如果传入未定义的选项,则会发生编译报错
{
test: /.txt$/,
use: [
{
loader: path.resolve(__dirname, 'loaders/my-raw-loader'),
options: {
esModule2: true,
},
},
],
},
源码地址:https://gitee.com/yanhuakang/webpack-demos/tree/master/proficient/step_1-custom-loader