作用
由于webpack本身只能识别js,所以在对其他类型的资源只能通过一定方式将其转化为js,发挥这个作用的就是loader。
流程
在 Webpack 进入构建阶段后,首先会通过 IO 接口读取文件内容,之后调用 LoaderRunner 并将文件内容以 source 参数形式传递到 Loader 数组,source 数据在 Loader 数组内可能会经过若干次形态转换,最终以标准 JavaScript 代码提交给 Webpack 主流程,以此实现内容翻译功能。
分类
同步loader
只有一个返回结果的同步loader可以直接return,有多个返回结果的同步loader使用this.callback()返回。
this.callback(
// 异常信息,Loader 正常运行时传递 null 值即可
err: Error | null,
// 转译结果
content: string | Buffer,
// 源码的 sourcemap 信息
sourceMap?: SourceMap,
// 任意需要在 Loader 间传递的值
// 经常用来传递 ast 对象,避免重复解析
data?: any );
异步loader
异步loader通过调用this.async获取callback函数,通过调用callback返回结果。
使用
尽可能地使你的 loader 异步化。但如果计算量很小,同步 loader 也是可以的。
pitching loader
我们都知道loader的执行是从右到左的,如style-loader<-css-loader<-less-loader,事实上,在调用loader进行转译前,loader还会有pitch阶段,pitch和normal阶段可以类比于事件的捕获和冒泡阶段。
|- 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
传递给 pitch 方法的 data,在执行阶段也会暴露在 this.data 之下,并且可以用于在循环时,捕获并共享前面的信息。
其次,如果某个 loader 在 pitch 方法中给出一个结果,那么这个过程会回过身来,并跳过剩下的 loader。按上面的逻辑,如果b-loader的pitch阶段返回的结果,那么执行流程会变成如下:
|- a-loader `pitch`
|- b-loader `pitch` returns a module
|- a-loader normal execution
基于pitch,解决了如下问题
- Loader 链条一旦启动之后,需要所有 Loader 都执行完毕才会结束,没有中断的机会 —— 除非显式抛出异常;
- 某些场景下并不需要关心资源的具体内容,但 Loader 需要在 source 内容被读取出来之后才会执行。
pitch函数的完整签名
function pitch( remainingRequest: string, previousRequest: string, data = {} ): void { }
包含三个参数:
remainingRequest: 当前 loader 之后的资源请求字符串;previousRequest: 在执行当前 loader 之前经历过的 loader 列表;data: 与 Loader 函数的data相同,用于传递需要在 Loader 传播的信息。
module.exports = { module: { rules: [ { test: /\.less$/i, use: [ "style-loader", "css-loader", "less-loader" ], }, ], }, };
css-loader.pitch 中拿到的参数依次为:
// css-loader 之后的 loader 列表及资源路径
remainingRequest = less-loader!./xxx.less
// css-loader 之前的 loader 列表
previousRequest = style-loader
// 默认值
data = {}
分类2
loader可以分为pre, post, inline(通过import 或与import类似的引入方式引入), normal(默认)
通过内联方式引入loader,可以为loader添加前缀达到指定效果。
Normal 阶段是按
前置(pre)、普通(normal)、行内(inline)、后置(post) 的顺序调用loader的,Pitching 阶段则相反。
pitch
结合以上两点,研究一下style-loader<-css-loader<-less-loader。
在pitch阶段,首先执行的是style-loader的pitch,由于style-loader本身只是将转译后的css插入html文件中,即不关注返回的source,所以我们只需要在pitch阶段完成其操作即可, 注意,pitch阶段是可以访问到data的,根据几个关键信息,style-loader的pitch返回了如下结果:
var api = require('xxx/style-loader/lib/runtime/injectStylesIntoLinkTag.js')
var content = require('!!css-loader!less-loader!./xxx.less');
返回后,调用链条中断,根据上述pitch中断返回的结果,遇到content内联loader,解析后根据!!css-loader跳过style-loader,并保持对xxx.less的引用,全部作用如下图,这个api变量即loader链公共的runtime模块,即后续链路仍在这个runtime文件进行操作。
编写一个style-loader
style-loader的pitch阶段返回了一段js代码,这段js代码包含两个部分:1. 用行内loader的方式指定了需要被处理的样式文件用:类似这样:import style from !!css-loader!less-loader!./xxx.less(没有style-loader);2. 将style作为style标签插入到dom中的代码,大致就是文中injectStylesIntoLinkTag。这两部分逻辑的js代码被webpack重新编译,当编译到import style from !!css-loader!less-loader!./xxx.less的时候就是文章中的“第二次”,而插入style到dom的代码已经追加上了,所以执行js的时候也是可以完成功能的。所以style-loader主函数是空,因为根本走不到主函数中。
所以实际上,style-loader向runtime文件写入用css-loader less-loader对less文件的解析,并添加把style文件插入dom的代码,返回后,webpack解析runtime文件,重新执行css-loader-pitch....等等
总结
由于css-loader将css转译成了js文件,而style-loader的作用是将这个js脚本执行后的代码插入style标签中,如果在style-loader的normal阶段执行,那么我们需要手动执行这个js脚本,与其这样不如在style-loader中,执行对css-loader 处理的引用,也就是import style from !!css-loader!less-loader!./xxx.less,webpack会递归执行,最后将style插入dom节点即可