跳到主内容

webpack loader基础入门

· 6分钟阅读

webpack loader 基础入门

在使用 webpack 的时候,我们可以使用 loader 来处理不同的文件,比如我们可以使用 loader 来处理 css 文件,或者使用 loader 来处理图片文件,或者使用 loader 来处理 js 文件。

loader 对模块的源代码进行转换。loader 在 importload(加载) 模块时预处理文件。

tip

loader 可以将文件从不同的语言(如 ts)转换为 js 或将内联图像转换为 data URL。loader 甚至允许你直接在 js 模块中 import CSS 文件!

例如使用 css-loader 告诉 webpack 处理 CSS 文件

npm install --save-dev css-loader

配置 webpack.config.js 指示 webpack 处理 css 文件

module.exports = {
module: {
rules: [{ test: /\.css$/, use: "css-loader" }],
},
};

除了 webpack 配置之外,也可以通过内联方式来配置 loader

import Styles from "style-loader!css-loader?modules!./styles.css";

loader 有什么特性

  1. loader 可以是同步的,也可以是异步的
  2. loader 支持链式调用。内联是从左到右,webpack 配置是从右到左
  3. loader 可以通过options对象参数来传递
  4. 可以通过 package.jsonmain 来将一个 npm 模块导出为 loader,还可以在 module.rules 中使用 loader 字段直接引用一个模块
  5. loader 能够产生额外的任意文件
  6. 插件(plugin)可以为 loader 带来更多特性

loader 遵循标准 模块解析 规则。多数情况下,loader 将从 模块路径 加载(通常是从 npm install, node_modules 进行加载)。

loader 通常为一个函数,输入文件流,转为 JavaScript 能识别的代码。可以上传到 npm 来使用,也可以本地引入,通常命名为 xxx-loader

loader 接口定义

同步 loader

loader 是一个普通的函数,它接受三个参数,分别是:sourcesourceMapcallback

可以通过 return 返回,也可以通过 callback 回调函数返回,callback可以传递多个参数

module.exports = function (source, sourceMap?, data?) {
// source 为 loader 的输入,可能是文件内容,也可能是上一个 loader 处理结果
return source;
};
module.exports = function (content, map, meta) {
this.callback(null, someSyncOperation(content), map, meta);
return; // 如果调用 callback() 时总是返回 undefined
};

异步 loader

异步 loader 使用 this.async 来获取 callback 函数,例如

module.exports = function (content, map, meta) {
var callback = this.async();
someAsyncOperation(content, function (err, result, sourceMaps, meta) {
if (err) return callback(err);
callback(null, result, sourceMaps, meta);
});
};

在 Node.js 这样的单线程环境下进行耗时长的同步计算不是个好主意,我们建议尽可能地使你的 loader 异步化。但如果计算量很小,同步 loader 也是可以的。

loader.pitch

loader 总是从右到左地被调用。有些情况下,loader 只需要关心 request 后面的元数据(metadata),并且忽略前一个 loader 的结果。在实际(从右到左)执行 loader 之前,会先从左到右调用 loader 上的 pitch 方法。对于以下 use 配置:

use: [
'a-loader',
'b-loader',
'c-loader'
]

将会发生这些步骤:

|- a-loader `pitch`
|- b-loader `pitch`
|- c-loader `pitch`
|- requested module is picked up as a dependency
|- c-loader normal execution
|- b-loader normal execution
|- a-loader normal execution

那么,为什么 loader 可以利用 "跳跃(pitching)" 阶段呢?

首先,传递给 pitch 方法的 data,在执行阶段也会暴露在 this.data 之下,并且可以用于在循环时,捕获和共享前面的信息。

module.exports = function (content) {
return someSyncOperation(content, this.data.value);
};

module.exports.pitch = function (remainingRequest, precedingRequest, data) {
data.value = 42;
};

其次,如果某个 loader 在 pitch 方法中给出一个结果,那么这个过程会回过身来,并跳过剩下的 loader。在我们上面的例子中,如果 b-loaderpitch 方法返回了一些东西:

module.exports = function (content) {
return someSyncOperation(content);
};

module.exports.pitch = function (remainingRequest, precedingRequest, data) {
if (someCondition()) {
return "module.exports = require(" + JSON.stringify("-!" + remainingRequest) + ");";
}
};

上面的步骤将被缩短为:

|- a-loader `pitch`
|- b-loader `pitch` returns a module
|- a-loader normal execution

具体的其他 api 可以查看官方文档

一个最简单的 loader 例子

import { getOptions } from "loader-utils";
import { validate } from "schema-utils";

import schema from "./options.json";

export default function rawLoader(source) {
const options = getOptions(this);

// 校验参数
validate(schema, options, {
name: "Raw Loader",
baseDataPath: "options",
});

// 对传入的 source 进行处理
const json = JSON.stringify(source)
.replace(/\u2028/g, "\\u2028")
.replace(/\u2029/g, "\\u2029");

const esModule = typeof options.esModule !== "undefined" ? options.esModule : true;

// 输出js文本
return `${esModule ? "export default" : "module.exports ="} ${json};`;
}