loader的执行顺序真的是从右到左吗

前言

作为切图仔,我们对webpack肯定不陌生。而其中有一个名为loader的配置,大家更是熟悉,基本没有哪一个使用webpack的前端项目能离开这个配置项,因为webpack并不认识除了js和json之外的文件,所以除了这些webpack认识的文件之外,就需要配置loader去将不认识的文件翻译成认识的,说到底,loader就是个翻译官。

虽然我们非常熟悉loader如何配置,但我们很可能存在一个不完善的概念,那就是无论是我们刚接触loader或者是背八股文时被灌输的一个概念,那就是,loader是按照从右到左的顺序执行的,所以loader的配置也是遵守着从右到左。这句话是没问题的,但它不完整

默默无闻的pitch

其实在loader中还有这么一个角色,名为pitch,大家可能没听说过它,但它一直默默无闻的为大家工作着,最典型的就是我们style-loader了。

一般来说loader就是一个函数,但js中,万物皆对象,函数也就自然是对象了,既然是对象,它能有用一个名为pitch的属性,没毛病吧。而这个pitch其实也是一个函数。这种拥有pitch的loader表现形式如下。

js 复制代码
function loader1() {};
loader1.pitch = () => {};

module.exports = loader1;

完整的loader执行顺序

完整的执行顺序其实是pitch从左到右,然后loader从右到左。

此时,我们假设配置了三个loader且他们都有用各自的pitch,他们分别为loader1(pitch1),loader2(pitch2),loader3(pitch3),那他们的执行顺序如下图。

这里要注意一下这个大标题,没有任何一个pitch有返回值,此时才是上图的这个执行顺序。

pitch是极为霸道的存在,如果某一个pitch有返回值,它将直接跳过后面的pitch和loader的执行,直接跳到上一个loader的执行,并且这个pitch的返回值会传给上一个loader这里。

上面这句话或许有点难以理解,我们来看图。

结合图来看,pitch2有返回值,那么它直接跳过了后面的pitch和loader(pitch3,loader3,loader2)的执行,然后去执行上一个loader(loader1)。这里再次强调一下,是跳过,意思就是不会再执行那些跳过的东西了。是不是体会到pitch的霸道了?

验证

想要验证上面所说的其实很简单,我们创建一个项目。

webpack.config中针对js文件的解析,采用我们自己写的loader。

js 复制代码
module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules|loaders/,
        use: [
          path.resolve(__dirname, 'loaders/loader1.js'),
          path.resolve(__dirname, 'loaders/loader2.js'),
          path.resolve(__dirname, 'loaders/loader3.js'),
        ],
      }
    ],
  },

然后我们分别写下这三个loader

js 复制代码
loaders/loader1.js
function loader(source) {
  console.log('loader1');
  return source;
}

loader.pitch = () => {
  console.log('pitch1');
};

module.exports = loader;

loaders/loader2.js
function loader(source) {
  console.log('loader2');
  return source;
}

loader.pitch = () => {
  console.log('pitch2');
};

module.exports = loader;

loaders/loader3.js
function loader(source) {
  console.log('loader3');
  return source;
}

loader.pitch = () => {
  console.log('pitch3');
};

module.exports = loader;

最后在package.json中配置一个build命令。

js 复制代码
"scripts": {
  "build": "webpack"
},

执行build命令。

至于pitch有返回值的情况,就让各位自行写demo测试了。

style-loader

style-loader这个loader想必大家都配置过,而且它的功能大家也非常清楚,就是加一个style标签而已,但它是使用pitch的最经典例子了。我们这一小节就来讲讲它如何使用pitch的。

style-loader完美的利用了pitch的霸道性,它不希望自己之后的pitch执行,所以它在自己的pitch中加了返回值,跳过了后面的pitch和loader。但这引发另外一个问题,那就是后面的loader是需要执行的,因为style-loader自身并不认识less、sass等文件,他需要相应的loader进行解析,因此,style-loader将后面的loader都采用了另一种方式去调用和执行。

这种所谓的另外一种方式是什么呢?其实大家都很熟悉,那就是require。我们来看下style-loader的pitch的源码(简化后的)。

