webpack loader 基础入门
在使用 webpack 的时候,我们可以使用 loader 来处理不同的文件,比如我们可以使用 loader 来处理 css 文件,或者使用 loader 来处理图片文件,或者使用 loader 来处理 js 文件。
loader 对模块的源代码进行转换。loader 在 import
或 load(加载)
模块时预处理文件。
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 有什么特性
- loader 可以是同步的,也可以是异步的
- loader 支持链式调用。内联是从左到右,webpack 配置是从右到左
- loader 可以通过
options
对象参数来传递 - 可以通过
package.json
的main
来将一个 npm 模块导出为 loader,还可以在 module.rules 中使用loader
字段直接引用一个模块 - loader 能够产生额外的任意文件
- 插件(plugin)可以为 loader 带来更多特性
loader 遵循标准 模块解析 规则。多数情况下,loader 将从 模块路径 加载(通常是从 npm install
, node_modules
进行加载)。
loader 通常为一个函数,输入文件流,转为 Javascript 能识别的代码。可以上传到 npm 来使用,也可以本地引入,通常命名为 xxx-loader
loader 接口定义
同步 loader
loader 是一个普通的函数,它接受三个参数,分别是:source
,sourceMap
,callback
可以通过 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-loader
的 pitch
方法返回了一些东西:
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};`;
}