webpack--loader

125 阅读1分钟

前言

d65f57b96be72814e93db700b1ef4813.png 我们平时使用webpack打包编译项目的时候,极大概率会使用loader对模块源代码进行转换。比如用sass-loader/less-loader将sass编译成css语言。

Loader基本概念

  1. 本质是一个函数
  2. 支持链式调用
  3. 可以同步也可以异步
  4. 按照相反的顺序执行(use数组里面是从下往上执行)
  5. 其pitch方法,use数组中pitch方法的执行顺序是从上往下执行
  6. 可通过options对象配置
  7. 命名规则:xxx-loader(例如style-loader)

准备工作

  1. 新建webpack.config.js
  2. npm i webpack webpack-cli -D

代码演示

  1. loader从下而上的执行顺序
// 使用less-loader/sass-loader
use: [
  "style-loader",
  "css-loader",
  "less-loader",
]
  • loader文件
// loader1.js
// 参数:文件内容(可能是文件内容,也可能是上一个 loader 处理结果)、文件sourceMap的映射信息、文件的额外信息(经常用来传递 ast 对象)
module.exports = function (content, map, meta) {
  console.log('FE 1111');

  return content;
}

// loader2.js
module.exports = function (content, map, meta) {
  console.log('FE 222');

  return content;
}

// loader3.js
module.exports = function (content, map, meta) {
  console.log('FE 333');

  return content;
}
  • webpack配置
// webpack.config.js
const { resolve } = require('path')
module.exports = {
  mode: "development",
  module: {
    rules: [
      {
        test: /\.js$/,
        /*
          单个loader:
        */
        // 未配置resolve之前的写法
        // loader: resolve(__dirname, 'loaders', 'loader1')
        // loader: 'loader1'
        /*
          使用多个loader:
        */
        use: [
          'loader1',
          'loader2',
          'loader3'
        ]
      }
    ]
  },
  resolve: {},
  // 如果仅配置loader的解析,可直接使用resolveLoader,他与 resolve 对象的属性集合相同,但仅用于解析 webpack 的 loader 包
  resolveLoader: {
    // 告诉 webpack 解析模块时应该搜索的目录
    modules: [
      'node_modules', // 默认的
      resolve(__dirname, 'loaders') // 自定义的
    ]
  }
}

dc8ae15b4ad35a8cccc1f419f1cd6858.png

  • 使用pitch

如果pitch有返回值,将跳过剩下的 loader

  • remainingRequest : 当前 loader 之后的资源请求字符串
  • previousRequest : 在执行当前 loader 之前经历过的 loader 列表
  • data : 与 Loader 函数的 data 相同,用于传递需要在 Loader 传播的信息
// loader3.js
module.exports = function (content, map, meta) {
  console.log('FE 333');

  return content;
}
module.exports.pitch = function () {
  console.log('FE pitch 333');
  // return remainingRequest
}
 

8ef4a0a193be2241f2bf4f1f3096be99.png

  • 使用addDependency
// less-loader
try {
    result = await (options.implementation || less).render(data, lessOptions);
  } catch (error) {
    // ...
  }

  const { css, imports } = result;

  imports.forEach((item) => {
    // ...
    this.addDependency(path.normalize(item));
  });

代码中首先调用 less 编译文件内容,之后遍历所有 import 数组,调用 this.addDependency 函数将 import 到的其它资源都注册为依赖,之后这些其它资源文件发生变化时都会触发重新编译

  1. 可同步亦可异步
  • 同步
// loader2.js 同步
module.exports = function(content, map, meta) {
  console.log("FE 222")
  // 使用return
  // return content
  // 或使用callback函数,返回 undefined
  this.callback(null, content, map, meta)
}
  • 异步
// loader2.js 异步
module.exports = function (content, map, meta) {
  console.log('FE 222');
  // 使用async方法
  const callback = this.async();

  setTimeout(() => {
    callback(null, content);
  }, 2000)
}
  1. 获取options

  • getOptions:提取给定的 loader 选项,接受一个可选的 JSON schema 作为参数
  • tips: 从 webpack 5 开始,this.getOptions 可以获取到 loader 上下文对象。它用来替代来自 loader-utils 中的 getOptions 方法。
  • 获取options之后我们可以拿到其中的配置,针对content做一些操作,比如替换字符等

  • webpack5之前的用法:
