一、什么是loader?
1、loader的本质
loader本质上是导出为函数的JavaScript模块。loader runner会调用此函数,然后将上一个loader产生的结果或者资源文件传入进去。
函数中的this作为上下文会被webpack填充,并且提供一些实用的方法。如loader-runner包含的方法工具可以使loader调用方式变为异步,或者获取query参数。
loader通过compiler处理预期会得到一个String或者Buffer(能够转换为string)类型的结果,代表模块的JavaScript源码的结果。
2、loader的基本代码结构
/**
*
* @param {string|Buffer} content 源文件的内容
* @param {object} [map] 可以被 https://github.com/mozilla/source-map 使用的 SourceMap 数据
* @param {any} [meta] meta 数据,可以是任何内容
*/
function webpackLoader(content, map, meta) {
// 你的 webpack loader 代码
}
二、loader的分类
loader可以分为4类:同步loader、异步loader、"Raw" Loader、Pitching Loader
1、同步loader
无论是return还是this.callback 都可以同步地返回转换后的content值。this.callback方法允许多个传参,调用时函数总是返回undefined。示例如下:
webpack简单配置


2、异步loader
使用this.async来获取callback函数,this.async告诉loader-runner这个loader将会异步的回调,返回this.callback。示例如下:

官方建议:loader 最初被设计为可以在同步 loader pipelines(如 Node.js ,使用 enhanced-require),以及 在异步 pipelines(如 webpack)中运行。然而,由于同步计算过于耗时,在 Node.js 这样的单线程环境下进行此操作并不是好的方案,我们建议尽可能地使你的 loader 异步化。但如果计算量很小,同步 loader 也是可以的
这个有个问题,如果我们在同步loader中异步返回结果,会怎样呢?结果是传到异步loader中的值是undefined,示例如下:

从结果可以看出,同步loader中执行完同步代码后直接返回,不会返回异步代码执行的结果
3、"Raw" Loader
默认情况下,资源文件会被转化为UTF-8字符串,然后传给loader。通过设置raw为true,loader可以接受原始的Buffer。每个loader都可以用String后者Buffer的形式传递它的处理结果。
compiler将会把他们在loader之间相互转换。示例如下:

4、Pitching Loader
loader总是从右到左被调用。有些情况下,loader只关心request后面的元数据,并忽略前一个loader的结果。在实际(从右到左)执行loader之前,会先从左到右调用loader上的pitch方法。
pitching阶段的作用
1)传递给pitching方法的data,在执行阶段也会暴露在this.data之下,并且可以利用于在循环时,捕获并共享前面的信息。示例如下:

2)如果某个loader在pitching方法中给出一个结果,那么这个过程会回过身来,并跳过剩下的loader。过程如下图(大佬的图片地址)

示例如下:

三、手动实现loader示例
1、实现babel-loader
babel的转换过程主要分为三步:解析(parse)->转换(transform)->生成(generate)
需要下载@babel/core包,使用包暴露的transfrom方法解析和生成ast抽象语法树,示例如下:
const babel = require('@babel/core')
const path = require('path')
module.exports = function (content, map, mete) {
const babelTransResule = babel.transform(content, {
filename: path.basename(this.resourcePath),
presets: ['@babel/preset-env']
})
const { code, ast } = babelTransResule
return this.callback(null, code, map, ast)
}
2、实现file-loader
直进直出,挪个位置
const loaderUtils = require("loader-utils"); //记得先导入loader-utils包
module.exports = function (content, map, mete) {
const filename = loaderUtils.interpolateName(this, '[hash].[ext]', {
content
})
this.emitFile(filename, content)
return `export default '${filename}'` // 导出字符串供后续loader调用
}
module.exports.raw = true // 因为是图片等资源,所以设置为true,由UTF-8变成buffer才行