Movatterモバイル変換


[0]ホーム

URL:


webpack logo
ag grid
ag charts

Writing a 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 Plugin

A plugin for webpack consists of:

  • A named JavaScript function or a JavaScript class.
  • Definesapply method in its prototype.
  • Specifies anevent hook to tap into.
  • Manipulates webpack internal instance specific data.
  • Invokes webpack provided callback after functionality is complete.
// A JavaScript class.classMyExampleWebpackPlugin{// Define `apply` as its prototype method which is supplied with compiler as its argumentapply(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 anapply method on their prototype. Thisapply method is called once by the webpack compiler while installing the plugin. Theapply method is given a reference to the underlying webpack compiler, which grants access to compiler callbacks. A plugin is structured as follows:

classHelloWorldPlugin{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 configurationplugins array:

// webpack.config.jsvar HelloWorldPlugin=require('hello-world');module.exports={// ... configuration settings here ...  plugins:[newHelloWorldPlugin({ options:true})],};

Useschema-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 objectconst schema={  type:'object',  properties:{    test:{      type:'string',},},};exportdefaultclassHelloWorldPlugin{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 thecompiler andcompilation objects. Understanding their roles is an important first step in extending the webpack engine.

classHelloCompilationPlugin{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;

For the list of hooks available oncompiler,compilation, and other important objects, see theplugins API docs.

Async event hooks

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

tapAsync

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

classHelloAsyncPlugin{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 usetapPromise method to tap into plugins, we need to return a promise which resolves when our asynchronous task is completed.

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

Example

Once we can latch onto the webpack compiler and each individual compilations, the possibilities become endless for what we can do with the engine itself. We can reformat existing files, create derivative files, or fabricate entirely new assets.

Let's write an example plugin that generates a new build file calledassets.md, the contents of which will list all of the asset files in our build. This plugin might look something like this:

classFileListPlugin{static defaultOptions={    outputFile:'assets.md',};// Any options should be passed in the constructor of your plugin,// (this is a public API of your plugin).constructor(options={}){// Applying user-specified options over the default options// and making merged options further available to the plugin methods.// You should probably validate all the options here as well.this.options={...FileListPlugin.defaultOptions,...options};}apply(compiler){const pluginName= FileListPlugin.name;// webpack module instance can be accessed from the compiler object,// this ensures that correct version of the module is used// (do not require/import the webpack or any symbols from it directly).const{ webpack}= compiler;// Compilation object gives us reference to some useful constants.const{ Compilation}= webpack;// RawSource is one of the "sources" classes that should be used// to represent asset sources in compilation.const{ RawSource}= webpack.sources;// Tapping to the "thisCompilation" hook in order to further tap// to the compilation process on an earlier stage.    compiler.hooks.thisCompilation.tap(pluginName,(compilation)=>{// Tapping to the assets processing pipeline on a specific stage.      compilation.hooks.processAssets.tap({          name: pluginName,// Using one of the later asset processing stages to ensure// that all assets were already added to the compilation by other plugins.          stage: Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE,},(assets)=>{// "assets" is an object that contains all assets// in the compilation, the keys of the object are pathnames of the assets// and the values are file sources.// Iterating over all the assets and// generating content for our Markdown file.const content='# In this build:\n\n'+            Object.keys(assets).map((filename)=>`-${filename}`).join('\n');// Adding new asset to the compilation, so it would be automatically// generated by the webpack in the output directory.          compilation.emitAsset(this.options.outputFile,newRawSource(content));});});}}module.exports={ FileListPlugin};

webpack.config.js

const{ FileListPlugin}=require('./file-list-plugin.js');// Use the plugin in your webpack configuration:module.exports={// …  plugins:[// Adding the plugin with the default optionsnewFileListPlugin(),// OR:// You can choose to pass any supported options to it:newFileListPlugin({      outputFile:'my-assets.md',}),],};

This will generate a markdown file with chosen name that looks like this:

# In this build:- main.css- main.js- index.html
tip

We are using synchronoustap() method to tap into theprocessAssets hook because we don't need to perform any asynchronous operations in the example above. However, theprocessAssets hook is an asynchronous one, so you can also usetapPromise() ortapAsync() if you actually need to.

tip

TheprocessAssets hook also supports theadditionalAssets property, that allows your plugin to intercept not only assets that were added by other plugins prior to the execution of the specified stage, but also for assets that were added on a later stages. This allows to intercept absolutely all the assets which are part of the compilation. However, in our example we are fine with using theSUMMARIZE stage to capture all the assets generated on previous stages (this should account for all assets in general case).

Different Plugin Shapes

A plugin can be classified into types based on the event hooks it taps into. Every event hook is pre-defined as synchronous or asynchronous or waterfall or parallel hook and hook is called internally using call/callAsync method. The list of hooks that are supported or can be tapped into is generally specified inthis.hooks property.

For example:

this.hooks={  shouldEmit:newSyncBailHook(['compilation']),};

It represents that the only hook supported isshouldEmit which is a hook ofSyncBailHook type and the only parameter which will be passed to any plugin that taps intoshouldEmit hook iscompilation.

Various types of hooks supported are :

Synchronous Hooks

  • SyncHook

    • Defined asnew SyncHook([params])
    • Tapped into usingtap method.
    • Called usingcall(...params) method.
  • Bail Hooks

    • Defined usingSyncBailHook[params]
    • Tapped into usingtap method.
    • Called usingcall(...params) method.

    In these types of hooks, each of the plugin callbacks will be invoked one after the other with the specificargs. If any value is returned except undefined by any plugin, then that value is returned by hook and no further plugin callback is invoked. Many useful events likeoptimizeChunks,optimizeChunkModules are SyncBailHooks.

  • Waterfall Hooks

    • Defined usingSyncWaterfallHook[params]
    • Tapped into usingtap method.
    • Called usingcall(...params) method

    Here each of the plugins is called one after the other with the arguments from the return value of the previous plugin. The plugin must take the order of its execution into account.It must accept arguments from the previous plugin that was executed. The value for the first plugin isinit. Hence at least 1 param must be supplied for waterfall hooks. This pattern is used in the Tapable instances which are related to the webpack templates likeModuleTemplate,ChunkTemplate etc.

Asynchronous Hooks

  • Async Series Hook

    • Defined usingAsyncSeriesHook[params]
    • Tapped into usingtap/tapAsync/tapPromise method.
    • Called usingcallAsync(...params) method

    The plugin handler functions are called with all arguments and a callback function with the signature(err?: Error) -> void. The handler functions are called in order of registration.callback is called after all the handlers are called.This is also a commonly used pattern for events likeemit,run.

  • Async waterfall The plugins will be applied asynchronously in the waterfall manner.

    • Defined usingAsyncWaterfallHook[params]
    • Tapped into usingtap/tapAsync/tapPromise method.
    • Called usingcallAsync(...params) method

    The plugin handler functions are called with the current value and a callback function with the signature(err: Error, nextValue: any) -> void. When callednextValue is the current value for the next handler. The current value for the first handler isinit. After all handlers are applied, callback is called with the last value. If any handler passes a value forerr, the callback is called with this error and no more handlers are called.This plugin pattern is expected for events likebefore-resolve andafter-resolve.

  • Async Series Bail

    • Defined usingAsyncSeriesBailHook[params]
    • Tapped into usingtap/tapAsync/tapPromise method.
    • Called usingcallAsync(...params) method
  • Async Parallel

    • Defined usingAsyncParallelHook[params]
    • Tapped into usingtap/tapAsync/tapPromise method.
    • Called usingcallAsync(...params) method

Configuration defaults

Webpack applies configuration defaults after plugins defaults are applied. This allows plugins to feature their own defaults and provides a way to create configuration preset plugins.

10 Contributors

slavafomintbroadleynveenjainiamakulovbyzykfranjohn21EugeneHlushkosnitin315rahul3vjamesgeorge007

[8]ページ先頭

©2009-2025 Movatter.jp