一、基础定义与定位
| 概念 | 本质 | 作用阶段 | 核心职责 |
|---|---|---|---|
| loader | 模块转换器(函数) | 编译阶段 | 将源文件转换为可打包的模块 |
| plugin | 事件监听与处理器(对象) | 整个构建周期 | 扩展 webpack 功能,执行特定任务 |
二、核心区别对比
1. 功能维度
-
loader:
- 专注于文件内容的转换,例如:
- 将
.jsx转为 JS(Babel-loader) - 将 Sass 转为 CSS(sass-loader)
- 将图片转为 Base64(url-loader)
- 将
- 单文件处理,输入源文件,输出转换后的代码。
- 专注于文件内容的转换,例如:
-
plugin:
- 监听 webpack 构建过程中的生命周期事件,执行对应操作:
- 打包后生成 HTML 文件(HtmlWebpackPlugin)
- 压缩 JS/CSS(UglifyJSPlugin)
- 热更新(HotModuleReplacementPlugin)
- 全局影响,可操作构建后的资源、修改打包配置等。
- 监听 webpack 构建过程中的生命周期事件,执行对应操作:
2. 用法与形态
-
loader 用法:
// webpack.config.js module.exports = { module: { rules: [ { test: /\.js$/, use: 'babel-loader', // 字符串形式 // 或对象形式配置参数 use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } } ] } }; -
plugin 用法:
const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { plugins: [ new HtmlWebpackPlugin({ template: './index.html' }), // 实例化插件 new webpack.DefinePlugin({ NODE_ENV: JSON.stringify('production') }) ] };
3. 执行时机与流程
-
loader 执行流程:
- 从右到左、从下到上调用(如
style-loader!css-loader!less-loader)。 - 每个 loader 处理前一个 loader 的输出,最终转为 JS 模块。
- 从右到左、从下到上调用(如
-
plugin 事件机制:
- 通过
compiler.hooks监听事件(如beforeCompile、afterEmit)。 - 例如:
HtmlWebpackPlugin在compilation.finish阶段生成 HTML 文件。
- 通过
三、问题
1. 问:为什么 loader 是函数,plugin 是对象?
- 答:
- loader 需直接处理文件内容,函数形态更适合输入输出转换;
- plugin 需注册事件钩子,对象形态可通过
apply方法绑定到 webpack 实例,符合发布-订阅模式。
2. 问:如何实现一个简单的 loader?
- 答:
// 示例:将文本转为大写的 loader module.exports = function(source) { // this.cacheable() // 开启缓存 return source.toUpperCase(); };- 核心:导出函数,接收源文件内容,返回转换后的代码。
3. 问:plugin 如何监听打包事件?
- 答:
// 示例:打包完成后打印日志的 plugin class LogPlugin { apply(compiler) { compiler.hooks.afterEmit.tap('LogPlugin', (compilation) => { console.log('打包完成!文件数量:', Object.keys(compilation.assets).length); }); } }- 关键:实现
apply方法,通过compiler.hooks注册事件处理器。
- 关键:实现
4. 问:生产环境中常见的 plugin 有哪些?
- 答:
- 优化类:
TerserPlugin(JS 压缩)、MiniCssExtractPlugin(CSS 抽离); - 资源类:
HtmlWebpackPlugin(生成 HTML)、ImageMinimizerPlugin(图片压缩); - 环境类:
DefinePlugin(注入环境变量)、ProvidePlugin(自动加载模块)。
- 优化类:
四、实战场景与选型
- 当需要处理文件内容时:用 loader(如
.vue文件 →vue-loader)。 - 当需要控制构建流程时:用 plugin(如打包后自动清理旧文件)。
- 组合使用案例:
// 场景:将 ES6 转为 ES5 并压缩 JS module.exports = { module: { rules: [{ test: /\.js$/, use: 'babel-loader' }] // loader 转换代码 }, plugins: [ new TerserPlugin() // plugin 压缩代码 ] };