Skip to content

Webpack loader

文档:https://webpack.docschina.org/api/loaders

loader 将不同类型的文件转换为 webpack 可识别的模块

示例代码:https://github.com/mouday/webpack-loader-plugins

loader 使用方式

  1. 配置方式: pre、normal、post (推荐使用)
  2. 内联方式: inline

配置方式

js
module: {
  rules: [
    {
      test: /\.js$/,
      loader: "babel-loader",
    },
  ]
}

内联方式

使用 ! 将资源中的 loader 分开

js
import style from "style-loader!css-loader?modules!./style.css";

内联方式跳过配置方式的 loader

js
// !  跳过 normal loader。
import style from "!style-loader!css-loader?modules!./style.css";

// -! 跳过 pre 和 normal loader。
import style from "-!style-loader!css-loader?modules!./style.css";

// !! 跳过 pre、 normal 和 post loader。
import style from "!!style-loader!css-loader?modules!./style.css";

loader 执行优先级

默认优先级

bash
pre:    前置 loader
normal: 普通 loader
inline: 内联 loader
post:   后置 loader

相同优先级

从右到左,从下到上

js
// 此时loader执行顺序:loader3 - loader2 - loader1
module: {
  rules: [
    {
      test: /\.js$/,
      loader: "loader1",
    },
    {
      test: /\.js$/,
      loader: "loader2",
    },
    {
      test: /\.js$/,
      loader: "loader3",
    },
  ],
},

指定优先级

js
// 此时loader执行顺序:loader1 - loader2 - loader3
module: {
  rules: [
    {
      enforce: "pre",
      test: /\.js$/,
      loader: "loader1",
    },
    {
      // 没有enforce就是normal
      test: /\.js$/,
      loader: "loader2",
    },
    {
      enforce: "post",
      test: /\.js$/,
      loader: "loader3",
    },
  ],
}

开发一个 loader

项目结构

bash
$ tree
.
├── loaders
   └── test-laoder.js
├── package.json
├── pnpm-lock.yaml
├── src
   └── index.js
└── webpack.config.js

项目文件

js
// loaders/test-laoder.js
/**
 *
 * @param {*} content 源文件的内容
 * @param {*} map SourceMap 数据
 * @param {*} meta 数据,可以是任何内容
 * @returns
 */
module.exports = function loader(content, map, meta) {
  console.log("hello loader");
  return content;
};

package.json

json
{
  "dependencies": {
    "webpack": "^5.72.1",
    "webpack-cli": "^4.9.2"
  }
}
js
// webpack.config.js
const path = require("path");

module.exports = {
  entry: "./src/index.js",

  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "bundle.js",
    clean: true,
  },

  module: {
    rules: [
      {
        test: /\.js$/,
        loader: "./loaders/test-laoder.js",
      },
    ],
  },

  plugins: [],

  mode: "development",
};
js
// src/index.js
console.log("Hello World");

编译打包

bash
# 安装依赖
$ pnpm add webpack webpack-cli

# 运行打包
$ npx webpack --config webpack.config.js
hello loader

loader 分类

同步 loader

js
module.exports = function (content, map, meta) {
  return content;
};

callback方式

js
module.exports = function (content, map, meta) {
  // 第一个参数:err 错误
  this.callback(null, content, map, meta);
};

异步 loader

js
module.exports = function (content, map, meta) {
  const callback = this.async();
  
  // 进行异步操作
  setTimeout(() => {
    callback(null, content, map, meta);
  }, 1000);
};

Raw Loader

通常用于处理图片资源

js
module.exports = function (content) {
  // content是一个Buffer数据
  return content;
};

// 开启 Raw Loader
module.exports.raw = true;

Pitching Loader

js
module.exports = function (content) {
  return content;
};

module.exports.pitch = function (remainingRequest, precedingRequest, data) {
  console.log("do somethings");
};

webpack完整的loader执行顺序

loader提前返回

loader API

方法名含义用法
this.async异步回调 loader。返回 this.callbackconst callback = this.async()
this.callback可以同步或者异步调用的并返回多个结果的函数this.callback(err, content, sourceMap?, meta?)
this.getOptions(schema)获取 loader 的 optionsthis.getOptions(schema)
this.emitFile产生一个文件this.emitFile(name, content, sourceMap)
this.utils.contextify返回一个相对路径this.utils.contextify(context, request)
this.utils.absolutify返回一个绝对路径this.utils.absolutify(context, request)

文档:https://webpack.docschina.org/api/loaders/

手写示例

clean-log-loader

去除console.log 语句

