Webpack 的插件(plugin)和 loader 的执行顺序有所不同。插件的执行顺序是按照它们在配置中的声明顺序执行的,而不是倒序执行。
在 Webpack 配置文件中,插件是按照它们在 plugins
数组中的顺序依次执行的。也就是说,先声明的插件会先执行,后声明的插件会后执行
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const MyCustomPlugin = require('./my-custom-plugin');
module.exports = {
plugins: [
new CleanWebpackPlugin(), // 这个插件会首先执行
new HtmlWebpackPlugin({ // 这个插件会第二个执行
template: './src/index.html'
}),
new MyCustomPlugin() // 这个插件会最后执行
]
};
插件的执行顺序是按照声明顺序执行的,因为插件通常会依赖于 Webpack 的生命周期钩子(hooks)。这些钩子在 Webpack 的编译过程中按特定顺序触发,因此插件需要按照声明顺序依次注册和执行,以确保它们在正确的时机进行相应的操作。
Webpack 插件通过钩子机制与 Webpack 的编译过程进行交互。常见的钩子包括:
compile
:编译开始时触发。compilation
:每次新的编译创建时触发。emit
:生成资源到输出目录之前触发。done
:编译完成时触发。插件可以在这些钩子上注册回调函数,以执行特定的任务。
以下是一个简单的自定义插件示例,展示如何在 Webpack 中创建和使用插件:
class MyCustomPlugin {
apply(compiler) {
// 使用 compiler.hooks.done.tap 注册一个钩子
compiler.hooks.done.tap('MyCustomPlugin', (stats) => {
console.log('编译完成!');
});
}
}
module.exports = MyCustomPlugin;
在 Webpack 配置文件中使用这个插件:
const MyCustomPlugin = require('./my-custom-plugin');
module.exports = {
plugins: [
new MyCustomPlugin()
]
};
命名插件:
使用正确的钩子:
emit
钩子在生成资源到输出目录之前触发,done
钩子在编译完成时触发。异步操作:
tapAsync
或 tapPromise
)并确保在操作完成后调用回调函数或返回 Promise。处理错误:
访问编译资源:
compilation
对象可以访问和修改编译资源。确保你对资源的修改是安全和必要的。以下是一个异步插件示例,它在生成资源到输出目录之前执行异步操作:
class AsyncPlugin {
apply(compiler) {
compiler.hooks.emit.tapAsync('AsyncPlugin', (compilation, callback) => {
setTimeout(() => {
console.log('异步操作完成');
callback();
}, 1000);
});
}
}
module.exports = AsyncPlugin;
在 Webpack 配置文件中使用这个异步插件:
const AsyncPlugin = require('./path/to/async-plugin');
module.exports = {
plugins: [
new AsyncPlugin()
]
};
apply
方法。apply
方法:通过 compiler
对象访问 Webpack 的编译生命周期钩子。apply
方法中注册钩子,以便在编译过程的特定阶段执行自定义逻辑。这个插件将在编译过程的不同阶段输出日志信息,并在生成资源之前添加一个自定义文件。
首先,创建一个插件类 ComprehensivePlugin
:
class ComprehensivePlugin {
apply(compiler) {
// 1. compile 钩子:编译开始时触发
compiler.hooks.compile.tap('ComprehensivePlugin', (params) => {
console.log('编译开始...');
});
// 2. compilation 钩子:每次新的编译创建时触发
compiler.hooks.compilation.tap('ComprehensivePlugin', (compilation) => {
console.log('新的编译创建...');
// 3. optimize 钩子:优化阶段开始时触发
compilation.hooks.optimize.tap('ComprehensivePlugin', () => {
console.log('优化阶段开始...');
});
// 4. emit 钩子:生成资源到输出目录之前触发(异步)
compilation.hooks.emit.tapAsync('ComprehensivePlugin', (compilation, callback) => {
console.log('生成资源到输出目录之前...');
// 添加一个自定义文件
const content = '这是一个由 ComprehensivePlugin 生成的文件。';
compilation.assets['custom-file.txt'] = {
source: () => content,
size: () => content.length
};
// 模拟异步操作
setTimeout(() => {
console.log('异步操作完成');
callback();
}, 1000);
});
});
// 5. afterEmit 钩子:生成资源到输出目录之后触发(异步)
compiler.hooks.afterEmit.tapAsync('ComprehensivePlugin', (compilation, callback) => {
console.log('生成资源到输出目录之后...');
callback();
});
// 6. done 钩子:编译完成时触发
compiler.hooks.done.tap('ComprehensivePlugin', (stats) => {
console.log('编译完成!');
});
// 7. failed 钩子:编译失败时触发
compiler.hooks.failed.tap('ComprehensivePlugin', (error) => {
console.error('编译失败:', error);
});
}
}
module.exports = ComprehensivePlugin;
在 Webpack 配置文件中使用这个插件:
const path = require('path');
const ComprehensivePlugin = require('./path/to/ComprehensivePlugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new ComprehensivePlugin()
]
};
创建一个简单的入口文件 src/index.js
:
console.log('Hello, Webpack!');
npx webpack
你应该会在控制台看到插件输出的日志信息,并在 dist
目录下看到一个名为 custom-file.txt
的文件,内容为 这是一个由 ComprehensivePlugin 生成的文件。
这个示例插件展示了以下知识点:
tapAsync
钩子处理异步操作,并在操作完成后调用回调函数。compilation
对象访问和修改编译资源。Webpack 插件通过钩子机制与 Webpack 的编译过程进行交互。Webpack 使用 Tapable 库来实现这些钩子。以下是一些常见的 Webpack 插件钩子及其解释:
compile
、compilation
、emit
、afterEmit
、done
、failed
。optimize
、optimizeAssets
、processAssets
、afterOptimizeAssets
。tap
注册同步钩子,使用 tapAsync
或 tapPromise
注册异步钩子在 Webpack 中,compiler
和 compilation
是两个不同的概念,它们分别代表了不同的编译阶段和作用范围。理解它们的区别对于编写插件和扩展 Webpack 功能非常重要。
Compiler:
compiler
对象代表了 Webpack 的整个编译器实例,管理整个编译过程。compiler
的生命周期从 Webpack 启动到编译结束,贯穿整个编译过程。compiler
钩子用于管理整个编译过程的全局事件,例如编译开始、编译完成等。Compilation:
compilation
对象代表了一次具体的编译过程,包含了当前模块资源、编译生成的资源、变化的文件等信息。compilation
对象。对于初始编译和每次增量编译,都会创建新的 compilation
对象。compilation
钩子用于管理具体的编译过程,例如优化、生成资源等。
compiler.hooks.compilation
和 compilation
钩子的区别**compiler.hooks.compilation
**:
compilation
对象创建时触发。compilation
钩子,以便在具体的编译过程中执行自定义逻辑。compiler.hooks.compilation.tap('MyPlugin', (compilation) => {
console.log('新的编译创建...');
// 在这里可以注册 compilation 钩子
compilation.hooks.optimize.tap('MyPlugin', () => {
console.log('优化阶段开始...');
});
});
compilation
钩子:
compilation.hooks.optimize.tap('MyPlugin', () => {
console.log('优化阶段开始...');
});
compiler
和 compilation
钩子
class MyPlugin {
apply(compiler) {
// 在新的 compilation 对象创建时触发
compiler.hooks.compilation.tap('MyPlugin', (compilation) => {
console.log('新的编译创建...');
// 在具体的编译过程中触发
compilation.hooks.optimize.tap('MyPlugin', () => {
console.log('优化阶段开始...');
});
compilation.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {
console.log('生成资源到输出目录之前...');
callback();
});
});
// 编译完成时触发
compiler.hooks.done.tap('MyPlugin', (stats) => {
console.log('编译完成!');
});
}
}
module.exports = MyPlugin;
compiler
对象:代表 Webpack 的整个编译器实例,管理整个编译过程。compiler
钩子用于管理全局编译事件。compilation
对象:代表一次具体的编译过程,包含当前模块资源、编译生成的资源等信息。compilation
钩子用于管理具体的编译过程。compiler.hooks.compilation
**:在新的 compilation
对象创建时触发,允许插件注册 compilation
钩子。compilation
钩子:在具体的编译过程中触发,允许插件在编译过程的不同阶段执行自定义逻辑compiler.hooks.compile.tap('MyPlugin', (params) => {
console.log('编译开始...');
});
compiler.hooks.compilation.tap('MyPlugin', (compilation) => {
console.log('新的编译创建...');
});
compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {
console.log('生成资源到输出目录之前...');
callback();
});
compiler.hooks.afterEmit.tapAsync('MyPlugin', (compilation, callback) => {
console.log('生成资源到输出目录之后...');
callback();
});
compiler.hooks.done.tap('MyPlugin', (stats) => {
console.log('编译完成!');
});
时机:编译失败时触发
compiler.hooks.failed.tap('MyPlugin', (error) => {
console.error('编译失败:', error);
});
compilation
对象代表了一次具体的编译过程,包含了当前模块资源、编译生成的资源、变化的文件等信息。以下是一些常见的 compilation
钩子:
compilation.hooks.optimize.tap('MyPlugin', () => {
console.log('优化阶段开始...');
});
compilation.hooks.optimizeAssets.tapAsync('MyPlugin', (assets, callback) => {
console.log('优化生成的资源...');
callback();
});
compilation.hooks.processAssets.tapAsync('MyPlugin', (assets, callback) => {
console.log('处理生成的资源...');
callback();
});
compilation.hooks.afterOptimizeAssets.tap('MyPlugin', (assets) => {
console.log('优化生成的资源之后...');
});
同步钩子:使用 tap
方法注册回调函数。
compiler.hooks.compile.tap('MyPlugin', (params) => {
console.log('同步钩子:编译开始...');
});
异步钩子:使用 tapAsync
或 tapPromise
方法注册回调函数。
compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {
console.log('异步钩子:生成资源到输出目录之前...');
callback();
});
compiler.hooks.emit.tapPromise('MyPlugin', (compilation) => {
return new Promise((resolve) => {
console.log('异步钩子:生成资源到输出目录之前...');
resolve();
});
});