轻松搞定 webpack loader (基础>>>深入)

1,264 阅读5分钟

序言

在学习之前先抛出三个问题,我们根据这三个问题深入学习 loader 。

  1. 什么是 loader ,它能干什么。
  2. 为什么需要 loader 。
  3. 如何使用 loader ,有哪些要掌握的核心。

什么是 loader ?


image.png
这是 webpack 官网的宣传图,我们可以看到,左侧各种类型的文件,统一打包成右边被浏览器识别支持的文件,那么这个转换的过程是如何实现的呢,没错就是 webpack 的核心概念之一( loader)实现的。

  1. loader 让 webpack 能够处理其他类型的文件,并将它们转换为有效模块
  2. loader 本质是一个函数,导出为函数的 JavaScript 模块
  3. 简单说就是将各种类型的文件,转换成 JS 模块。

为什么需要loader

因为 webpack 只能理解 JavaScriptJSON文件。所以需要一个模块来帮它处理其他类型的文件,loader 就是做这件事情的模块。

webpack常用的loader

样式:style-loader、css-loader、less-loader、sass-loader 等
文件:raw-loader、file-loader 、url-loader 等
编译:babel-loader、coffee-loader 、ts-loader 等
校验测试:mocha-loader、jshint-loader 、eslint-loader 等
为什么有这么多种 loader 呢,是因为一种 loader 处理相应的文件,这样的职责单一的设计,可以高度复用和灵活组合

loader怎么用

首先 loader 是辅助 webpack 的模块 ,所以要配置 webpack.config 文件指定 loader 的执行区间和执行方式,我们以一个 sass-loader 的例子演示一下使用。

  1. 首先匹配要处理的文件,通过 test,正则过滤出 scss 文件。
  2. 经过 sass-loader 转换 sass 文件为 css 文件, 并且包装一层 module.exports 成为一个 js module 。
  3. 通过 style-loader 将创建一个style 标签将上面转换完成的 css 文件嵌入 html 中。
  4. 最后 css-loader 处理其中的 @importurl()。因为 webpack 不认识,所以将其转化成 require 形式
module.exports = {
  module: {
    rules: [
        {
          test: /\.scss$/,
          use:[
              {loader:'style-loader'},
              {loader:'css-loader',options:{sourceMap:true,modules:true}},
              {loader:'sass-loader',options:{sourceMap:true}}
          ],
          exclude:/node_modules/,
          enforce: "post",
      }
    ]
  }
}

深入 loader

我们已经知道了 loader 的概念和基本使用,所以接下来会探究一下 loader 的分类和执行流程。通过上例,我们发现还有一个 enforce: "post" 配置,这就是loader 分类的配置

enforce ( loader 分类)

类别与优先级

  1. 在 webpack 中 loader 可以被分为四类,分别是 :普通normal前置pre行内inline后置 post
  2. 普通 normal ,前置pre,后置 post ,通过 enforce 可以进行分类设置,若无设置,则默认为 normal。
  3. 行内 inline 比较特殊,是在 import / require 的时候,直接写入代码中,用来过滤其他的 loader 。
  4. 四种loader调用先后顺序为:pre > normal > inline > post 。

inline(行内 loader )

使用方法是在 import / require 的时候,将他的三种前缀语法写入代码中:

  1. ! : 忽略 normal-loader。
  2. -! : 忽略 pre-loader 和 normal-loader.
  3. !! : 忽略所有 loader (pre / normal / post)

行内 loader 通过 ! 控制资源中的 loader,同时支持在 loader 后面通过 ? 传递参数。
Tip:
该分类是相对于 rules ,并非是某个 loader ,只要在当前这个 rules 范围内的所有 loader 都遵循此设定。
实例
假设我们有4个 loader ,分别为 pre-loader ,normal-loader ,post-loader ,inline-loader。
以上 loader 均为:

module.exports = function (content) {
  console.log("x-loader");
  return content;
};

