Webpack loader 之 style-loader

Webpack loader 之 style-loader

webpack 本身只能处理 js 文件,而我们项目中 js 文件会依赖的文件还包括 html、css、jpg 等其他类型,自此 loader 就是专门用于将非 JS 模块转换成 JS 模块,同时推荐其单一职责(一个 loader 只负责一种转换)

loader

官方文档 - Loaders

loader 基本知识

Loader Interface

上面三个文档基本上已经全面的解释了loader的相关知识,下面罗列一些我们常用的知识点和个人理解:

定义

loader 本质上是导出为函数的 JavaScript 模块。loader runner 会调用此函数,然后将上一个 loader 产生的结果或者资源文件传入进去。

/**
 *
 * @param {string|Buffer} content 源文件的内容
 * @param {object} [map] 可以被 https://github.com/mozilla/source-map 使用的 SourceMap 数据
 * @param {any} [meta] meta 数据,可以是任何内容
 */
module.exports = function (content, map, meta) {
  // 你的 webpack loader 代码
  // 这里我们只是打印一句话并返回 content (也可以是自定义的内容) 给下一个 loader
  console.log("This is my first custom loader");
  return content;
};

/**
 * @remainingRequest 剩余请求: loader链中在自己之后的 loader 的 request 字符串
 * @precedingRequest 前置请求 loader链中在自己之前的 loader 的 request字符串
 * @data 数据对象
 */
module.exports.pitch = function (remainingRequest, precedingRequest, data) {
  // 你的 webpack loader 代码
  //   这里处理完后的 data 可在 normal 方法中通过 this.data 得到更新后的值
  data.value = 66;
  //   return something;
};
复制代码

类别

Rule.enforce

包括 前置(pre)、普通(normal)、行内(inline)、后置(post) 四种类型,其区别则表现为不同的执行顺序

inline 时可通过 ! 进行禁用 loader

// 禁用普通 loaders
import { a } from "!./file1.js";

// 禁用前置和普通 loaders
import { b } from "-!./file2.js";

// 禁用所有的 laoders
import { c } from "!!./file3.js";
复制代码

不应使用内联 loader 和 ! 前缀,因为它是非标准的。它们可能会被 loader 生成代码使用

  • Pitching 阶段 (自左向右): loader 上的 pitch 方法,按照 后置(post)、行内(inline)、普通(normal)、前置(pre) 的顺序调用。更多详细信息,请查看 [Pitching Loader](webpack.docschina.org/api/loaders…

    用处:有些情况下,loader 只关心 request 后面的 元数据(metadata),并且忽略前一个 loader 的结果

  • Normal 阶段 (自右向左): loader 上的 常规方法,按照 前置(pre)、普通(normal)、行内(inline)、后置(post) 的顺序调用。模块源码的转换, 发生在这个阶段。

例如以下配置:

module.exports = {
  //...
  module: {
    rules: [
      {
        //...
        use: ["a-loader", "b-loader", "c-loader"],
      },
    ],
  },
};
复制代码

其执行顺序为:

|- 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
复制代码

而如果我们在上述配置的情况下,b-loaderpitch 中返回了一些东西,那么执行顺序又会变为:

|- a-loader `pitch`
  |- b-loader `pitch` returns something
|- a-loader normal execution
复制代码

所以当 loaderpitch 方法中有返回值时,它会跳过剩下的 loader 方法(哪怕他们都有 pitch 方法),然后回到自己本身并结束

模式

  • 同步:this.callback
  • 异步:this.async
// callback
module.exports = function (content, map, meta) {
  const output = someSyncOperation(content);

  return output;
  // or
  this.callback(null, output, map, meta);
  return;
};

// async
module.exports = function (content, map, meta) {
  const callback = this.async();

  someAsyncOperation(content, function (err, result, sourceMaps, meta) {
    if (err) return callback(err);

    callback(null, result, sourceMaps, meta);
  });
};
复制代码

编写自己的 loader

官方建议遵循的原则:webpack.docschina.org/contribute/…

style-loader

基本知识

webpack style-loader 文档

将 CSS 插入到 DOM,一般与 css-loader(加载 CSS 文件并解析 import 的 CSS 文件,最终返回 CSS 代码) 一起使用

使用

例子

npm init -y

yarn add webpack webpack-cli style-loader css-loader file-loader --save-dev
复制代码

创建几个 css 测试文件:

/* assets/a.css */
.a {
  font-size: 16px;
}
复制代码
/* assets/b.css */
.b {
  font-size: 16px;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
}
复制代码
/* assets/c.css */
@import url(./a.css);

.c {
  display: inline-block;
  font-weight: 600;
}
复制代码

入口文件:

// src/index.js
export const add = (num1, num2) => {
  return num1 + num2;
};

import "../assets/c.css";
import "../assets/b.css";

// import bCSS from '../assets/b.css';
// bCSS.use();
// bCSS.unuse();

console.log("this is index.js");
console.log(add(66, 99));
复制代码
<!-- dist/index.html -->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>起步</title>
    <!-- <link rel="stylesheet" type="text/css" href="./assets/b.css" /> -->
  </head>
  <body>
    <div class="b">This is webpack loader example</div>
  </body>
  <script src="main.js"></script>
</html>
复制代码

webpack 配置文件:webpack.config.js

const path = require("path");

module.exports = {
  mode: "development",
  entry: "./src/index.js",
  output: {
    filename: "main.js",
    path: path.resolve(__dirname, "dist"),
  },
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [
          // 'style-loader',
          {
            loader: "style-loader",
            options: {
              injectType: "styleTag", // 默认值
              // injectType: "singletonStyleTag"
              // injectType: "lazyStyleTag"
              // injectType: "lazySingletonStyleTag"
              // injectType: "linkTag"    // 以 link 形式挂载到 DOM,此时需要配合  url-loader 而不是 css-loader
            },
          },
          "css-loader",
          // 'file-loader'
        ],
      },
    ],
  },
};
复制代码

