Webpack的Loader和Plugin:你以为只是配置?其实是“流水线工人”和“包工头”

0 阅读4分钟

你配置过Webpack吗?是不是照着文档写了几行rulesplugins,然后它就神奇地把代码打包好了?今天我们不背配置,直接钻进Webpack的肚子里,看看Loader和Plugin到底在干什么。看完你就能自己写一个Loader和一个Plugin,再也不用怕面试官问“Webpack原理”了。

前言

把Webpack想象成一家汽车工厂。原料是各种文件(JS、CSS、图片、字体……),产品是打包后的bundle。

  • Loader:流水线上的工人。每个工人只干一件事:把某种原料加工成下一个工人能处理的形式。比如把Sass转成CSS,把ES6转成ES5。
  • Plugin包工头。包工头不管具体加工,而是监听整个生产流程——开工前、完成某个环节后、打包结束——然后在合适的时机做全局性的事,比如抽离CSS、生成HTML、压缩代码。

今天我们就来认识这两位“功臣”,顺便自己动手写一个。

一、Loader:干啥啥都行,专精第一名

Loader是一个函数,它接收源文件内容,返回处理后的内容。一个文件可以经过多个Loader串联(从右到左,从下到上)。

// 一个最简单的Loader:把内容里的“Hello”换成“Hi”
module.exports = function(source) {
  const result = source.replace(/Hello/g, 'Hi');
  return result;
};

配置里Loader的执行顺序

module.exports = {
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: ['style-loader', 'css-loader', 'sass-loader']
        // 执行顺序:sass-loader → css-loader → style-loader
      }
    ]
  }
};

像流水线:sass-loader把SCSS转成CSS → css-loader处理CSS中的依赖(@import等)→ style-loader把CSS注入到DOM的<style>标签。

常用Loader举例

  • babel-loader:把ES6+转成ES5
  • css-loader:解析@importurl()
  • style-loader:把CSS插入DOM
  • file-loader:把文件输出到目录,返回路径
  • url-loader:小文件转成base64,大文件走file-loader
  • sass-loader:编译Sass/SCSS

动手写一个“清除console”的Loader

// clean-console-loader.js
module.exports = function(source) {
  // 移除console.log、console.warn等
  const cleaned = source.replace(/console\.(log|warn|error)\([^)]*\);?/g, '');
  return cleaned;
};

在webpack.config.js里使用:

module: {
  rules: [
    {
      test: /\.js$/,
      use: path.resolve(__dirname, 'clean-console-loader.js')
    }
  ]
}

二、Plugin:包工头,管全局

Plugin是一个(或者带有apply方法的对象)。它通过监听Webpack生命周期里的钩子(hooks),在特定时机干预打包过程。

class MyPlugin {
  apply(compiler) {
    // 在打包结束后执行
    compiler.hooks.done.tap('MyPlugin', (stats) => {
      console.log('🎉 打包完成了!');
    });
  }
}

Webpack的钩子有同步和异步之分。比如emit(生成资源到输出目录之前)是异步的,要用tapAsync

常用Plugin举例

  • HtmlWebpackPlugin:自动生成HTML,并注入打包后的JS/CSS
  • MiniCssExtractPlugin:把CSS抽成单独文件
  • DefinePlugin:定义全局常量(比如环境变量)
  • CleanWebpackPlugin:打包前清理输出目录

动手写一个“打包完成发通知”的Plugin

class NotifyPlugin {
  apply(compiler) {
    compiler.hooks.done.tap('NotifyPlugin', (stats) => {
      const time = stats.endTime - stats.startTime;
      console.log(`✅ 打包成功,耗时 ${time}ms`);
      // 这里可以调系统通知API(需要额外库)
    });
  }
}
module.exports = NotifyPlugin;

使用:

const NotifyPlugin = require('./notify-plugin');
plugins: [new NotifyPlugin()]

三、Loader和Plugin的核心区别

维度LoaderPlugin
职责转换单个文件影响整个构建流程
作用范围匹配test正则的文件全局
实现形式函数类(带apply方法)
运行时机模块加载过程中生命周期钩子
常见例子babel-loader, css-loaderHtmlWebpackPlugin, CleanPlugin

形象比喻:

  • Loader:工人,只会加工原料。
  • Plugin:包工头,指挥全局,监听事件。

四、编写Loader的进阶技巧

1. 获取Loader选项

const loaderUtils = require('loader-utils');
module.exports = function(source) {
  const options = loaderUtils.getOptions(this);
  // 使用options...
  return source;
};

2. 异步Loader

如果Loader里要异步操作(比如网络请求),用this.async()

module.exports = function(source) {
  const callback = this.async();
  setTimeout(() => {
    const result = source.toUpperCase();
    callback(null, result);
  }, 1000);
};

3. 生成多个文件

可以用this.emitFile

const { RawSource } = require('webpack-sources');
module.exports = function(source) {
  this.emitFile('new-file.txt', new RawSource('hello'));
  return source;
};

五、编写Plugin的进阶技巧

1. 常用钩子

  • compiler.hooks.entryOption:读取入口配置后
  • compiler.hooks.beforeRun:开始编译前
  • compiler.hooks.compile:编译前
  • compiler.hooks.emit:生成资源到输出目录前(可以修改文件内容)
  • compiler.hooks.done:打包完成

2. 修改输出内容

class ModifyFilePlugin {
  apply(compiler) {
    compiler.hooks.emit.tapAsync('ModifyFilePlugin', (compilation, callback) => {
      // compilation.assets 包含所有待输出文件
      const content = compilation.assets['bundle.js'].source();
      const modified = content.replace('old', 'new');
      compilation.assets['bundle.js'] = {
        source: () => modified,
        size: () => modified.length
      };
      callback();
    });
  }
}

六、总结:从配置使用者到原理掌握者

  • Loader:文件转换器,函数式,串联处理。
  • Plugin:构建流程干预者,事件监听式,做全局工作。
  • 掌握原理后,你就能:
    • 写自定义Loader处理特殊文件(比如把XML转成JS对象)
    • 写自定义Plugin做自动上传CDN、生成资源清单等
    • 更从容地调试Webpack配置错误

下次面试官问“Webpack的Loader和Plugin区别”,你可以自信地画出流水线和包工头的比喻,顺便掏出自己写的Loader和Plugin代码。

如果你觉得今天的“工厂之旅”够形象,点个赞让更多人看到。明天我们将深入Webpack优化——如何让打包速度飞起,让产物体积瘦成闪电。我们明天见!