webpack 之 Loader 浅析

360 阅读3分钟

我正在参加「掘金·启航计划」


1. 什么是loader

在Webpack中,Loader是用于将非JavaScript文件(如CSS、图片、字体等)转换为模块的函数。它们作为构建过程的一部分,用于处理项目中的不同类型的资源。

Webpack Loader允许你在打包过程中对不同类型的文件进行处理和转换。当Webpack遇到一个匹配特定规则的模块时,它会使用相应的Loader来转换模块的内容,并将其添加到构建输出中。

每个Loader可以执行一些特定的转换操作,例如:

  • 将ES6代码转换为ES5代码(Babel Loader)。
  • 将Sass文件转换为CSS并添加浏览器前缀(Sass Loader和PostCSS Loader)。
  • 将HTML文件转换为字符串(HTML Loader)。
  • 压缩和优化图像(Image Loader)。
  • ...

比如,我们简单配置一个Babel Loader和CSS Loader

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.js$/, // 匹配.js文件
        use: 'babel-loader', // 使用Babel Loader处理
      },
      {
        test: /\.css$/, // 匹配.css文件
        use: ['style-loader', 'css-loader'], // 使用CSS Loader和Style Loader处理
      },
    ],
  },
};

同步

Loader默认是以同步模式运行的。也就是说,在处理资源文件时,Webpack会按照定义的Loader顺序依次应用它们,并等待每个Loader完成转换或处理后再继续进行下一个Loader。

在同步模式下,Loader在处理资源时,可以直接返回转换后的结果,而不需要使用回调函数或Promise来通知Webpack。

Loader 如果返回单个处理结果,可以在直接 return。如果有多个处理结果,则必须调用 this.callback()。this.callback 方法则更灵活,因为它允许传递多个参数,而不仅仅是 content。

module.exports = function (content, map, meta) {
  return someSyncOperation(content);
};
// 需要传递多个参数,用 this.callback
module.exports = function (content, map, meta) {
  this.callback(null, someSyncOperation(content), map, meta);
  return; // 当调用 callback() 函数时,总是返回 undefined
};

异步

默认情况下,Loader以同步模式运行。但有些特定的Loader可能需要在异步模式下进行处理。为了支持 Loader 的异步操作,Webpack 提供了多种处理异步的方式。

  1. Loader 中返回一个 Promise 对象,在 Promise resolve 后传递转换后的结果给 Webpack。这种方式适用于需要进行复杂异步操作的情况。
module.exports = function(source) {
  return new Promise((resolve, reject) => {
    // 执行异步操作
    someAsyncOperation(source, (error, result) => {
      if (error) {
        reject(error);
      } else {
        resolve(result);
      }
    });
  });
};
  1. 在 Loader 中使用异步回调函数来通知 Webpack 转换结束,并传递转换后的结果。
module.exports = function(source) {
  const callback = this.async();

  // 执行异步操作
  someAsyncOperation(source, (error, result) => {
    if (error) {
      callback(error);
    } else {
      callback(null, result);
    }
  });
};

2. 自定义loder

// my-custom-loader.js

module.exports = function(source) {
  // 转换输入字符串为大写
  const transformed = source.toUpperCase();

  // 返回转换后的结果
  return transformed;
};

在Webpack配置中配置Loader规则:在Webpack的配置文件中,找到module.rules(或module.loaders)字段,添加一个新的规则来使用你的自定义Loader。

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.txt$/, // 匹配.txt文件
        use: './path/to/my-custom-loader.js' // 使用你的自定义Loader处理
      }
    ]
  }
};

然后我们自己写一款捕捉async函数执行异常的loader.

loader函数的第一个参数content,我们可以利用正则表达式修改content。但如果实现的功能比较复杂,正则表达式会变得异常复杂难以开发

主流的方法是将代码字符串转化对象,我们对对象进行数据操作,再将操作完的对象转化为字符串返回

如果发生异常,那么该异常将被捕获,并且错误信息将会被记录到控制台中。将错误信息保存到一个字符串变量errorMessage中。使用path.resolve()方法获取日志文件的绝对路径,fs.appendFileSync()方法将错误信息追加写入日志文件中。最后抛出一个新的Error对象,用于告知构建过程中发生了错误。

const path = require('path');
const fs = require('fs');

module.exports = function(source) {
  try {
    // 执行源代码
    eval(source);
  } catch (error) {
    // 处理异常
    console.error('Error caught in loader:', error);

    // 记录错误信息
    const errorMessage = `Error caught in loader: ${error}\n`;
    
    // 将错误信息写入日志文件
    const logFilePath = path.resolve(__dirname, 'error-log.txt');
    fs.appendFileSync(logFilePath, errorMessage);
    
    // 抛出错误(可选)
    throw new Error('An error occurred during the loading process.');
  }

  // 返回转换后的源代码
  return source;
};

常用loader

  • url-loaderfile-loader 类似,但当文件 size 小于设置的 limit 值,会返回 data URL

  • file-loader 将文件保存至输出文件夹中并返回 URL (默认是是绝对路径,可以 outputPath 和 publicPath 通过配置成相对路径)

  • babel-loader 使用 Babel 加载 ES2015+ 代码并将其转换为 ES5

  • style-loader 将样式模块导出的内容以往 中注入多个 的形式,添加到 DOM 中

  • css-loader 加载 CSS 文件并解析 @import 的 CSS 文件,将 url() 处理成 require() 请求,最终返回 CSS 代码

  • less-loader 加载并编译 LESS 文件

  • sass-loader 加载并编译 SASS/SCSS 文件

  • postcss-loader 使用 PostCSS 加载并转换 CSS/SSS 文件

  • stylus-loader 加载并编译 Stylus 文件