// 1. 安装:
npm i loader-utils schema-utils -D
// 2. 导入:
const { getOptions } = require("loader-utils")
const { validate } = require("schema-utils")
// 3. 使用:
const options = getOptions(this);
validate(schema, options, {
    name: 'loader1'
  })
ps:我们在升级了sass-loader等loader之后,可能会出现 “TypeError: this.getOptions is not a function” 的报错信息,其实就是loader版本太高与webpack版本不匹配
  • 修改loader文件
const schema = require('./schema');
// 基础
// module.exports = function(content, map, meta) {
//   console.log("FE 111")
//   // console.log(content)
//   // 使用 return
//   // return content
//   // 或使用 callback 函数
//   this.callback(null, content, map, meta)
// }

// 校验otions
module.exports = function(content, map, meta) {
  console.log("FE 111")
  // console.log(content)
  /*
    getOptions:提取给定的 loader 选项,接受一个可选的 JSON schema 作为参数
  */
  const options = this.getOptions(schema)
  console.log('loader1 options:', options)
  // 使用 return或使用 callback 函数
  // return content
  this.callback(null, content, map, meta)
}
  • schema.json文件
{
    "type": "object",
    "properties": {
    	"name": {
    		"type": "string",
    		"description": "名称"
    	}
    },
    // 如果设置为true表示除了校验前面写的string类型还可以  接着  校验其余类型,如果为false表示校验了string类型之后不可以再校验其余类型
    "additionalProperties": false
}
  • 如果otions不符合规范会有报错信息
  1. 自定义babel-loader
  • my-babel-loader.js
// npm i @babel/core @babel/preset-env -D
// 导入Babel核心库
const babel = require('@babel/core');
// 导入promisify
const { promisify } = require('util');

const schema = require('./my-babel-schema.json');

// babel.transform用来编译代码的方法,是一个普通异步方法
// util.promisify将普通异步方法转化成基于promise的异步方法
const transform = promisify(babel.transform);

module.exports = function (content, map, meta) {
  // 获取loader的options配置并校验
  const options = this.getOptions(schema)
  // 创建异步
  const callback = this.async();

  // 使用babel编译代码
  transform(content, options)
    .then(({code, map}) => callback(null, code, map, meta))
    .catch((e) => callback(e))
  // try {
  //   const result = transform(content, options)
  //   callback(null, result, map, meta)
  // } catch(err) {
  //   // 第一个参数有值时,其他参数无效
  //   callback(err)
  // }

}

module.exports.raw = true;
  • my-babel-schema.json
{
	"type": "object",
	"properties": {
		"presets": {
          "type": "array"
        }
      },
	"additionalProperties": false
}
  • webpack.config.js
const { resolve } = require('path')
module.exports = {
  mode: "production", // development
  module: {
    rules: [
      {
        test: /\.js$/,
        /*
          单个loader:
        */
        // 未配置resolve之前的写法
        // loader: resolve(__dirname, 'loaders', 'loader1')
        // loader: 'loader1'
        /*
          使用多个loader:
        */
        use: [
          // 'loader1',
          // 配置options
          {
            loader: 'loader1',
            options: {
              // name: true // options 会有报错信息
              name: 'FE'
            }
          },
          'loader2',
          'loader3',
          // 使用自定义的babel-loader:
          {
            loader: 'my-babel-loader',
            options: {
              presets: [
                '@babel/preset-env'
              ]
            }
          }
        ]
      }
    ]
  },
  resolve: {},
  // 如果仅配置loader的解析,可直接使用resolveLoader,他与 resolve 对象的属性集合相同,但仅用于解析 webpack 的 loader 包
  resolveLoader: {
    // 告诉 webpack 解析模块时应该搜索的目录
    modules: [
      'node_modules', // 默认的
      resolve(__dirname, 'loaders') // 自定义的
    ]
  }
}