loader是用与对模块的源代码进行转换(处理),之前我们已经使用过很多loader,比如css-loader、style-loader、babel-loader等。
这里我们来学习如何自定义自己的loader:
我们可以在本地创建自己的loader,新建文件夹,创建loader文件
在webpack中配置引入
const path = require("path");
module.exports = {
mode: "development",
context: path.resolve(__dirname, "."),
entry: "./src/main.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "./build"),
},
module: {
rules: [
{
test: /\.js$/i,
use: "jujuul-loader01",
},
],
},
resolveLoader: {
modules: ["node_modules", "./jujuul-loader"],
},
};
这里通过 resolveLoader 这个配置,修改了 webpack 默认读取模块的配置,添加了我们本地新建的 jujuul-loader 文件夹,我们rules中的文件会按照resolveLoader中modules数组的顺序去各个文件夹中查找,比如我们rules中use使用了jujuul-loader01这个文件,首先在node_modules中查找,找不到再去jujuul-loader中查找。
resolveLoader属性
loader的执行顺序
在3个loader文件中暴露一个pitch方法,
module.exports = function (content, sourcemap, meta) {
console.log(content, "这是我们的loader01");
return content;
};
module.exports.pitch = function () {
console.log("loader pitch 01");
};
在该方法中我们做一个打印
我们发现打印的顺序跟默认暴露方法中打印的顺序不同
这是因为webpack中有pitchLoader和normalLoader,我们平时调用的都是normalLoader,而对于pitchLoader,webpack有不同的处理,具体是webpack的loader-runner这个文件夹中的LoaderRunner.js文件,其中定义了一个变量 loaderIndex,初始值为0,每次执行webpack的loader会先执行一个递归方法,递归方法中每有一个pitchLoader,loaderIndex就加一,并且其是被递归调用的,然后会去调用normalLoader,normalLoader被调用后loaderIndex减一,也是递归调用,这个时候因为loaderIndex的值是从大到小,所以webpack处理的loader顺序也是从后到前(从数组最后执行到数组最前)
pitch和enforce
patch
- run-loader先优先执行pitchLoader,在执行时会给变量loaderIndex做加一,loaderIndex++
- run-loader之后会执行normalLoader,执行normalLoader时进行loaderIndex—
- 而loaderIndex初始值为0,所以pitchLoader会按顺序执行,而normalLoader则是反序执行
那么,能不能改变它们的执行顺序呢?
可以,我们可以将其拆分为多个 rule 对象,通过 enforce 来改变它们的顺序
enforce
enfore一共有四种方式:
- 默认所有的loader都是normal
- 在行内设置的loader是inline
- 也可以通过enforce设置 pre 和 post
在 Pitching 和 Normal 它们的执行顺序分别是:
我们的loader必须有返回值,如果是同步返回,那么有两种返回方式
方式一:直接 return 返回
module.exports = function (content, sourcemap, meta) {
console.log(content, "这是我们的loader01");
return content;
};
方式二:通过 this.callback 返回
module.exports = function (content, sourcemap, meta) {
console.log(content, "这是我们的loader01");
this.callback(null, content);
};
异步loader
如果是异步返回的loader,需要处理之后才能实现异步
module.exports = function (content, sourcemap, meta) {
console.log(content, "这是我们的loader01");
const callback = this.async();
setTimeout(() => {
this.callback(null, content);
}, 3000);
};
传入和验证参数
如果我们想要像其他的loader一样,传入一些参数,并且验证这些参数,那么可以这样做
传入参数
- 安装插件:
npm install loader-utils -D
配置我们要传入的值
module: {
rules: [
{
test: /\.js$/i,
use: {
loader: "jujuul-loader01",
// 在这里配置要传入的值
options: {
name: "jujuul",
age: "string",
},
},
},
]
}
引用插件并取值 ```javascript const { getOptions } = require(“loader-utils”);
module.exports = function (content, sourcemap, meta) { // …
// 获取传入的参数: const options = getOptions(this); console.log(“传入的参数”, options);
// ...
};
这里有一点要注意,getOptions方法在 2.0.2 版本的插件中还存在,在最新版已经没有这个方法了
<a name="wOrnI"></a>
## 验证参数
1. 安装插件:
`npm install schema-utils -D`
2. 使用
```javascript
const { validate } = require("schema-utils");
module.exports = function (content, sourcemap, meta) {
// ...
// 验证传入的参数:
validate(schema, options, {
name: "jujuul-loader02",
});
// ...
};
自定义babel-loader案例
实现一个自定义 loader 的案例
首先,告知 webpack.config 使用我们自定义的 jujuulbabel-loader 来处理 js 文件
{ test: /\.js$/i, use: { loader: "jujuulbabel-loader", options: { presets: ["@babel/preset-env"], }, }, },
实现 jujuulbabel-loader ```javascript const babel = require(“@babel/core”); const { getOptions } = require(“loader-utils”);
module.exports = function (content) { // 0. 设置为异步loader this.callback = this.async(); // 1. 获取传入的参数 const options = getOptions(this);
// 2. 对源代码进行转换 babel.transform(content, options, (err, result) => { if (err) { this.callback(err); } else { this.callback(null, result.code); } }); };
需要注意的点:
1. 因为是实现类似于 babel-loader 的功能,所以需要引入 @babel/core,又因为要接收传值,所以引入 loader-utils ,使用 getOptions 接收值
1. 因为处理需要时间,所以需要将 loader 转为异步 loader
<a name="UcMd8"></a>
# 自定义md-loader
目标:<br />自定义一个 md-loader,实现对 md 文件的处理。<br />实现过程:
1. 首先,webpack配置解析md文件规则
```javascript
{
test: /\.md$/i,
use: [
"jujuulmd-loader",
],
},
- 引入marked插件,实现对md文档的转换 ```javascript const marked = require(“marked”);
module.exports = function (content) { const htmlContent = marked.parse(content); return moduleCode; };
3. 页面中显示md转换出的html结构
```javascript
// 引入md并且赋值给code变量
import code from "./doc.md";
console.log("hello loader");
const message = "hello world";
console.log(message);
const foo = () => {
console.log("foo");
};
foo();
// 在html文件中插入md
document.body.innerHTML = code;
自定义loader总结
没学以前以为很难,学了才知道原来这么简单。
首先是在webpack中配置规则,也就是你想要解析什么类型的文件,更具体点就是以什么作为后缀的文件,是css还是js,或者是png、md、html之类的。
{
test: /\.md$/i,
use: [
"jujuulmd-loader",
],
},
然后就是指定它们使用什么loader,可以给这个loader传入一些额外的参数,当然,这一切的前提是必须先安装官方提供的 loader-utils 等工具插件。
然后就是在具体的 xxx-loader.js 文件中做处理。
需要暴露一个函数,函数的形参 content 就是传入的要处理的文件,然后对 content 做处理,比如 md-loader 中就是通过 marked 插件解析 content,再通过 highlight-js 插件对 js 代码做高亮处理,最后再把处理过后的 content 或者是 别的什么替代 content 的变量导出,就完成了。
const marked = require("marked");
const hljs = require("highlight.js");
module.exports = function (content) {
marked.setOptions({
highlight: function (code, lang) {
return hljs.highlight(lang, code).value;
},
});
const htmlContent = marked.parse(content);
const innerContent = "`" + htmlContent + "`";
const moduleCode = `var code=${innerContent};export default code;`;
return moduleCode;
};