webpack plugin

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()
  ]
};

总结

  • Loader:按相反顺序执行,从后往前。
  • Plugin:按声明顺序执行,从前往后。

注意事项

  1. 命名插件

    • 在注册钩子时,给插件一个唯一的名称,以便在调试和日志中更容易识别。
  2. 使用正确的钩子

    • Webpack 提供了许多钩子,覆盖了编译过程的各个阶段。选择合适的钩子来实现你的功能。例如,emit 钩子在生成资源到输出目录之前触发,done 钩子在编译完成时触发。
  3. 异步操作

    • 如果插件需要执行异步操作,可以使用异步钩子(如 tapAsync 或 tapPromise)并确保在操作完成后调用回调函数或返回 Promise。
  4. 处理错误

    • 在插件中处理可能的错误,并在适当的时候调用 Webpack 的错误处理机制,以确保编译过程不会因为插件的错误而中断。
  5. 访问编译资源

    • 通过 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 方法中注册钩子,以便在编译过程的特定阶段执行自定义逻辑。
  • 注意事项:命名插件、使用正确的钩子、处理异步操作、处理错误、访问编译资源。

编写一个包含所有主要钩子和知识点的 Webpack 插件示例 

示例插件:ComprehensivePlugin

这个插件将在编译过程的不同阶段输出日志信息,并在生成资源之前添加一个自定义文件。

1. 创建插件类

首先,创建一个插件类 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;
2. 使用插件

在 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()

  ]

};
3. 测试插件

创建一个简单的入口文件 src/index.js

console.log('Hello, Webpack!');
npx webpack

你应该会在控制台看到插件输出的日志信息,并在 dist 目录下看到一个名为 custom-file.txt 的文件,内容为 这是一个由 ComprehensivePlugin 生成的文件。

总结

这个示例插件展示了以下知识点:

  1. 使用不同的钩子:展示了如何在编译过程的不同阶段使用 Webpack 的钩子。
  2. 处理异步操作:使用 tapAsync 钩子处理异步操作,并在操作完成后调用回调函数。
  3. 错误处理:展示了如何在编译失败时处理错误。
  4. 访问和修改编译资源:展示了如何通过 compilation 对象访问和修改编译资源。

Webpack 插件通过钩子机制与 Webpack 的编译过程进行交互。Webpack 使用 Tapable 库来实现这些钩子。以下是一些常见的 Webpack 插件钩子及其解释:

  • Compiler 钩子:管理整个编译过程,常见钩子包括 compilecompilationemitafterEmitdonefailed
  • Compilation 钩子:管理具体的编译过程,常见钩子包括 optimizeoptimizeAssetsprocessAssetsafterOptimizeAssets
  • 同步和异步钩子:使用 tap 注册同步钩子,使用 tapAsync 或 tapPromise 注册异步钩子

在 Webpack 中,compiler 和 compilation 是两个不同的概念,它们分别代表了不同的编译阶段和作用范围。理解它们的区别对于编写插件和扩展 Webpack 功能非常重要。

Compiler 和 Compilation 的区别

  1. Compiler

    • 作用范围compiler 对象代表了 Webpack 的整个编译器实例,管理整个编译过程。
    • 生命周期compiler 的生命周期从 Webpack 启动到编译结束,贯穿整个编译过程。
    • 钩子compiler 钩子用于管理整个编译过程的全局事件,例如编译开始、编译完成等。
  2. 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 钩子:

  • 时机:优化阶段开始时触发。
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('优化生成的资源之后...');
});

 

Async 和 Sync 钩子

  • 同步钩子:使用 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();
  });
});

 

 

 

 

你可能感兴趣的:(webpack,webpack,前端,node.js)