From 6d1c7587603ce80a0736b054d2a3a79609abaae3 Mon Sep 17 00:00:00 2001 From: Naveen Jain Date: Sun, 27 May 2018 21:04:39 +0530 Subject: [PATCH 1/5] Update Writing plugin and Plugin pattern page --- src/content/contribute/plugin-patterns.md | 118 +++++----- src/content/contribute/writing-a-plugin.md | 247 +++++++++++++-------- 2 files changed, 210 insertions(+), 155 deletions(-) diff --git a/src/content/contribute/plugin-patterns.md b/src/content/contribute/plugin-patterns.md index 89c9e415416a..50f04712a88c 100644 --- a/src/content/contribute/plugin-patterns.md +++ b/src/content/contribute/plugin-patterns.md @@ -1,6 +1,8 @@ --- title: Plugin Patterns sort: 5 +contributors: + - nveenjain --- Plugins grant unlimited opportunity to perform customizations within the webpack build system. This allows you to create custom asset types, perform unique build modifications, or even enhance the webpack runtime while using middleware. The following are some features of webpack that become useful while writing plugins. @@ -10,63 +12,66 @@ Plugins grant unlimited opportunity to perform customizations within the webpack After a compilation is sealed, all structures within the compilation may be traversed. ```javascript -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... +class MyPlugin { + apply(compiler) { + compiler.hooks.emit.tapAsync("MyPlugin", (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(); + // 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(); - }); -}; + callback(); + }); + } +} module.exports = MyPlugin; ``` -- `compilation.modules`: An array of modules (built inputs) in the compilation. Each module manages the build of a raw file from your source library. -- `module.fileDependencies`: An array of source file paths included into a module. This includes the source JavaScript file itself (ex: `index.js`), and all dependency asset files (stylesheets, images, etc) that it has required. Reviewing dependencies is useful for seeing what source files belong to a module. -- `compilation.chunks`: An array of chunks (build outputs) in the compilation. Each chunk manages the composition of a final rendered assets. -- `chunk.modules`: An array of modules that are included into a chunk. By extension, you may look through each module's dependencies to see what raw source files fed into a chunk. -- `chunk.files`: An array of output filenames generated by the chunk. You may access these asset sources from the `compilation.assets` table. +* `compilation.modules`: An array of modules (built inputs) in the compilation. Each module manages the build of a raw file from your source library. +* `module.fileDependencies`: An array of source file paths included into a module. This includes the source JavaScript file itself (ex: `index.js`), and all dependency asset files (stylesheets, images, etc) that it has required. Reviewing dependencies is useful for seeing what source files belong to a module. +* `compilation.chunks`: An array of chunks (build outputs) in the compilation. Each chunk manages the composition of a final rendered assets. +* `chunk.modules`: An array of modules that are included into a chunk. By extension, you may look through each module's dependencies to see what raw source files fed into a chunk. +* `chunk.files`: An array of output filenames generated by the chunk. You may access these asset sources from the `compilation.assets` table. ### Monitoring the watch graph While running webpack middleware, each compilation includes a `fileDependencies` array (what files are being watched) and a `fileTimestamps` hash that maps watched file paths to a timestamp. These are extremely useful for detecting what files have changed within the compilation: ```javascript -function MyPlugin() { - this.startTime = Date.now(); - this.prevTimestamps = {}; +class MyPlugin { + constructor() { + this.startTime = Date.now(); + this.prevTimestamps = {}; + } + apply(compiler) { + compiler.hooks.emit.tapAsync("MyPlugin", (compilation, callback) => { + var changedFiles = Object.keys(compilation.fileTimestamps).filter( + watchfile => { + return ( + (this.prevTimestamps[watchfile] || this.startTime) < + (compilation.fileTimestamps[watchfile] || Infinity) + ); + } + ); + + this.prevTimestamps = compilation.fileTimestamps; + callback(); + }); + } } -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; ``` @@ -77,22 +82,21 @@ You may also feed new file paths into the watch graph to receive compilation tri Similar to the watch graph, it's fairly simple to monitor changed chunks (or modules, for that matter) within a compilation by tracking their hashes. ```javascript -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)); - +class MyPlugin{ + constructor(){ + this.chunkVersions = {}; + } + apply(compiler){ + compiler.hooks.emit.tapAsync((compilation, callback)=>{ + var changedChunks = compilation.chunks.filter((chunk) => { + var oldVersion = this.chunkVersions[chunk.name]; + this.chunkVersions[chunk.name] = chunk.hash; + return chunk.hash !== oldVersion; + } + }); callback(); - }.bind(this)); -}; + } +} module.exports = MyPlugin; ``` diff --git a/src/content/contribute/writing-a-plugin.md b/src/content/contribute/writing-a-plugin.md index 91f8034f59ff..542708cdbef6 100644 --- a/src/content/contribute/writing-a-plugin.md +++ b/src/content/contribute/writing-a-plugin.md @@ -3,6 +3,7 @@ title: Writing a Plugin sort: 4 contributors: - tbroadley + - nveenjain --- 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! @@ -13,26 +14,25 @@ A plugin for `webpack` consists of - A named JavaScript function. - Defines `apply` method in its prototype. -- Specifies an [event hook](/api/compiler-hooks/) on which to bind itself. +- Specifies an [event hook](/api/compiler-hooks/) to tap into. - Manipulates webpack internal instance specific data. - Invokes webpack provided callback after functionality is complete. ```javascript -// A named JavaScript function. -function MyExampleWebpackPlugin() { - -}; - -// Defines `apply` method in it's prototype. -MyExampleWebpackPlugin.prototype.apply = function(compiler) { - // Specifies webpack's event hook to attach itself. - compiler.plugin('webpacksEventHook', function(compilation /* Manipulates webpack internal instance specific data. */, callback) { - console.log("This is an example plugin!!!"); - - // Invokes webpack provided callback after functionality is complete. - callback(); - }); -}; +// A JavaScript class. +class MyExampleWebpackPlugin { + // Define `apply` as it's prototype method which is supplied with compiler as it's argument + apply(compiler) { + // Specifiy Hook in which you want to plugin to + compiler.hooks.some_hook_you_wish_to_plugin_to.tap( + "Name of your plugin for debugging purposes", + compilation => { + /* Manipulate webpack internal instance specific data using compilation */ + console.log("This is example of synchronouse plugin"); + } + ); + } +} ``` ## Compiler and Compilation @@ -50,33 +50,30 @@ These two components are an integral part of any webpack plugin (especially a `c ## 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 simple plugin is structured as follows: ```javascript -function HelloWorldPlugin(options) { - // Setup the plugin instance with options... +class HelloWorldPlugin { + apply(compiler) { + compiler.hooks.done.tap("Hello World Plugin", ( + stats /* stats is passed as argument when done hook is tapped. */ + ) => { + console.log("Hello World!"); + }); + } } -HelloWorldPlugin.prototype.apply = function(compiler) { - compiler.plugin('done', function() { - console.log('Hello World!'); - }); -}; - module.exports = HelloWorldPlugin; ``` Then to install the plugin, just include an instance in your webpack config `plugins` array: ```javascript -var HelloWorldPlugin = require('hello-world'); +var HelloWorldPlugin = require("hello-world"); var webpackConfig = { // ... config settings here ... - plugins: [ - new HelloWorldPlugin({options: true}) - ] + plugins: [new HelloWorldPlugin({ options: true })] }; ``` @@ -85,19 +82,17 @@ var webpackConfig = { Using the compiler object, you may bind callbacks that provide a reference to each new compilation. These compilations provide callbacks for hooking into numerous steps within the build process. ```javascript -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."); +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; ``` @@ -106,21 +101,47 @@ For more information on what callbacks are available on the `compiler`, `compila ## Async compilation plugins -Some compilation plugin steps are asynchronous, and pass a callback function that _must_ be invoked when your plugin is finished running. +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 asyncronous 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. ```javascript -function HelloAsyncPlugin(options) {} +class HelloAsyncPlugin{ + apply(compiler){ + + compiler.hooks.emit.tapAsync("HelloAsyncPlugin", (compilation, callback) => { + // Do something async... + setTimeout(function() { + console.log("Done with async work..."); + callback(); + }, 1000); + } + } +}; -HelloAsyncPlugin.prototype.apply = function(compiler) { - compiler.plugin("emit", function(compilation, callback) { +module.exports = HelloAsyncPlugin; +``` - // Do something async... - setTimeout(function() { - console.log("Done with async work..."); - callback(); - }, 1000); +#### tapPromise - }); +When we use tapPromise method to tap into plugins, we need to return a promise which resolves when our asynchronous task is completed. + +```javascript +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; @@ -133,76 +154,106 @@ Once we can latch onto the webpack compiler and each individual compilations, th Let's write a simple example plugin that generates a new build file called `filelist.md`; the contents of which will list all of the asset files in our build. This plugin might look something like this: ```javascript -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; +class FileListPlugin { + apply(compiler) { + // emit is asynchronous hook, tapping into it using tapAsync, you can use tapPromise/tap(synchronous) as well + compiler.hooks.emit.tapAsync("FileListPlugin", (compiler, 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"; } - }; - callback(); - }); -}; + // 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; ``` ## Different Plugin Shapes -A plugin can be classified into types based on the event it is registered to. Every event hook decides how it is going to apply the plugins in its registry. +A plugin can be classified into types based on the event hooks it taps into. Every event hook is pre-defined as synchronous/asynchronous/waterfall/parallel and hook is called internally using call/callAsync method. The list of hooks that are supported/can be tapped into are generally specified in this.hooks property. -- __synchronous__ The Tapable instance applies plugins using +For example:- + +```javascript +this.hooks = { + shouldEmit: new SyncBailHook(["compilation"]) +}; +``` -`applyPlugins(name: string, args: any...)` +It represents that the only hook supported is shouldEmit which is a hook of `SyncBailHook` type and the only parameter which will be passed to any plugin that taps into shouldEmit hook is compilation. -`applyPluginsBailResult(name: string, args: any...)` +Various types of hooks supported are :- -This means that each of the plugin callbacks will be invoked one after the other with the specific `args`. -This is the simplest format for a plugin. Many useful events like `"compile"`, `"this-compilation"` expect plugins to have synchronous execution. +### Synchronous Hooks -- __waterfall__ Plugins applied using +- **SyncHook** + - defined as `new SyncHook([params])` + - Tapped into using `tap` method. + - Called using `call(...params)` method. -`applyPluginsWaterfall(name: string, init: any, args: any...)` +- **Bail Hooks** + - defined using `SyncBailHook[params]` + - Tapped into using `tap` method. + - Called using `call(...params)` method. -Here each of the plugins are called one after the other with the args 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 is `init`. This pattern is used in the Tapable instances which are related to the `webpack` templates like `ModuleTemplate`, `ChunkTemplate` etc. + In these type of hooks, each of the plugin callbacks will be invoked one after the other with the specific `args`. 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 like `"optimizeChunks"`, `"optimizeChunkModules"` are SyncBailHooks. -- __asynchronous__ When all the plugins are applied asynchronously using +- **Waterfall Hooks** + - defined using `SyncWaterfallHook[params]` + - Tapped into using `tap` method. + - Called using `call( ... params)` method -`applyPluginsAsync(name: string, args: any..., callback: (err?: Error) -> void)` + Here each of the plugins are called one after the other with the args 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 is `init`. Hence atleast 1 param must be supplied for waterfall Hooks. This pattern is used in the Tapable instances which are related to the `webpack` templates like `ModuleTemplate`, `ChunkTemplate` etc. -The plugin handler functions are called with all args 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 like `"emit"`, `"run"`. +### Asynchronous Hooks -- __async waterfall__ The plugins will be applied asynchronously in the waterfall manner. +- **Async Series Hook** + - defined using `AsyncSeriesHook[params]` + - Tapped into using `tap`/`tapAsync`/`tapPromise` method. + - Called using `callAsync( ... params)` method -`applyPluginsAsyncWaterfall(name: string, init: any, callback: (err: Error, result: any) -> void)` + The plugin handler functions are called with all args 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 like `"emit"`, `"run"`. -The plugin handler functions are called with the current value and a callback function with the signature `(err: Error, nextValue: any) -> void.` When called `nextValue` is the current value for the next handler. The current value for the first handler is `init`. After all handlers are applied, callback is called with the last value. If any handler passes a value for `err`, the callback is called with this error and no more handlers are called. -This plugin pattern is expected for events like `"before-resolve"` and `"after-resolve"`. +- **Async waterfall** The plugins will be applied asynchronously in the waterfall manner. + - defined using `AsyncWaterfallHook[params]` + - Tapped into using `tap`/`tapAsync`/`tapPromise` method. + - Called using `callAsync( ... params)` method -- __async series__ It is the same as asynchronous but if any of the plugins registered fails, then no more plugins are called. + The plugin handler functions are called with the current value and a callback function with the signature `(err: Error, nextValue: any) -> void.` When called `nextValue` is the current value for the next handler. The current value for the first handler is `init`. After all handlers are applied, callback is called with the last value. If any handler passes a value for `err`, the callback is called with this error and no more handlers are called. + This plugin pattern is expected for events like `"before-resolve"` and `"after-resolve"`. -`applyPluginsAsyncSeries(name: string, args: any..., callback: (err: Error, result: any) -> void)` +- **Async Series Bail** + - defined using `AsyncSeriesBailHook[params]` + - Tapped into using `tap`/`tapAsync`/`tapPromise` method. + - Called using `callAsync( ... params)` method --__parallel__ - +-**parallel** - -`applyPluginsParallel(name: string, args: any..., callback: (err?: Error) -> void)` +- **Async Parallel** + - defined using `AsyncParallelHook[params]` + - Tapped into using `tap`/`tapAsync`/`tapPromise` method. + - Called using `callAsync( ... params)` method -`applyPluginsParallelBailResult(name: string, args: any..., callback: (err: Error, result: any) -> void)` +- **Async Series Bail** + - defined using `AsyncSeriesBailHook[params]` + - Tapped into using `tap`/`tapAsync`/`tapPromise` method. + - Called using `callAsync( ... params)` method From 7d33eee18fd12de2d0047b3b4f76ea7bd1e98dac Mon Sep 17 00:00:00 2001 From: Naveen Jain Date: Mon, 3 Sep 2018 10:18:00 +0530 Subject: [PATCH 2/5] Fixed formatting issues --- src/content/contribute/plugin-patterns.md | 18 +++--- src/content/contribute/writing-a-plugin.md | 74 +++++++++++----------- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/src/content/contribute/plugin-patterns.md b/src/content/contribute/plugin-patterns.md index 50f04712a88c..66efdc5a7a74 100644 --- a/src/content/contribute/plugin-patterns.md +++ b/src/content/contribute/plugin-patterns.md @@ -14,19 +14,19 @@ After a compilation is sealed, all structures within the compilation may be trav ```javascript class MyPlugin { apply(compiler) { - compiler.hooks.emit.tapAsync("MyPlugin", (compilation, callback) => { + compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => { // Explore each chunk (build output): - compilation.chunks.forEach(function(chunk) { + compilation.chunks.forEach(chunk => { // Explore each module within the chunk (built inputs): - chunk.modules.forEach(function(module) { + chunk.modules.forEach(module => { // Explore each source file path that was included into the module: - module.fileDependencies.forEach(function(filepath) { + module.fileDependencies.forEach(filepath => { // we've learned a lot about the source structure now... }); }); // Explore each asset filename generated by the chunk: - chunk.files.forEach(function(filename) { + chunk.files.forEach(filename => { // Get the asset source for each file generated by the chunk: var source = compilation.assets[filename].source(); }); @@ -56,7 +56,7 @@ class MyPlugin { this.prevTimestamps = {}; } apply(compiler) { - compiler.hooks.emit.tapAsync("MyPlugin", (compilation, callback) => { + compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => { var changedFiles = Object.keys(compilation.fileTimestamps).filter( watchfile => { return ( @@ -82,13 +82,13 @@ You may also feed new file paths into the watch graph to receive compilation tri Similar to the watch graph, it's fairly simple to monitor changed chunks (or modules, for that matter) within a compilation by tracking their hashes. ```javascript -class MyPlugin{ +class MyPlugin { constructor(){ this.chunkVersions = {}; } apply(compiler){ - compiler.hooks.emit.tapAsync((compilation, callback)=>{ - var changedChunks = compilation.chunks.filter((chunk) => { + compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => { + var changedChunks = compilation.chunks.filter( (chunk) => { var oldVersion = this.chunkVersions[chunk.name]; this.chunkVersions[chunk.name] = chunk.hash; return chunk.hash !== oldVersion; diff --git a/src/content/contribute/writing-a-plugin.md b/src/content/contribute/writing-a-plugin.md index 542708cdbef6..5c4de7912320 100644 --- a/src/content/contribute/writing-a-plugin.md +++ b/src/content/contribute/writing-a-plugin.md @@ -10,7 +10,7 @@ Plugins expose the full potential of the webpack engine to third-party developer ## Creating a Plugin -A plugin for `webpack` consists of +A plugin for webpack consists of - A named JavaScript function. - Defines `apply` method in its prototype. @@ -25,10 +25,10 @@ class MyExampleWebpackPlugin { apply(compiler) { // Specifiy Hook in which you want to plugin to compiler.hooks.some_hook_you_wish_to_plugin_to.tap( - "Name of your plugin for debugging purposes", + 'Name of your plugin for debugging purposes', compilation => { /* Manipulate webpack internal instance specific data using compilation */ - console.log("This is example of synchronouse plugin"); + console.log('This is example of synchronouse plugin'); } ); } @@ -55,10 +55,10 @@ Plugins are instantiated objects with an `apply` method on their prototype. This ```javascript class HelloWorldPlugin { apply(compiler) { - compiler.hooks.done.tap("Hello World Plugin", ( + compiler.hooks.done.tap('Hello World Plugin', ( stats /* stats is passed as argument when done hook is tapped. */ ) => { - console.log("Hello World!"); + console.log('Hello World!'); }); } } @@ -69,7 +69,7 @@ module.exports = HelloWorldPlugin; Then to install the plugin, just include an instance in your webpack config `plugins` array: ```javascript -var HelloWorldPlugin = require("hello-world"); +var HelloWorldPlugin = require('hello-world'); var webpackConfig = { // ... config settings here ... @@ -85,10 +85,10 @@ Using the compiler object, you may bind callbacks that provide a reference to ea class HelloCompilationPlugin { apply(compiler) { // Tap into compilation hook which gives compilation as argument to the callback function - compiler.hooks.compilation.tap("HelloCompilationPlugin", compilation => { + 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."); + compilation.hooks.optimize.tap('HelloCompilationPlugin', () => { + console.log('Assets are being optimized.'); }); }); } @@ -105,16 +105,16 @@ Some plugin hooks are asynchronous. To tap into them, we can use `tap` method wh ### 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. +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. ```javascript class HelloAsyncPlugin{ apply(compiler){ - compiler.hooks.emit.tapAsync("HelloAsyncPlugin", (compilation, callback) => { + compiler.hooks.emit.tapAsync('HelloAsyncPlugin', (compilation, callback) => { // Do something async... setTimeout(function() { - console.log("Done with async work..."); + console.log('Done with async work...'); callback(); }, 1000); } @@ -126,17 +126,17 @@ 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. +When we use `tapPromise` method to tap into plugins, we need to return a promise which resolves when our asynchronous task is completed. ```javascript class HelloAsyncPlugin{ apply(compiler){ - compiler.hooks.emit.tapPromise("HelloAsyncPlugin", compilation => { + 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..."); + console.log('Done with async work...'); resolve(); }, 1000); }) @@ -157,18 +157,18 @@ Let's write a simple example plugin that generates a new build file called `file class FileListPlugin { apply(compiler) { // emit is asynchronous hook, tapping into it using tapAsync, you can use tapPromise/tap(synchronous) as well - compiler.hooks.emit.tapAsync("FileListPlugin", (compiler, callback) => { + compiler.hooks.emit.tapAsync('FileListPlugin', (compiler, callback) => { // Create a header string for the generated file: - var filelist = "In this build:\n\n"; + 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"; + filelist += '- ' + filename + '\n'; } // Insert this list into the webpack build as a new file asset: - compilation.assets["filelist.md"] = { + compilation.assets['filelist.md'] = { source: function() { return filelist; }, @@ -187,73 +187,73 @@ module.exports = FileListPlugin; ## 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/asynchronous/waterfall/parallel and hook is called internally using call/callAsync method. The list of hooks that are supported/can be tapped into are generally specified in this.hooks property. +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 are generally specified in this.hooks property. For example:- ```javascript this.hooks = { - shouldEmit: new SyncBailHook(["compilation"]) + shouldEmit: new SyncBailHook(['compilation']) }; ``` -It represents that the only hook supported is shouldEmit which is a hook of `SyncBailHook` type and the only parameter which will be passed to any plugin that taps into shouldEmit hook is compilation. +It represents that the only hook supported is `shouldEmit` which is a hook of `SyncBailHook` type and the only parameter which will be passed to any plugin that taps into `shouldEmit` hook is `compilation`. Various types of hooks supported are :- ### Synchronous Hooks -- **SyncHook** +- __SyncHook__ - defined as `new SyncHook([params])` - Tapped into using `tap` method. - Called using `call(...params)` method. -- **Bail Hooks** +- __Bail Hooks__ - defined using `SyncBailHook[params]` - Tapped into using `tap` method. - Called using `call(...params)` method. - In these type of hooks, each of the plugin callbacks will be invoked one after the other with the specific `args`. 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 like `"optimizeChunks"`, `"optimizeChunkModules"` are SyncBailHooks. + In these type of hooks, each of the plugin callbacks will be invoked one after the other with the specific `args`. 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 like `optimizeChunks`, `optimizeChunkModules` are SyncBailHooks. -- **Waterfall Hooks** +- __Waterfall Hooks__ - defined using `SyncWaterfallHook[params]` - Tapped into using `tap` method. - Called using `call( ... params)` method - Here each of the plugins are called one after the other with the args 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 is `init`. Hence atleast 1 param must be supplied for waterfall Hooks. This pattern is used in the Tapable instances which are related to the `webpack` templates like `ModuleTemplate`, `ChunkTemplate` etc. + Here each of the plugins are 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 is `init`. Hence atleast 1 param must be supplied for waterfall hooks. This pattern is used in the Tapable instances which are related to the webpack templates like `ModuleTemplate`, `ChunkTemplate` etc. ### Asynchronous Hooks -- **Async Series Hook** +- __Async Series Hook__ - defined using `AsyncSeriesHook[params]` - Tapped into using `tap`/`tapAsync`/`tapPromise` method. - Called using `callAsync( ... params)` method - The plugin handler functions are called with all args 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 like `"emit"`, `"run"`. + 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 like `emit`, `run`. -- **Async waterfall** The plugins will be applied asynchronously in the waterfall manner. +- __Async waterfall__ The plugins will be applied asynchronously in the waterfall manner. - defined using `AsyncWaterfallHook[params]` - Tapped into using `tap`/`tapAsync`/`tapPromise` method. - Called using `callAsync( ... 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 called `nextValue` is the current value for the next handler. The current value for the first handler is `init`. After all handlers are applied, callback is called with the last value. If any handler passes a value for `err`, the callback is called with this error and no more handlers are called. - This plugin pattern is expected for events like `"before-resolve"` and `"after-resolve"`. + This plugin pattern is expected for events like `before-resolve` and `after-resolve`. -- **Async Series Bail** +- __Async Series Bail__ - defined using `AsyncSeriesBailHook[params]` - Tapped into using `tap`/`tapAsync`/`tapPromise` method. - Called using `callAsync( ... params)` method --**parallel** - +-__parallel__ - -- **Async Parallel** +- __Async Parallel__ - defined using `AsyncParallelHook[params]` - Tapped into using `tap`/`tapAsync`/`tapPromise` method. - Called using `callAsync( ... params)` method -- **Async Series Bail** +- __Async Series Bail__ - defined using `AsyncSeriesBailHook[params]` - Tapped into using `tap`/`tapAsync`/`tapPromise` method. - Called using `callAsync( ... params)` method From 3138c5c72e9c7dd78044798c8e6a71930cd4aa00 Mon Sep 17 00:00:00 2001 From: Fernando Montoya Date: Mon, 3 Sep 2018 15:34:20 +0200 Subject: [PATCH 3/5] Fix markdown lint issues --- src/content/contribute/writing-a-plugin.md | 137 ++++++++++----------- 1 file changed, 66 insertions(+), 71 deletions(-) diff --git a/src/content/contribute/writing-a-plugin.md b/src/content/contribute/writing-a-plugin.md index 5c4de7912320..314464b31978 100644 --- a/src/content/contribute/writing-a-plugin.md +++ b/src/content/contribute/writing-a-plugin.md @@ -4,6 +4,8 @@ sort: 4 contributors: - tbroadley - nveenjain + - iamakulov + - byzyk --- 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! @@ -24,30 +26,14 @@ class MyExampleWebpackPlugin { // Define `apply` as it's prototype method which is supplied with compiler as it's argument apply(compiler) { // Specifiy Hook in which you want to plugin to - compiler.hooks.some_hook_you_wish_to_plugin_to.tap( - 'Name of your plugin for debugging purposes', - compilation => { - /* Manipulate webpack internal instance specific data using compilation */ - console.log('This is example of synchronouse plugin'); - } - ); + compiler.hooks.some_hook_you_wish_to_plugin_to.tap('Name of your plugin for debugging purposes', compilation => { + /* Manipulate webpack internal instance specific data using compilation */ + console.log('This is example of synchronouse plugin'); + }); } } ``` -## 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. - -- The `compiler` object represents the fully configured webpack environment. This object is built once upon starting webpack, and is configured with all operational settings including options, loaders, and plugins. When applying a plugin to the webpack environment, the plugin will receive a reference to this compiler. Use the compiler to access the main webpack 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. - -These two components are an integral part of any webpack plugin (especially a `compilation`), so developers will benefit by familiarizing themselves with these source files: - -- [Compiler Source](https://github.com/webpack/webpack/blob/master/lib/Compiler.js) -- [Compilation Source](https://github.com/webpack/webpack/blob/master/lib/Compilation.js) - ## 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 simple plugin is structured as follows: @@ -66,20 +52,21 @@ class HelloWorldPlugin { module.exports = HelloWorldPlugin; ``` -Then to install the plugin, just include an instance in your webpack config `plugins` array: +Then to use the plugin, include an instance in your webpack config `plugins` array: ```javascript +// webpack.config.js var HelloWorldPlugin = require('hello-world'); -var webpackConfig = { +module.exports = { // ... config settings here ... plugins: [new HelloWorldPlugin({ options: true })] }; ``` -## Accessing the compilation +## Compiler and Compilation -Using the compiler object, you may bind callbacks that provide a reference to each new compilation. These compilations provide callbacks for hooking into numerous steps within the build process. +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. ```javascript class HelloCompilationPlugin { @@ -97,9 +84,9 @@ class HelloCompilationPlugin { module.exports = HelloCompilationPlugin; ``` -For more information on what callbacks are available on the `compiler`, `compilation`, and other important objects, see the [plugins](/api/plugins/) doc. +The list of hooks available on the `compiler`, `compilation`, and other important objects, see the [plugins API](/api/plugins/) docs. -## Async compilation plugins +## 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 asyncronous methods. @@ -108,18 +95,17 @@ Some plugin hooks are asynchronous. To tap into them, we can use `tap` method wh 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. ```javascript -class HelloAsyncPlugin{ - apply(compiler){ - +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; ``` @@ -129,20 +115,19 @@ module.exports = HelloAsyncPlugin; When we use `tapPromise` method to tap into plugins, we need to return a promise which resolves when our asynchronous task is completed. ```javascript -class HelloAsyncPlugin{ - apply(compiler){ - +class HelloAsyncPlugin { + apply(compiler) { compiler.hooks.emit.tapPromise('HelloAsyncPlugin', compilation => { // return a Promise that resolves when we are done... - return new Promise((resolve,reject)=>{ + return new Promise((resolve, reject) => { setTimeout(function() { console.log('Done with async work...'); resolve(); }, 1000); - }) - } + }); + }); } -}; +} module.exports = HelloAsyncPlugin; ``` @@ -203,57 +188,67 @@ Various types of hooks supported are :- ### Synchronous Hooks -- __SyncHook__ - - defined as `new SyncHook([params])` - - Tapped into using `tap` method. - - Called using `call(...params)` method. +- **SyncHook** + + - Defined as `new SyncHook([params])` + - Tapped into using `tap` method. + - Called using `call(...params)` method. -- __Bail Hooks__ - - defined using `SyncBailHook[params]` - - Tapped into using `tap` method. - - Called using `call(...params)` method. +- **Bail Hooks** + + - Defined using `SyncBailHook[params]` + - Tapped into using `tap` method. + - Called using `call(...params)` method. In these type of hooks, each of the plugin callbacks will be invoked one after the other with the specific `args`. 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 like `optimizeChunks`, `optimizeChunkModules` are SyncBailHooks. -- __Waterfall Hooks__ - - defined using `SyncWaterfallHook[params]` - - Tapped into using `tap` method. - - Called using `call( ... params)` method +- **Waterfall Hooks** + + - Defined using `SyncWaterfallHook[params]` + - Tapped into using `tap` method. + - Called using `call( ... params)` method Here each of the plugins are 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 is `init`. Hence atleast 1 param must be supplied for waterfall hooks. This pattern is used in the Tapable instances which are related to the webpack templates like `ModuleTemplate`, `ChunkTemplate` etc. ### Asynchronous Hooks -- __Async Series Hook__ - - defined using `AsyncSeriesHook[params]` - - Tapped into using `tap`/`tapAsync`/`tapPromise` method. - - Called using `callAsync( ... params)` method +- **Async Series Hook** + + - Defined using `AsyncSeriesHook[params]` + - Tapped into using `tap`/`tapAsync`/`tapPromise` method. + - Called using `callAsync( ... 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 like `emit`, `run`. -- __Async waterfall__ The plugins will be applied asynchronously in the waterfall manner. - - defined using `AsyncWaterfallHook[params]` - - Tapped into using `tap`/`tapAsync`/`tapPromise` method. - - Called using `callAsync( ... params)` method +- **Async waterfall** The plugins will be applied asynchronously in the waterfall manner. + + - Defined using `AsyncWaterfallHook[params]` + - Tapped into using `tap`/`tapAsync`/`tapPromise` method. + - Called using `callAsync( ... 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 called `nextValue` is the current value for the next handler. The current value for the first handler is `init`. After all handlers are applied, callback is called with the last value. If any handler passes a value for `err`, the callback is called with this error and no more handlers are called. This plugin pattern is expected for events like `before-resolve` and `after-resolve`. -- __Async Series Bail__ - - defined using `AsyncSeriesBailHook[params]` - - Tapped into using `tap`/`tapAsync`/`tapPromise` method. - - Called using `callAsync( ... params)` method +- **Async Series Bail** + + - Defined using `AsyncSeriesBailHook[params]` + - Tapped into using `tap`/`tapAsync`/`tapPromise` method. + - Called using `callAsync( ... params)` method + + someMethod() { + // Call a hook: + this.hooks.compilation.call(); + +- **Async Parallel** --__parallel__ - + - Defined using `AsyncParallelHook[params]` + - Tapped into using `tap`/`tapAsync`/`tapPromise` method. + - Called using `callAsync( ... params)` method -- __Async Parallel__ - - defined using `AsyncParallelHook[params]` - - Tapped into using `tap`/`tapAsync`/`tapPromise` method. - - Called using `callAsync( ... params)` method +- **Async Series Bail** -- __Async Series Bail__ - - defined using `AsyncSeriesBailHook[params]` - - Tapped into using `tap`/`tapAsync`/`tapPromise` method. - - Called using `callAsync( ... params)` method + - Defined using `AsyncSeriesBailHook[params]` + - Tapped into using `tap`/`tapAsync`/`tapPromise` method. + - Called using `callAsync( ... params)` method From 50ffa2c42257e5ddcd8cc6dda304433780a6ce31 Mon Sep 17 00:00:00 2001 From: Fernando Montoya Date: Tue, 18 Sep 2018 03:39:19 +0200 Subject: [PATCH 4/5] Update plugin-patterns.md --- src/content/contribute/plugin-patterns.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/content/contribute/plugin-patterns.md b/src/content/contribute/plugin-patterns.md index e993c84763de..00d990edabd4 100644 --- a/src/content/contribute/plugin-patterns.md +++ b/src/content/contribute/plugin-patterns.md @@ -83,18 +83,18 @@ Similar to the watch graph, it's fairly simple to monitor changed chunks (or mod ```javascript class MyPlugin { - constructor(){ + constructor() { this.chunkVersions = {}; } - apply(compiler){ - compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => { - var changedChunks = compilation.chunks.filter( (chunk) => { - var oldVersion = this.chunkVersions[chunk.name]; - this.chunkVersions[chunk.name] = chunk.hash; - return chunk.hash !== oldVersion; - } + apply(compiler) { + compiler.hooks.emit.tapAsync("MyPlugin", (compilation, callback) => { + var changedChunks = compilation.chunks.filter(chunk => { + var oldVersion = this.chunkVersions[chunk.name]; + this.chunkVersions[chunk.name] = chunk.hash; + return chunk.hash !== oldVersion; }); - callback(); + callback(); + }); } } From f30146777105d2c149d9db81341ea2e844a60720 Mon Sep 17 00:00:00 2001 From: Fernando Montoya Date: Tue, 18 Sep 2018 03:44:00 +0200 Subject: [PATCH 5/5] Update plugin-patterns.md --- src/content/contribute/plugin-patterns.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/contribute/plugin-patterns.md b/src/content/contribute/plugin-patterns.md index 00d990edabd4..e59013d4cc03 100644 --- a/src/content/contribute/plugin-patterns.md +++ b/src/content/contribute/plugin-patterns.md @@ -87,7 +87,7 @@ class MyPlugin { this.chunkVersions = {}; } apply(compiler) { - compiler.hooks.emit.tapAsync("MyPlugin", (compilation, callback) => { + compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => { var changedChunks = compilation.chunks.filter(chunk => { var oldVersion = this.chunkVersions[chunk.name]; this.chunkVersions[chunk.name] = chunk.hash;