js
// console-log-laoder.js
module.exports = function (content, map, meta) {
  return content.replace(/console\.log\(.*\);?/g, '');
};

使用

js
// webpack.config.js

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: "./loaders/console-log-laoder.js",
      },
    ],
  },
};

给内容中增加一个作者信息

js
// banner-loader/index.js
const schema = require("./schema.json");

module.exports = function (content, map, meta) {
  // schema对options进行规则校验
  const options = this.getOptions(schema);
  
  const prefix = `
       /**
       * Author: ${options.author}
       **/
    `;
  return prefix + content;
};

参数验证规则 banner-loader/schema.json

json
{
  "type": "object",
  "properties": {
    "author": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

说明:

json
// 是否允许额外的属性
"additionalProperties": false

使用

js
// webpack.config.js

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: "./loaders/banner-loader/index.js",
        options: {
          author: "曹操"
        },
      },
    ],
  },
};

babel-loader

自定义babel-loader,转换ES6语法

bash
npm i -D @babel/preset-env @babel/core
js
// babel-loader/index.js
const babel = require("@babel/core");

const schema = require("./schema.json");

// https://www.babeljs.cn/docs/babel-core

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

  // schema对options进行规则校验
  const options = this.getOptions(schema);

  // 使用babel 对代码进行编译
  babel.transform(content, options, function (err, result) {
    if (err) {
      callback(err);
    } else {
      callback(null, result.code);
    }
  });
};

参数校验规则 babel-loader/schema.json

json
{
  "type": "object",
  "properties": {
    "presets": {
      "type": "array"
    }
  },
  "additionalProperties": true
}

入口文件

js
// src/index.js

function sum(...args) {
  return args.reduce((x, y) => x + y, 0);
}

使用

js
// webpack.config.js

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: "./loaders/babel-loader/index.js",
        options: {
          presets: [
            '@babel/preset-env'
          ],
        },
      },
    ],
  }
};

file-loader

使用webpack提供的loader工具

$ pnpm i -D loader-utils
js
// file-loader.js

// https://github.com/webpack/loader-utils
const loaderUtils = require("loader-utils");

module.exports = function (content) {

  // 1、根据文件内容生成带hash的文件名
  const interpolatedName = loaderUtils.interpolateName(
    this,
    "[hash].[ext][query]",
    { content }
  );

  // 2、将文件输出
  this.emitFile(interpolatedName, content)

  // 3、返回文件名
  return `module.exports = "${interpolatedName}"`;
};

// raw loader 处理图片资源,content输入为Buffer类型
module.exports.raw = true;

使用

js
// src/index.js

import './style.css';
css
/* style.css */
.box {
  width: 200px;
  height: 200px;
  /* css 文件中使用图片资源 */
  background-image: url("./girl.png");
}
js
// webpack.config.js
module.exports = {
  module: {
    rules: [
      // 处理图片资源
      {
        test: /\.(png|jpe?g|gif)$/,
        loader: "./loaders/file-loader/index.js",
        // 不使用 asset 模块处理,避免生成重复资源
        type: 'javascript/auto'
      },
      // 处理css资源
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader',
        ]
      },
    ],
  },
};

css代码替换如下

css
.box {
    width: 200px;
    height: 200px;
    background-image: url(/39e33252eac24cf9.png);
}

style-loader

在file-loader 的基础上继续

定义loader, 将css插入到body中

js
// style-loader.js

module.exports = function () {};

module.exports.pitch = function (remainingRequest) {
  // remainingRequest 剩余的还要处理的loader

  //   console.log(remainingRequest);
  //   _modules/css-loader/dist/cjs.js!/root/webpack-loader/src/style.css

  // 将绝对路径转换为相对路径
  const relativePath = remainingRequest
    .split("!")
    .map((absolutePath) => {
      return this.utils.contextify(this.context, absolutePath);
    })
    .join("!");

  console.log(relativePath);

  // 引入css-loader处理后的资源
  // 创建style标签,将内容插入到页面中
  const script = `
        import style from '!!${relativePath}';

        const styleElement = document.createElement('style');
        styleElement.innerHTML = style;
        document.head.appendChild(styleElement); 
    `;

  // 终止后面的loader执行
  return script;
};

使用

js
// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: "./src/index.js",

  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "bundle.js",
    clean: true,
  },

  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/,
        loader: "./loaders/file-loader/index.js",
        // 不使用 asset 模块处理,避免生成重复资源
        type: 'javascript/auto'
      },
      {
        test: /\.css$/,
        use: [
          './loaders/style-loader/index.js',
          'css-loader',
        ]
      },
    ],
  },

  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
    }),
  ],

  mode: "development",
};