Writing a Webpack Plugin

Plugins expose the full potential of the webpack engine to third-party developers. Using staged build callbacks, developers can introduce their own behaviors into the webpack build process. Building plugins is a bit more advanced than building loaders, because you'll need to understand some of the webpack low-level internals to hook into them. Be prepared to read some source code!

Creating a Webpack Plugin

A plugin for webpack consists of:

  • A named JavaScript function or a JavaScript class.
  • Defines apply method in its prototype.
  • Specifies an event hook to tap into.
  • Manipulates webpack internal instance specific data.
  • Invokes webpack provided callback after functionality is complete.
// A JavaScript class.
class MyExampleWebpackPlugin {
  // Define `apply` as its prototype method which is supplied with compiler as its argument
  apply(compiler) {
    // Specify the event hook to attach to
    compiler.hooks.emit.tapAsync(
      'MyExampleWebpackPlugin',
      (compilation, callback) => {
        console.log('This is an example plugin!');
        console.log(
          'Here’s the `compilation` object which represents a single build of assets:',
          compilation
        );

        // Manipulate the build using the plugin API provided by webpack
        compilation.addModule(/* ... */);

        callback();
      }
    );
  }
}

Basic plugin architecture

Plugins are instantiated objects with an apply method on their prototype. This apply method is called once by the webpack compiler while installing the plugin. The apply method is given a reference to the underlying webpack compiler, which grants access to compiler callbacks. A plugin is structured as follows:

class HelloWorldPlugin {
  apply(compiler) {
    compiler.hooks.done.tap(
      'Hello World Plugin',
      (
        stats /* stats is passed as an argument when done hook is tapped.  */
      ) => {
        console.log('Hello World!');
      }
    );
  }
}

module.exports = HelloWorldPlugin;

Then to use the plugin, include an instance in your webpack configuration plugins array:

// webpack.config.js
var HelloWorldPlugin = require('hello-world');

module.exports = {
  // ... configuration settings here ...
  plugins: [new HelloWorldPlugin({ options: true })],
};

Use schema-utils in order to validate the options being passed through the plugin options. Here is an example:

import { validate } from 'schema-utils';

// schema for options object
const schema = {
  type: 'object',
  properties: {
    test: {
      type: 'string',
    },
  },
};

export default class HelloWorldPlugin {
  constructor(options = {}) {
    validate(schema, options, {
      name: 'Hello World Plugin',
      baseDataPath: 'options',
    });
  }

  apply(compiler) {}
}

Compiler and Compilation

Among the two most important resources while developing plugins are the compiler and compilation objects. Understanding their roles is an important first step in extending the webpack engine.

class HelloCompilationPlugin {
  apply(compiler) {
    // Tap into compilation hook which gives compilation as argument to the callback function
    compiler.hooks.compilation.tap('HelloCompilationPlugin', (compilation) => {
      // Now we can tap into various hooks available through compilation
      compilation.hooks.optimize.tap('HelloCompilationPlugin', () => {
        console.log('Assets are being optimized.');
      });
    });
  }
}

module.exports = HelloCompilationPlugin;

The list of hooks available on the compiler, compilation, and other important objects, see the plugins API docs.

Async event hooks

Some plugin hooks are asynchronous. To tap into them, we can use tap method which will behave in synchronous manner or use one of tapAsync method or tapPromise method which are asynchronous methods.

tapAsync

When we use tapAsync method to tap into plugins, we need to call the callback function which is supplied as the last argument to our function.

class HelloAsyncPlugin {
  apply(compiler) {
    compiler.hooks.emit.tapAsync(
      'HelloAsyncPlugin',
      (compilation, callback) => {
        // Do something async...
        setTimeout(function () {
          console.log('Done with async work...');
          callback();
        }, 1000);
      }
    );
  }
}

module.exports = HelloAsyncPlugin;

tapPromise

When we use tapPromise method to tap into plugins, we need to return a promise which resolves when our asynchronous task is completed.

class HelloAsyncPlugin {
  apply(compiler) {
    compiler.hooks.emit.tapPromise('HelloAsyncPlugin', (compilation) => {
      // return a Promise that resolves when we are done...
      return new Promise((resolve, reject) => {
        setTimeout(function () {
          console.log('Done with async work...');
          resolve();
        }, 1000);
      });
    });
  }
}

module.exports = HelloAsyncPlugin;

Resources

https://webpack.js.org/contribute/writing-a-plugin/

https://webpack.js.org/api/plugins/

https://webpack.js.org/concepts/plugins/
 

#webpack #javascript 

Writing a Custom Webpack Plugin
1 Likes55.95 GEEK