4. Loader机制详解:Loader是什么、Loader的执行顺序、常用Loader分类、自定义Loader的简单思路

说到Webpack,Loader绝对是个绕不开的核心概念。我刚开始用Webpack那会儿,总觉得Loader就是个「黑盒子」——把文件扔进去,出来就能用了。后来踩了不少坑,才真正搞明白它到底在干什么。

Loader是什么?说白了就是个转换器

Webpack本身只认识JavaScript和JSON。但现实项目中,我们有TypeScript、Vue单文件、SCSS、图片……这些文件Webpack统统不认识。怎么办?

Loader就是干这个的。它负责把各种文件「翻译」成Webpack能理解的模块。

核心概念:Loader本质上是一个函数,接收源文件内容,返回处理后的内容。

举个例子,你写了个TypeScript文件:

// app.ts
const greeting: string = 'Hello, Loader!';
console.log(greeting);

Webpack看到.ts文件,一脸懵。但配置了ts-loader后,它就会把这个文件交给ts-loader处理,转成普通的JavaScript:

// 转换后
const greeting = 'Hello, Loader!';
console.log(greeting);

嗯,就是这么个道理。Loader就是Webpack和各类资源之间的「翻译官」。

Loader的执行顺序:从右到左,从下到上

这个规则我记了好久才记住。你想想看,Webpack处理Loader的时候,顺序是反着来的。

看个配置就明白了:

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: ['style-loader', 'css-loader', 'sass-loader']
      }
    ]
  }
};

这里use数组的顺序是['style-loader', 'css-loader', 'sass-loader']。但执行顺序呢?从右到左

也就是说:

  1. 先执行sass-loader,把SCSS转成CSS
  2. 再执行css-loader,解析CSS中的@importurl()
  3. 最后执行style-loader,把CSS注入到<style>标签里

为什么这样设计?我个人理解是:每个Loader只负责一件事,处理完的结果交给下一个Loader。就像流水线一样,原料从右边进来,成品从左边出去。

小技巧:如果你觉得「从右到左」不好记,可以换个思路——把Loader的执行顺序想象成「洋葱模型」。最外层的Loader最后执行,最内层的Loader最先执行。

还有一种写法是用loader字段,配合options

{
  test: /\.scss$/,
  use: [
    'style-loader',
    {
      loader: 'css-loader',
      options: {
        modules: true
      }
    },
    'sass-loader'
  ]
}

执行顺序依然是从右到左。嗯,这里要注意,options里的配置只对当前Loader生效。

常用Loader分类

我根据项目经验,把Loader分成了三大类。这样分类比较好记,也方便你快速定位问题。

1. 编译类Loader

这类Loader负责把「高级语言」转成「浏览器能跑的语言」。

Loader名称 用途 个人评价
babel-loader 把ES6+转成ES5 前端标配,几乎每个项目都用
ts-loader 把TypeScript转成JavaScript TypeScript项目首选
vue-loader 解析Vue单文件组件 Vue项目必备

我在项目中遇到过一个问题:用了babel-loader但没配@babel/preset-env,结果转了半天啥也没变。说白了,Loader只是个「搬运工」,真正干活的是它内部的插件。

2. 样式类Loader

这类Loader处理CSS、SCSS、Less等样式文件。

Loader名称 用途 注意事项
css-loader 解析CSS中的@importurl() 必须配合其他Loader使用
style-loader 把CSS注入到<style>标签 开发环境好用,生产环境建议用MiniCssExtractPlugin
sass-loader 把SCSS/SASS转成CSS 需要安装node-sassdart-sass
less-loader 把Less转成CSS 需要安装less

避坑指南:我曾经在生产环境忘了把style-loader换成MiniCssExtractPlugin.loader,结果CSS全部内联到了JavaScript里,页面加载时白屏了好几秒。嗯,这个坑我记到现在。

3. 文件类Loader

这类Loader处理图片、字体、音频等静态资源。

Loader名称 用途 适用场景
file-loader 把文件复制到输出目录,返回文件URL 图片、字体等静态资源
url-loader 把小文件转成Base64内联 小图标、小图片(通常<8KB)
raw-loader 把文件内容作为字符串导入 SVG、文本文件

你想想看,url-loader其实就是file-loader的升级版。小文件直接内联,减少HTTP请求;大文件就交给file-loader处理。不过现在Webpack 5已经内置了Asset Modules,这些Loader慢慢用得少了。

自定义Loader的简单思路

说实话,大部分场景用现成的Loader就够了。但有时候业务需求特殊,就得自己动手写一个。我写过几次自定义Loader,其实没那么玄乎。

一个最简单的Loader长这样:

// my-loader.js
module.exports = function(source) {
  // source是源文件内容
  // 处理source...
  return source.replace(/Hello/g, 'Hi');
};

然后在Webpack配置里引用:

{
  test: /\.txt$/,
  use: ['./loaders/my-loader.js']
}

就这么简单?嗯,基本思路就是这样。但实际项目中,你可能会用到Loader提供的工具方法:

const { getOptions } = require('loader-utils');
const { validate } = require('schema-utils');

module.exports = function(source) {
  // 获取配置参数
  const options = getOptions(this);
  
  // 校验参数
  const schema = {
    type: 'object',
    properties: {
      replaceText: { type: 'string' }
    }
  };
  validate(schema, options, 'My Loader');
  
  // 处理内容
  return source.replace(/Hello/g, options.replaceText || 'Hi');
};

个人建议:写自定义Loader时,记住三个原则:

  • 单一职责——一个Loader只做一件事
  • 无副作用——不要修改源文件,只返回处理后的内容
  • 可组合——你的Loader应该能和其他Loader配合使用

我曾经写过一个Loader,用来给Vue组件自动注入国际化函数。当时业务需求是每个组件都要用$t()方法,但手动导入太麻烦。我就写了个Loader,在编译阶段自动注入。效果还不错,团队用了大半年。

不过要提醒你的是,自定义Loader虽然灵活,但能不用就不用。社区里成熟的Loader经过大量项目验证,稳定性比你临时写的强得多。除非业务确实特殊,否则优先考虑组合现有Loader。

小结

Loader机制说白了就是Webpack的「插件化文件处理系统」。记住三个要点:

  • Loader是函数,输入源文件,输出处理后的内容
  • 执行顺序从右到左、从下到上
  • 常用Loader分三类:编译、样式、文件

下一章我们会聊聊Plugin机制。Plugin和Loader有什么区别?Plugin能做什么Loader做不了的事?嗯,到时候你就知道了。