module.exports.pitch = function (remainingRequest, precedingRequest, data) {
  console.log("x-loader-pitch");
};
  1. 无前缀信息时
import "/Users/jiangyuereee/Desktop/loader/inline-loader.js!./txt.txt"

post-loader-pitch
inline-loader-pitch
normal-loader-pitch
pre-loader-pitch
pre-loader-pitch
normal-loader-pitch
inline-loader-pitch
post-loader-pitch

我们可以发现,执行顺序是按照我们上文提到的优先级顺序进行排列,并且触发方式类似于洋葱模型。

  1. !前缀信息
import "!/Users/jiangyuereee/Desktop/loader/inline-loader.js!./txt.txt"

post-loader-pitch
inline-loader-pitch
pre-loader-pitch
pre-loader-pitch
inline-loader-pitch
post-loader-pitch

我们看到 normal-loader 并没有触发,所以过滤成功。

  1. -!前缀信息
import "-!/Users/jiangyuereee/Desktop/loader/inline-loader.js!./txt.txt"

post-loader-pitch
inline-loader-pitch
inline-loader-pitch
post-loader-pitch

我们看到 normal-loader 和 pre-loader 并没有触发,所以过滤成功。

  1. !!前缀信息
import "!!/Users/jiangyuereee/Desktop/loader/inline-loader.js!./txt.txt"

post-loader-pitch
post-loader-pitch

我们看到只有 inline-loader 触发,所以过滤成功。

  1. 当出现相同的 loader 的情况下,调用的优先级为,从低到高,自右往左。(pitch情况下,则相反)

loader.png

Loader-pitch

  1. loader 的调用过程:在从右往左调用 loader 方法之前都会从左往右调用一遍 loader上面的 pitch 方法。

loader调用过程.png

  1. loader 的熔断机制

一旦一个 pitch 返回了结果,那么直接熔断,不会执行后面的 pitch ,直接将现有结果传递给前一个 loader 。
loade熔断过程.png
我们看到上图中 loader2.pitch 返回了结果,直接把结果传递给 loader1,剩下的都不会触发了。

同步 / 异步 loader


在官方文档中有描述
如果是单个处理结果,可在同步模式中直接返回,但是如果有多个结果,则要调用 this.callpack()。在异步模式中,必须调用 this.async()来告知 loader runner 等待异步结果,它会返回 this.callback()回调函数,随后 loader 必须返回 undefined ,并且调用该回调函数。
在 webpack 中,loader 会依赖于读取外部配置文件,进行网络请求等,如果同步操作,会造成进程堵塞。所以loader 会有同步 / 异步之分

在 loader 中,可以通过两种方式返回数据

  1. return :只能返回 content。
  2. callback :可以返回
  • err : Error | null (错误信息)
  • content : string | Buffer (content信息)
  • sourceMap : SourceMap (sourceMap)
  • meta? : any (奇怪的数据,AST等等)

同步 loader

module.exports = function(content, map, meta) {
  // return handleData(content);
  this.callback(null, handleData(content), handleSourceMap(map), meta);
  return; // 当调用 callback() 函数时,总是返回 undefined
};

异步 loader

module.exports = function(content, map, meta) {
  var callback = this.async();
  asycnHandleData(content, function(err, result) {
    if (err) return callback(err);
    callback(null, result, map, meta);
  });
};

总结: 要注意的是,异步要通过 this.async 来获取 callback。

loader的输出


由loader的返回方法 callback 可知,当 loader 链路到了最后一个 loader 的时候,compiler 期待得到的结果是可以转换为StringBuffer 或者直接是个 String 。表示当前模板处理后的 JS 源码。根据返回参数得知,还可以传递一个 sourceMap
除去正常的 loader 输出的 JS 源码,还可以根据 this.emitFile 进行额外输出文件。

emitFile(
    name: string, 
    content: Buffer|string, 
    sourceMap?: {...}
);

相关使用工具

  1. webpack 官方中文文档
  2. Loader 机制
  3. Loader-runner调试工具