How to Write a Web Pack Plug-in

Original: how to write a plugin

Translator: neal1991

welcome to star my articles-translator , providing you advanced articles translation. Any suggestion, please issue or contact me

LICENSE: MIT

Plug-ins can expose the full potential of webpack engines to third-party developers. By using phase build callbacks, developers can introduce their own behavior into the construction process of webpack. Building plug-ins is more advanced than building loader s, because you need to understand some of the low-level internal hooks of webpack. Ready to read some source code!

Compiler and Compilation

The two most important resources in plug-in development are compiler and compilation objects. Understanding their roles is an important first step in expanding the web pack engine.

  • The compiler object represents a fully configured web pack environment. Once the webpack is turned on, the object is built and configured using all the operation settings, including options, loaders, and plugins. When a plug-in is applied to a web pack environment, the plug-in will get a reference to the compiler. Using this compiler, you can access the main web pack environment.

  • A compilation object represents a single build of versioned assets. While running Webpack development middleware, a new compilation will be created each time a file change is detected, thus generating a new set of compiled assets. A compilation surfaces information about the present state of module resources, compiled assets, changed files, and watched dependencies. The compilation also provides many callback points at which a plugin may choose to perform custom actions.

  • A compilation object represents a build of version resources. When running webpack development middleware, a new compilation is generated every time a file change is detected, thus generating a series of compiled resources. Compilation represents information about module resources, compiled resources, changed files, and the current state of monitoring dependencies. The compilation also provides a number of callbacks that plug-ins can choose to perform custom actions.

These two components are an internal part of any web pack plug-in (especially compilation), so developers will benefit greatly from familiarizing themselves with these source code files:

Basic plug-in architecture

Plug-ins are instance objects, and on their prototype, there will be an application method. When the plug-in is installed, the application method is called by webpack compiler. This apply gives a reference to the potential webpack compiler, guaranteeing access to the compiler callback. A simple plug-in structure is as follows:

function HelloWorldPlugin(options) {
  // Setup the plugin instance with options...
}

HelloWorldPlugin.prototype.apply = function(compiler) {
  compiler.plugin('done', function() {
    console.log('Hello World!'); 
  });
};

module.exports = HelloWorldPlugin;

The next step is to install the plug-in, as long as you add an instance to your web pack configuration plugins array:

var HelloWorldPlugin = require('hello-world');

var webpackConfig = {
  // ... config settings here ...
  plugins: [
    new HelloWorldPlugin({options: true})
  ]
};

Visit compilation

Using the compiler object, you may bind to provide that callback for each new compilation reference. These compilations provide callbacks to hooks for many steps in the build process.

function HelloCompilationPlugin(options) {}

HelloCompilationPlugin.prototype.apply = function(compiler) {

  // Setup callback for accessing a compilation:
  compiler.plugin("compilation", function(compilation) {
    
    // Now setup callbacks for accessing compilation steps:
    compilation.plugin("optimize", function() {
      console.log("Assets are being optimized.");
    });
  });
};

module.exports = HelloCompilationPlugin;

For more information on compiler s, callbacks on compilation, and other important objects, see the [[plugins API|plugins]] documentation.

Asynchronous compilation plugins

Some component plug-in steps are asynchronous, and when your plug-in is running, pass a callback function that must be called.

function HelloAsyncPlugin(options) {}

HelloAsyncPlugin.prototype.apply = function(compiler) {
  compiler.plugin("emit", function(compilation, callback) {

    // Do something async...
    setTimeout(function() {
      console.log("Done with async work...");
      callback();
    }, 1000);

  });
};

module.exports = HelloAsyncPlugin;

A simple example

Once we can lock in webpack compiler and each individual compilation, we can use the engine itself to achieve unlimited potential. We can reformat existing files, create derivative files, or create entirely new resources.

Let's write a simple plug-in example that generates a new package file filelist.md; the content of this file lists all the resource files that exist in our build. This plug-in may look like this:

function FileListPlugin(options) {}

