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']。但执行顺序呢?从右到左。
也就是说:
- 先执行
sass-loader,把SCSS转成CSS - 再执行
css-loader,解析CSS中的@import和url() - 最后执行
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中的@import和url() |
必须配合其他Loader使用 |
style-loader |
把CSS注入到<style>标签 |
开发环境好用,生产环境建议用MiniCssExtractPlugin |
sass-loader |
把SCSS/SASS转成CSS | 需要安装node-sass或dart-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做不了的事?嗯,到时候你就知道了。