js 复制代码
// style-loader
// remainingRequest是剩余请求,也就是后面未执行的loaders加上一个源文件
loader.pitch = function(remainingRequest) {
  // 1 获取剩下的请求 remainingRequest是由!链接的路径(laoder1!loader2!file)其实就是内联loader的形式
  // 2 分割出各个请求的路径,前面是loader,最后一个是文件路径
  // 3 把路径从绝对路径编程相对于根目录的相对路径,其实就是模块Id // 路径前面要加上!!,只使用行内loader,不使用rule里面配置的loader,否则会死循环
  const request = '!!' + (remainingRequest.split('!')
    .map( requestAbsPath => this.utils.contextify(this.context, requestAbsPath) )
    .join('!'));
  let script = `
    let styleCSS = require(${JSON.stringify(request)});
    let style = document.createElement('style');
    style.innerHTML = styleCSS; document.head.appendChild(style);
  `;
  return script;
};

上面这段代码,大家可能对第6行会有所疑惑,这里我们将引出另外一个概念,那就是loader的分类和loader的特殊配置。

loader的分类

  • post:后置
  • inline:内联
  • normal:正常
  • pre:前置

这种分类与loader本身是没有关系的,只是跟使用loader时的配置有关。因为在loader配置中,它可能是由多个文件合并而来的,为了保证执行的时候按照特定的执行顺序来执行,所以给loader进行了分类。

他们的加载顺序是:pre => inline => normal => post

loader的特殊配置

loader还有特殊配置,在资源模块前添加,内联的方式。

  • -!:名为noPreAutoLoaders,指不要pre和normal
  • !:名为noAutoLoaders,指不要normal
  • !!:名为noPrePostAutoLoaders,指只要inline

上面这些分类和配置有什么用呢?

这里我们就不细说都分别有什么用,我们把目光放回到style-loader代码的第6行。

js 复制代码
const request = '!!' + (remainingRequest.split('!')
  .map( requestAbsPath => this.utils.contextify(this.context, requestAbsPath) )
  .join('!'));

'!!'双感叹,noPrePostAutoLoaders,只要内联loader。为什么要这么写?

答案其实很简单,避免死循环。因为当我们加载到指定类型文件时(如less),webpack就会根据loader配置中的正则去匹配,而我们这个代码中的request的最后是有一个file的,也就是文件,webpack再加载这个文件的时候又会匹配到webpack.config中针对该文件类型配置的loader,从而触发死循环。

下图就是假设不是只要内联loader的情况。

结尾

好了,到这里我们就将loader完整的执行顺序讲了一遍。

本文通过loader的完整执行顺序引出了pitch相关知识,然后再扩展了loader的分类以及特殊配置两个小概念,之后是完整讲述了style-loader的执行并且通过style-loader的执行更全面得认识pitch。

那么本文到此结束,都看到这里了,各位观众老爷觉得本文有帮助的话,点个赞吧🌹

相关推荐
小马哥编程25 分钟前
Function.prototype和Object.prototype 的区别
javascript
小白学前端66625 分钟前
React Router 深入指南:从入门到进阶
前端·react.js·react
web130933203981 小时前
前端下载后端文件流,文件可以下载,但是打不开,显示“文件已损坏”的问题分析与解决方案
前端
王小王和他的小伙伴1 小时前
解决 vue3 中 echarts图表在el-dialog中显示问题
javascript·vue.js·echarts
学前端的小朱1 小时前
处理字体图标、js、html及其他资源
开发语言·javascript·webpack·html·打包工具
outstanding木槿1 小时前
react+antd的Table组件编辑单元格
前端·javascript·react.js·前端框架
好名字08212 小时前
前端取Content-Disposition中的filename字段与解码(vue)
前端·javascript·vue.js·前端框架
摇光932 小时前
js高阶-async与事件循环
开发语言·javascript·事件循环·宏任务·微任务
隐形喷火龙2 小时前
element ui--下拉根据拼音首字母过滤
前端·vue.js·ui
m0_748241122 小时前
Selenium之Web元素定位
前端·selenium·测试工具