webpack loader
一、loader的本质是什么?
loader 本质上是导出为函数的 JavaScript 模块。loader runner(有兴趣自行了解)会调用此函数,然后将上一个 loader 产生的结果或者资源文件传入进去。函数中的 this 作为上下文会被 webpack 填充,并且 loader runner 中包含一些实用的方法。
如下代码是loader函数
1 | /** |
二、webpack loader的作用是什么?
loader 用于对模块的源代码进行转换
解释:
在实际开发过程中,webpack 默认只能打包处理以 .js 后缀名结尾的模块。其他非 .js 后缀名结尾的模块,webpack 默认处理不了,需要调用 loader 加载器才可以正常打包,否则会报错。
需要注意的是,loader返回的必须是js代码(要不然webpack还是处理不了…)。
三、loader的调用顺序
以一下代码为例
1 | { |
- webpack 默认只能打包处理.js文件,处理不了其它后缀的文件
- 由于前端项目中包含了其它后缀文件,当处理不了这些文件时,会查找webpack配置文件,看module.rules数组中,是否配置了对应的loader加载器
- webpack把.css文件转交给css-loader
- 当css-loader处理完毕后,会把处理结果转交下一个loader(style-loader)
- 当style-loader处理完毕后,发现没有下一个loader了,于是就把处理结果转交给webpack
- webpack进行后续处理
当链式调用多个 loader 的时候,它们的执行顺序是反方向(从右向左或者从下向上执行)
四、loader的分类
根据Rule.enforce字段的类型值,可以把loader分为:前置(pre)、普通(normal)、行内(inline)、后置(post)四种,其中pre、post可以通过enforce字段进行指。normal类型需要enforce字段无值,最后的类型开启方式则是loader通过 import/require 行内方式加载时,才表示inline类型。
举例
normal类型
1 | { |
因为loader中并没有enforce字段,所以为normal
pre类型
1 | { |
post类型
1 | { |
inline类型
1 | import test from 'vue-loader!./test.vue'; |
(一) loader的引入方式
- 配置方式(推荐):在 webpack.config.js 文件中指定 loader。
- 内联方式:在每个 import 语句中显式指定 loader。
注意在 webpack v4 版本可以通过 CLI 使用 loader,但是在 webpack v5 中被弃用。
具体loader引入细节:请访问webpack
(二) pre、normal、inline、post 优先级
在 Pitching 阶段: loader 上的 pitch 方法,按照 后置(post)、行内(inline)、普通(normal)、前置(pre) 的顺序调用。(post>inline>normal>pre)
在 Normal 阶段: loader 上的 常规方法,按照 前置(pre)、普通(normal)、行内(inline)、后置(post) 的顺序调用。模块源码的转换, 发生在这个阶段。(pre>normal>inline>post)
注: Pitching和Normal后面会有讲解
例子1: Normal阶段下不同类型loader执行顺序
inlineLoader
1 | // inlineLoader.js |
normalLoader
1 | // normalLoader.js |
postLoader
1 | // postLoader.js |
preLoader
1 | // preLoader.js |
webpack 中相关loader配置
1 | { |
页面引入
1 | require('./test.txt'); |
注: 行内loader只能在页面中引入
执行结果
注:图中前三个打印(preLoader、normalLoader、postLoader)是require()调用引起的
例子2: pitch阶段下不同类型loader执行顺序
保持其他内容不变的情况下,在各个loader文件中加入pitch函数,例如
1 | module.exports = function (source, map) { |
运行结果为
可以发现pitch阶段优先级为pre>normal>inline>post。
有人会不解,为什么有的类型loader会出现两次?这是因为inline-loader在处理时,会执行其余三种类型的loader。
如果不想这么做可以在前面加上’!!’;代码如下
1 | import '!!E:\\iyouTest\\kh-new-project\\build\\inlineLoader.js!./test.txt'; |
至于’!!’详细细节查看webpack-概念-loader
五、loader 的Pitch和Normal阶段
webpack执行loader时分为两个阶段,分别是Pitch和Normal阶段。其中Pitch阶段先执行,Normal阶段后执行,如下图所示
对于不同阶段,loader执行顺序为
- Pitch阶段: 从左到右(或者从上到下)调用 loader 上的 pitch 方法。
- Normal阶段: 从右到左被(或者从下到上)调用loader
例子
1 | { |
Pitch阶段: style-loader->css-loader
Normal阶段: css-loader->style-loader
(一) 如何在loader添加pitch函数?
在之前的已经有演示了,就是在normal loader中添加pitch属性,并且该属性是函数。
1 | /** |
(二)、pitch loader返回非undefined值时,loader的处理情况
在第一个loader的pitch函数中直接返回有值,那么后续的loader不再执行,直接把结果返回给webpack
不处于第一种情况时,会跳过剩下的loader和读取文件资源,直接将返回值传入上一个normal loader中执行
六、编写loader
(一)、loader编写规范
- 单一原则: 每个 Loader 只做一件事;
- 链式调用: Webpack 会按顺序链式调用每个 Loader;
- 统一原则: 遵循 Webpack 制定的设计规则和结构,输入与输出均为字符串,各个 Loader 完全独立,即插即用;
详细的loader编写规范查看webpack
(二)、同步/异步loader
同步loader: 默认创建的loader是同步loader。
两种同步loader写法
直接使用return返回内容
1
2
3module.exports = function (content, map, meta) {
return someSyncOperation(content);
};使用this.callback函数完成同步
1
2
3
4
5
6
7
8
9/**
* @param {string|Buffer} content 源文件的内容
* @param {object} [map] 可以被 https://github.com/mozilla/source-map 使用的 SourceMap 数据
* @param {any} [meta] meta 数据,可以是任何内容
*/
module.exports = function (content, map, meta) {
this.callback(null, someSyncOperation(content), map, meta);
return; // 当调用 callback() 函数时,总是返回 undefined
};注: this.callback 方法则更灵活,因为它允许传递多个参数,而不仅仅是 content。而且callback函数准寻nodejs的错误优先函数风格
异步loader: 在函数中使用 this.async
1 | module.exports = function (content, map, meta) { |
何时使用异步loader?
如果计算量很小,使用同步 loader,否则异步loader
(三)、编写一个简单的同步loader
这里以实现’替换txt文本中指定的字符串’功能为例,演示如何编写loader
创建文件夹并在其中创建将要使用的loader js文件
在刚才创建的loader文件中写一个简单的替换内容的函数
1
2
3
4
5
6
7
8
9
10
11/**
*
* @param {string|Buffer} content 源文件的内容
* @param {object} [map] 可以被 https://github.com/mozilla/source-map 使用的 SourceMap 数据
* @param {any} [meta] meta 数据,可以是任何内容
*/
function testLoader(content, map, meta) {
return `export default function(){
return ${ JSON.stringify(content.replace('loader','testLoader'))}
}`
}在webpack配置的module字段中写匹配规则
1
2
3
4
5{
test: /\.txt$/,
exclude: /node_modules/,
loader: require.resolve('./testLoader.js'),
},创建txt文件和内容并在vue文件中引入(创建的文件名和位置根据自己需求而定)
txt文件中的内容
vue文件中引入1
2
3import test from './test.txt';
console.log(test());结果