然后执行 webpack(也可以在 package.jsonscripts中配置一个快捷命令 xxx,yarn xxx)进行打包,接着在浏览器中打开 index.html 可查看打包结果

injectType

可选值:styleTag(默认值)、singletonStyleTagautoStyleTaglazyStyleTaglazySingletonStyleTaglazyAutoStyleTaglinkTag

  • styleTag

    通过使用多个 <style></style> 自动把 styles 插入到 DOM 中,即一个 import 就会生成一个 style 标签

<style>
  .a {
    font-size: 16px;
  }
</style>
<style>
  .c {
    display: inline-block;
    font-weight: 600;
  }
</style>
<style>
  .b {
    font-size: 16px;
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
  }
</style>
复制代码
  • singletonStyleTag

    对应 styleTag,不过它是把他们整合到一个 style 标签中,如有重复的样式名不会额外处理

<style>
  .a {
    font-size: 16px;
  }
  .c {
    display: inline-block;
    font-weight: 600;
  }

  .a {
    font-size: 16px;
  }
  .b {
    font-size: 16px;
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
  }
</style>
复制代码
  • linkTag

    使用多个 将 styles 插入到 DOM 中

<link
  rel="stylesheet"
  href="file:///D:/testCodes/webpack-style-loder/dist/d06ed220ba698dc21e8270035e104dce.css"
/>
<link
  rel="stylesheet"
  href="file:///D:/testCodes/webpack-style-loder/dist/ada3b1108d67c1ded7467f095ae0a549.css"
/>
复制代码

insert

Type: String|Function, Default: head

默认情况下,除非指定 insert,否则 style-loader 会把 / 添加到页面的 标签尾部。

这会使得 style-loader 创建的 CSS 比 标签内已经存在的 CSS 拥有更高的优先级。 当默认行为不能满足你的需求时,你可以使用其他值,但我们不推荐这么做。

除了 injectTypeinsert参数外,还有 attributesesModule等,详见上述官方文档

感谢封面图:阿宝哥 - 搞懂webpack-loader

预告:style-loader源码视角看 loader 原理

猜你喜欢

转载自juejin.im/post/7068618437156667422