webpack-loader分析

1,388 阅读3分钟

v2-0434451a787f204ef23b9a562598fb32_1440w.png

webpack-loader

基本工作流是将一个文件以字符串的形式读入,对其进行语法分析及转换,然后交由下一环节进行处理,所有载入的模块最终都会经过moduleFactory处理,转成JavaScript可以识别和运行的代码,从而完成模块的集成。

loader分类与优先级

loader可以被分为四类,分别是:后置post,普通normal,行内inline,前置pre

四种loader调用先后顺序为:pre > normal > inline > post

调用的优先级为,自下而上,自右向左。(pitch情况下,则反过来)

rules: [
     {
       test: /.xss$/,
       use: [
         {
           loader: 'loader-1.js',
         },
         {
           loader: 'loader-2.js',
         },
       ],
     },
     {
       test: /.xss$/,
       use: [
         {
           loader: 'loader-3.js',
         },
         {
           loader: 'loader-4.js',
         },
       ],
       enforce: "pre",
     },
     {
       test: /.xss$/,
       use: [
         {
           loader: 'loader-5.js',
         },
         {
           loader: 'loader-6.js',
         },
       ],
       enforce: "post",
     },
   ]
   // loader-3-pitch
   // loader-4-pitch
   // loader-1-pitch
   // loader-2-pitch
   // loader-5-pitch
   // loader-6-pitch
   // loader-6
   // loader-5
   // loader-2
   // loader-1
   // loader-4
   // loader-3

三种前缀语法:

  • !:忽略normal loader
  • -!:忽略pre loadernormal loader
  • !!:忽略所有loaderpre / noraml / post

以loader-1为pre loader,loader-2为normal loader,loader-3为post loader为例。

  • 无前缀
 import "../loader/loader-4.js!./loader.les"
 loader-3-pitch
 loader-4-pitch
 loader-2-pitch
 loader-1-pitch
 loader-1
 loader-2
 loader-4
 loader-3
  • !前缀
 import "!../loader/loader-4.js!./loader.les"
 loader-3-pitch
 loader-4-pitch
 loader-1-pitch
 loader-1
 loader-4
 loader-3
  • -!前缀
 import "-!../loader/loader-4.js!./loader.les"
 loader-3-pitch
 loader-4-pitch
 loader-4
 loader-3
  • !!前缀
 import "!!../loader/loader-4.js!./loader.les"
 loader-4-pitch
 loader-4

同步/异步 loader

loader本质上是导出为函数的JavaScript模块。loader runner会调用此函数,然后将上一个 loader产生的结果或者资源文件传入进去。函数中的 this 作为上下文会被webpack填充,并且 loader runner中包含一些实用的方法

  • 单个处理结果,可以在 同步模式 中直接返回
  • 多个处理结果,须调用 this.callback()
  • 异步模式 中,须调用 this.async()
/**
 *
 * @param {string|Buffer} content 源文件的内容
 * @param {object} [map] 可以被 https://github.com/mozilla/source-map 使用的 SourceMap 数据
 * @param {any} [meta] meta 数据,可以是任何内容
 */
function webpackLoader(content, map, meta) {
  // 你的 webpack loader 代码
}

Pitching Loader

  • remainingRequest:当前loader右侧的所有loader加上资源路径,根据!分割,连接而成的内联loader
  • precedingRequest:当前loader左侧的所有loader,根据!分割,连接而成的内联loader
  • data:在pitch阶段和normal阶段之间共享的data对象。即:pitch阶段的参数datanormal阶段通过this.data获取的data为同一对象。

loader 总是 从右到左被调用。有些情况下,loader只关心request后面的 元数据(metadata),并且忽略前一个loader的结果。在实际(从右到左)执行loader之前,会先 从左到右 调用loader上的pitch方法

WX20210421-140252.png

为什么loader可以利用pitching阶段呢?首先,传递给pitch方法的data,在执行阶段也会暴露在this.data之下,并且可以用于在循环时,捕获并共享前面的信息

module.exports = function (content) {
  return someSyncOperation(content, this.data.value);
};
module.exports.pitch = function (remainingRequest, precedingRequest, data) {
  data.value = 42;
};

如果某个 loader 在 pitch 方法中给出一个结果,那么这个过程会回过身来,并跳过剩下的 loader。在我们上面的例子中,如果 b-loader 的 pitch 方法返回了一些东西

WX20210421-144619.png

module.exports = function (content) {
  return someSyncOperation(content);
};
module.exports.pitch = function (remainingRequest, precedingRequest, data) {
  if (someCondition()) {
    return (
      'module.exports = require(' +
      JSON.stringify('-!' + remainingRequest) +
      ');'
    );
  }
};