FileListPlugin.prototype.apply = function(compiler) {
  compiler.plugin('emit', function(compilation, callback) {
    // Create a header string for the generated file:
    var filelist = 'In this build:\n\n';

    // Loop through all compiled assets,
    // adding a new line item for each filename.
    for (var filename in compilation.assets) {
      filelist += ('- '+ filename +'\n');
    }
    
    // Insert this list into the Webpack build as a new file asset:
    compilation.assets['filelist.md'] = {
      source: function() {
        return filelist;
      },
      size: function() {
        return filelist.length;
      }
    };

    callback();
  });
};

module.exports = FileListPlugin;

Useful plug-in patterns

Plug-ins allow for as little customization as possible within the web pack build system. This allows you to create custom resource types, perform special build adjustments, or set up a way to further improve the web pack runtime when using middleware. The following features of webpack become useful in developing plug-ins.

Explore assets, chunks, modules, and dependencies

After the compilation is completed, all structures in the compilation can be traversed.

function MyPlugin() {}

MyPlugin.prototype.apply = function(compiler) {
  compiler.plugin('emit', function(compilation, callback) {
    
    // Explore each chunk (build output):
    compilation.chunks.forEach(function(chunk) {
      // Explore each module within the chunk (built inputs):
      chunk.modules.forEach(function(module) {
        // Explore each source file path that was included into the module:
        module.fileDependencies.forEach(function(filepath) {
          // we've learned a lot about the source structure now...
        });
      });

      // Explore each asset filename generated by the chunk:
      chunk.files.forEach(function(filename) {
        // Get the asset source for each file generated by the chunk:
        var source = compilation.assets[filename].source();
      });
    });

    callback();
  });
};

module.exports = MyPlugin;
  • Compoilation. modules: An array of modules (build inputs) in compilation. Each module manages the construction of source files from the source code repository.

  • module.fileDependencies: An array of source file paths contained in the module. This includes the source JavaScript file itself (for example, index.js) and all dependency resource files (stylesheets, images, etc.) required. Viewing dependencies is useful to see which source files belong to modules.

  • Compoilation. chunks: An array of chunks in Compilation (build output). Each chunk manages the combination of the final rendering resources.

  • chunk.modules: An array of modules contained in a chunk. By extending, you can view the dependencies of each module to see the original source files passed into the chunk

  • chunk.files: An array of output file names generated by chunk. You can access these resources from the compilation.assets table.

Detection observation chart

When running webpack middleware, each component contains an array of fileDependencies (files being monitored) and a file Timestamps hash that maps the path of the observed file to a timestamp. These are very useful for detecting which files in the compilation have changed:

function MyPlugin() {
  this.startTime = Date.now();
  this.prevTimestamps = {};
}

MyPlugin.prototype.apply = function(compiler) {
  compiler.plugin('emit', function(compilation, callback) {
    
    var changedFiles = Object.keys(compilation.fileTimestamps).filter(function(watchfile) {
      return (this.prevTimestamps[watchfile] || this.startTime) < (compilation.fileTimestamps[watchfile] || Infinity);
    }.bind(this));
    
    this.prevTimestamps = compilation.fileTimestamps;
    callback();
  }.bind(this));
};

module.exports = MyPlugin;

You can also pass a new file path into the observation chart to receive the compilation trigger when these files change. Simply push the valid file path to the compilation.fileDependencies array to add it to the observation list. Note: Reconstruct the fileDependencies array in each compilation, so your plug-in must push its observed dependencies to each compilation to keep them under surveillance.

Changing chunks

Similar to observation diagrams, you can monitor changed blocks (or modules) in compilation by tracking their hash values.

function MyPlugin() {
  this.chunkVersions = {};
}

MyPlugin.prototype.apply = function(compiler) {
  compiler.plugin('emit', function(compilation, callback) {
    
    var changedChunks = compilation.chunks.filter(function(chunk) {
      var oldVersion = this.chunkVersions[chunk.name];
      this.chunkVersions[chunk.name] = chunk.hash;
      return chunk.hash !== oldVersion;
    }.bind(this));
    
    callback();
  }.bind(this));
};

module.exports = MyPlugin;

Keywords: Javascript Webpack

Added by blueway on Mon, 01 Jul 2019 04:23:50 +0300