看我来实现一个webpack loader

loader的基本概念

在webpack中,loader本质是一个函数,在我们执行文件操作时,会通过该函数对接收到的内容进行转换,返回转换后的结果。因为 Webpack 只支持 JavaScript 文件,所有有的时候,loader也对其他类型的文件进行转译处理,转换为 Webpack 支持的文件。

实现一个同步的 loader

在实现之前,我们先看一下什么是同步loader:

  • 默认创建的Loader就是同步的Loader
  • 这个Loader必须通过return或者this.callback来返回结果,交给下一个loader来处理
  • 通常在有错误的情况下,会使用this.callback
    • 第一个参数为ERR或者null
    • 返回的是 string 或者 buffer

在这里我们先初始化一个项目

COPY
1
2
npm init -y
npm install webpack webpack-cli

再新建两个文件 index.jswebpack.config.js
具体结构如下图所示

其中index.js中的内容

COPY
1
2
// index.js
console.log('自己实现一个loader')

webpack.config.js 中的内容

COPY
1
2
3
4
5
6
7
8
9
10
11
const path = require('path')
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
}
}

接着,在根目录下新建一个同步loader syncLoader.js

COPY
1
2
3
4
5
// syncLoader.js
module.exports = function (source) {
console.log('source: ', source)
return source
}

修改 Webpack 打包配置

COPY
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const path = require('path')
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
},
// 通过resolveLoader 配置项,指定loader查找文件路径
resolveLoader: {
// 查找顺序从左往右
modules: ['node_modules', './']
},
module: {
// loader 是一个数组,执行顺序从下到上,从右往左
rules: [
{
test: /\.js$/, // 资源文件匹配正则表达式
use: 'syncLoader'
}
]
}
}

准备就绪,使用webpack进行打包,因为 package.json 已经声明了 "main": "index.js" 所以我们直接在根目录输入 webpack 即可,不出意料的话,会在终端输出以下内容:

COPY
1
2
3
source:  console.log('自己实现一个loader');
asset main.js 1.22 KiB [compared for emit] (name: main)
./src/index.js 40 bytes [built] [code generated]

很明显,第一行的打印内容中 sourcesyncLoader.js 中的内容,后面拼接的是 index.js 中的文本,如下:

COPY
1
2
3
4
5
6
7
8
// index.js
console.log('自己实现一个loader');

// syncLoader.js
module.exports = function (source) {
console.log('source: ', source)
return source
}

我们接着使用 syncLoader.jsindex.js 中的内容作出一些处理,比如:

COPY
1
2
3
4
5
6
7
8
9
// index.js
console.log('自己实现一个loader')

// syncLoader.js
module.exports = function (source) {
source += '同步loader';
console.log('source: ', source);
return source
}

输出结果为

COPY
1
2
3
source:  console.log('自己实现一个loader');同步loader
asset main.js 1.24 KiB [compared for emit] (name: main)
./src/index.js 56 bytes [built] [code generated]

当然,我们可以使用 loader-utils 来完成更多自定义的功能,我这里安装 loader-utils@1.2.3

先改写 webpack.config.js 的内容,主要再rules中给 syncLoader 增加了 options 属性,其中 options 的内容可以自定义

COPY
1
2
3
4
5
6
7
8
9
10
11
12
rules: [
{
test: /\.js$/, // 资源文件匹配正则表达式
use: {
loader: 'syncLoader',
options: {
message: '同步loader'
}
}
}
]

然后我们就可以在 syncLoader.js 通过loader-utilsgetOptions 获取到 options 配置项

COPY
1
2
3
4
5
6
7
8
const loaderUtils = require('loader-utils')
module.exports = function (source) {
const options = loaderUtils.getOptions(this)
// { message: '同步loader' }
console.log(options)
source += options.message
this.callback(null, source)
}

打包结果

COPY
1
2
3
{ message: '同步loader' }
asset main.js 1.24 KiB [emitted] (name: main)
./src/index.js 52 bytes [built] [code generated]

可以看到,在控制台输出了 syncLoader loader 新增的配置项内容 同步loader,跟之前的内容相同。
至此,我们就完成了一个同步 loader

当然我们要尽可能的异步化 Loader,如果计算量很小,同步也可以,所以看看如何实现一个异步的 loader

实现一个异步的 loader

在实现之前,我们先看一下什么是异步loader:

  • 使用Loader进行一些异步的操作
  • 我们希望在异步操作完成之后,再返回这个loader处理的结果
  • 使用 this.async() 实现

在根目录新建一个js文件 asyncLoader.js

COPY
1
2
3
4
5
6
7
8
module.exports = function (source) {
const asyncfunc = this.async()
setTimeout(() => {
source += '异步loader'
asyncfunc(null, res)
}, 300)
}

修改 webpack.config.js ,在use中配置异步的loader,修改后的内容如下

COPY
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
const path = require('path')
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
},
// 通过resolveLoader 配置项,指定loader查找文件路径
resolveLoader: {
// 查找顺序从左往右
modules: ['node_modules', './']
},
module: {
// loader 是一个数组,执行顺序从下到上,从右往左
rules: [
{
test: /\.js$/, // 资源文件匹配正则表达式
use: [
{
loader: 'syncLoader',
options: {
message: '同步loader'
}
},
// 简写
// 'asyncLoader',
{
loader: 'asyncLoader',
}
]
}
]
}
}

由于loader的执行机制,所以我们预测 先执行 asyncLoader,再执行syncLoader
查看 dist/main.js 内容,确实如此,至此我们就简单实现了一个 异步loader

总结

  • Loader本质是一个导出为函数的JavaScript模块
  • loader 的执行顺序是从下往上的,Loader runner库会调用这个函数,然后将上一个loader产生的结果或者资源文件传入进去
  • 我们可以通过 loader-utils 获取到 loader 的配置,再对原始文件进行对应操作
  • 异步loader主要通过 this.async() 实现

那么那么,为什么 loader 的执行是从下往上,从右往左的呢 ?。。。

作者: 果汁
文章链接: https://guozhigq.github.io/post/e8da5eee.html
版权声明: All posts on this blog are licensed under the CC BY-NC-SA 4.0 license unless otherwise stated. Please cite 果汁来一杯 !