Webpack loader 的执行机制是 Webpack 核心功能之一。让我详细解释 loader 的执行顺序和工作原理。
1. Loader 的基本概念
Loader 就像是 Webpack 的"翻译官",负责将各种类型的文件转换为 Webpack 能够处理的 JavaScript 模块。
javascript
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
}
};
2. Loader 的执行顺序
2.1 从右到左,从下到上
javascript
// 示例 1:数组形式
{
test: /\.css$/,
use: [
'style-loader', // 第三个执行
'css-loader', // 第二个执行
'sass-loader' // 第一个执行
]
}
// 示例 2:函数形式
{
test: /\.js$/,
use: [
{
loader: 'babel-loader',
options: { presets: ['@babel/preset-env'] }
},
'eslint-loader' // 先执行 eslint-loader,再执行 babel-loader
]
}
2.2 执行流程示例
对于 .scss 文件:
javascript
{
test: /\.scss$/,
use: [
'style-loader', // 3. 将 CSS 插入到 DOM
'css-loader', // 2. 将 CSS 转换为 CommonJS
'sass-loader' // 1. 将 SCSS 编译为 CSS
]
}
执行顺序:sass-loader → css-loader → style-loader
3. Loader 的链式调用
每个 loader 都将前一个 loader 的结果作为输入,形成处理管道:
javascript
// 伪代码表示 loader 执行流程
const source = '/* scss source code */';
// 1. sass-loader 处理
const cssCode = sassLoader(source);
// 2. css-loader 处理
const jsModule = cssLoader(cssCode);
// 3. style-loader 处理
const finalResult = styleLoader(jsModule);
4. Loader 的 pitch 阶段
Loader 实际上有两个执行阶段:
4.1 Normal 阶段(正常阶段)
从右到左执行 loader 函数
4.2 Pitch 阶段(前置阶段)
从左到右执行 loader 的 pitch 方法
javascript
// loader 结构
function loader(source) {
// normal loader:处理文件内容
return transformedSource;
}
loader.pitch = function(remainingRequest, previousRequest, data) {
// pitch loader:在 normal loader 之前执行
// 可以中断 loader 链
};
4.3 Pitch 阶段执行顺序
javascript
// webpack 配置
use: ['loader-a', 'loader-b', 'loader-c']
// 执行顺序:
// pitch-a → pitch-b → pitch-c → 读取资源 → normal-c → normal-b → normal-a
5. Loader 的类型
5.1 前置 loader (pre)
javascript
{
enforce: 'pre',
test: /\.js$/,
loader: 'eslint-loader'
}
5.2 后置 loader (post)
javascript
{
enforce: 'post',
test: /\.js$/,
loader: 'babel-loader'
}
5.3 普通 loader (normal)
javascript
{
test: /\.css$/,
loader: 'css-loader' // 默认就是 normal
}
6. 完整的执行顺序
javascript
module.exports = {
module: {
rules: [
{
enforce: 'pre',
test: /\.js$/,
loader: 'eslint-loader' // 1. 最先执行
},
{
test: /\.js$/,
use: ['babel-loader'] // 3. 然后执行
},
{
enforce: 'post',
test: /\.js$/,
loader: 'uglify-loader' // 4. 最后执行
},
{
test: /\.css$/,
use: [
'style-loader', // 6. 执行
'css-loader', // 5. 执行
'sass-loader' // 2. 执行(与 js loader 并行)
]
}
]
}
};
7. Loader 的开发示例
了解 loader 执行顺序有助于编写自定义 loader:
javascript
// simple-loader.js
module.exports = function(source) {
console.log('Normal loader executing');
return source + '\n// Processed by simple-loader';
};
module.exports.pitch = function(remainingRequest, previousRequest, data) {
console.log('Pitch loader executing');
// 如果返回内容,会跳过后续 loader
// return 'module.exports = "skipped"';
};
总结
- 执行方向:normal loader 从右到左,pitch loader 从左到右
- 优先级:pre → normal → post
- 链式处理:每个 loader 处理前一个 loader 的输出
- 可中断:pitch 阶段可以中断 loader 链
- 单一职责:每个 loader 只完成一个特定任务
理解 loader 的执行顺序对于优化构建流程和调试构建问题非常重要。