详解 style-loader 和 css-loader

上面讲了如何写一个简单的loader,那么如果我们需要加载css,应该怎么实现呢?我们一般cssloader都是这么配置的:

     {
       test: /\.css$/,
       use: [
         { loader: "style-loader" },
         { loader: "css-loader" }
       ]
     }

即我们的CSS代码会先被css-loader处理一次,然后再交给style-loader进行处理。那么这两步分别是做什么呢?

  1. css-loader的作用是处理css中的@importurl这样的外部资源
  2. style-loader的作用是把样式插入到DOM中,方法是在head中插入一个style标签,并把样式写入到这个标签的innerHTML

画一张示意图来表示:

style-loader-and-css-loader-pipeline.png

上面的处理过程即webpack建议一个loader只做一件事。我们对图片等资源的处理和把样式插入到DOM分为两个任务,这样每个loader做的事情就比较简单,而且可以通过不同的组合实现更高级的功能

style-loader 原理解析

style-loader 的主要作用就是把 CSS 代码插入DOM中,我们看看他的代码。style-loader 的主要一段代码如下所示:

 `${esModule ? `import api from ${_loaderUtils.default.stringifyRequest(this, `!${_path.defa> ult.join(__dirname, 'runtime/injectStylesIntoStyleTag.js')}`)};
            import content${namedExport ? ', * as locals' : ''} from ${_loaderUtils.default. stringifyRequest(this, `!!${request}`)};` : `var api = require(${_loaderUtils.default.stringifyRequest(this, `!${_path.default.join(__dirname, 'runtime/injectStylesIntoStyleTag.js')} `)});
            var content = require(${_loaderUtils.default.stringifyRequest(this, `!!${request}`)});
           content = content.__esModule ? content.default : content;`}
           var options = ${JSON.stringify(runtimeOptions)};
           options.insert = ${insert};
           options.singleton = ${isSingleton};
           var update = api(content, options);
           ${hmrCode}
           ${esModule ? namedExport ? `export * from ${_loaderUtils.default.stringifyRequest(this, `!! ${request}`)};` : 'export default content.locals || {};' : 'module.exp>           orts = content.locals || {};'}`;

它其实就是返回了一段JS代码,这段代码主要就分为两步:

  1. 通过require来获取css文件的内容,得到是一个字符串
  2. 调用applyToSingletonTag把css内容插入到 DOM中去

WX20210422-160605.png

WX20210422-162628@2x.png

style-loader源码解析

css-loader原理

 style.css
   @import './global.css';
   h1 {
      color: #0080ff;
   }
  .icon {
     width: 100px;
     height: 100px;
     background-image: url('./icon.jpg');
     background-size: contain;
   }
 global.css
   body {
      background-color: #ff0000;
   }

css-loader 会执行两次,因为有两个文件,每一个css文件的处理,比如 style.css 的处理会包括两部分:

  • @import替换成 require("-!../node_modules/css-loader/index.js!./global.css")-!`` 语法会禁用掉preLoaderloader,因此这一个require`并不会递归调用自己。
  • background-image: url('./icon.jpg’)替换成 "background-image: url(" + require("./icon.jpg") + “)",图片的处理比较简单,因为如何加载图片,这是另一个loader的事情,css-loader不负责这部分,他只需要关心把url转换成require就可以了。 简单的概括,就是把这两种css内部的依赖,替换成JS的语法 require,这样webpack就能正确处理了。

css-loader转译后输出的代码:

 // Imports
 import ___CSS_LOADER_API_SOURCEMAP_IMPORT___ from "../node_modules/css-loader/dist/runtime/cssWithMappingToString.js";
 import ___CSS_LOADER_API_IMPORT___ from "../node_modules/css-loader/dist/runtime/api.js";
 var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(___CSS_LOADER_API_SOURCEMAP_IMPORT___);
 // Module
 ___CSS_LOADER_EXPORT___.push([module.id, ".foo-baz {\n    font-weight: bold;\n}\n.bar {\n    > background: #ff0000;\n}", "",{"version":3,"sources":["webpack://./src/index.css"],"names":[],"mappings":"AAAA;IACI,iBAAiB;AACrB;AACA;IACI,mBAAmB;AACvB","sourcesContent":[".foo-baz {\n    font-weight: bold;\n}\n.bar {\n    background: #ff0000;\n}"],"sourceRoot":""}]);
 // Exports
 export default ___CSS_LOADER_EXPORT___;

WX20210422-160642.png

webpck-loader官方文档

字节前端loader知识分享

webpack loader机制源码解析

揭秘webpack loader

【webpack进阶】你真的掌握了loader么?- loader十问

【工程化】深入浅出 CSS Modules

【Webpack进阶】Loader深入解析