What are plug-ins and what plug-ins do?

Plug-ins are an important part of the WebPack ecosystem and provide a powerful way for the community to directly touch the WebPack compilation process.

The plug-in can hook into all key events that are triggered during each compilation. At each step of compilation, the plug-in has full access to the Compiler object and, if appropriate, to the current compilation object.

To sum up: WebPack is essentially an event flow mechanism, and its workflow is to concatenate plug-ins. So each plug-in in this process is how to execute, using the Tapable library.

We can think of WebPack as a production line that goes through a series of processes to convert source files into output.

Each process on the production line has a single responsibility, and there are dependencies between multiple processes. Only after the current process is completed can the next process be handled.

Our plugin is like a function that plugs into the production line and does something to the resources on the production line at a specific time. Webpack it organizes this complex production line through Tapable.

Two. What isTapable?

Tapable, a small library, is a core tool for WebPack, but can be used elsewhere to provide a similar plug-in interface.

Many objects in Webpack extend from the Tapable class. This class exposes the TAP, tapAsync, and tapPromise registered event methods, whose corresponding call methods are Call, callAsync, and Promise, and you can use these methods to inject custom build steps that will be fired at different times throughout the build process.

See the documentation for more information. It is important to understand the three TAP methods and the hooks that provide them.

Tapable exposes a number of hook classes that provide hooks for our plug-in to mount. So these hooks can be divided into two categories, namely “synchronous” and “asynchronous”. Asynchronous hooks are divided into two categories, “parallel” or “serial”. Synchronous hooks are only “serial”.

As shown in figure:

Example the use of tapable

Reference documents are as follows:

Mayufo. Gitbooks. IO/webpack/con…

Juejin. Cn/post / 684490…

const  {SyncHook} = require('tapable')


class Lesson {
    constructor(){
        this.hook={
            arch:new SyncHook(['data'])
        }
    }
    tap(){
       this.hook.arch.tap('vue',(data)=>{
           console.log('vue', data);
       })
    }
    call(){
        this.hook.arch.call(123)
    }
}


const l = new Lesson()
l.tap()
l.call(
Copy the code

How to develop a custom plug-in?

Reference: www.cnblogs.com/tugenhua070…

The principle of simulation Compiler

The compiler. In js

const { SyncHook, AsyncParallelHook } = require('tapable'); class Compiler { constructor(options) { this.hooks = { kzSyncHook: new SyncHook(['name', 'age']), kzAsyncHook: new AsyncParallelHook(['name', 'age']) }; let plugins = options.plugins; if (plugins && plugins.length > 0) { plugins.forEach(plugin => plugin.apply(this)); }} run() {console.log(' execute ---------'); This.kzsynchook (' I am empty wisdom ', 31); This.kzasynchook (' I am empty intelligence ', 31); } kzSyncHook(name, age) { this.hooks.kzSyncHook.call(name, age); } kzAsyncHook(name, age) { this.hooks.kzAsyncHook.callAsync(name, age); } } module.exports = CompilerCopy the code

In the main. Js

const Compiler = require('./compiler'); class MyPlugin { constructor() { } apply(compiler) { compiler.hooks.kzSyncHook.tap("eventName1", (name, Age) => {console.log(' synchronize eventName1: ${name} this year ${age} old, but still single '); }); Compiler. Hooks. KzAsyncHook. TapAsync (' eventName2, (name, age) = > {setTimeout (() = > {the console. The log (` eventName2 asynchronous events: ${name} this year ${age} old, but still single '); }, 1000)}); } } const myPlugin = new MyPlugin(); const options = { plugins: [myPlugin] }; const compiler = new Compiler(options); compiler.runCopy the code

Above is a simple demo of the logic of Compiler and Webpack’s plug-in principle. That is to say, in the Webpack source code is done in a similar way.

The above is just the basics of a simple implementation, but how do we implement a plug-in in our Webpack?

In our webpack website, we will introduce how to write a plug-in to meet the following conditions, the official website address from the official website: To write a Webpack plug-in needs to be composed of the following:

  1. A javascript naming function.

  2. Define an apply method on the prototype of the plug-in function.

  3. Specifies a hook function bound to WebPack itself.

  4. Handles specific data for real columns within webpack.

  5. Call the webPack callback function when the functionality is complete.

The two most commonly used objects for Plugin development are Compiler and Compilation, which bridge the gap between Plugin and Webpack.

The compiler object

The Compiler object contains all configuration information for the Webpack environment, including options (loaders, plugins…). These items, this object is instantiated when WebPack starts, and it’s globally unique. We can think of it as a real column for Webpack.

The basic source code can be read as follows:

/ webpack/lib/webpack.js const Compiler = require("./Compiler") const webpack = (options, callback) => { ... // Initialize webPack configuration parameters options = new WebpackOptionsDefaulter().process(options); // Initialize the compiler object, where options.context is process.cwd() let compiler = new Compiler(options.context); Compiler.options = options // Add the initialization parameter new NodeEnvironmentPlugin(). Apply (compiler) // Add Node to compiler For (const plugin of options.plugins) {plugin.apply(compiler); }...Copy the code

As you can see above, the Compiler object contains all webPack configurable content. When developing the plug-in, we can get everything relevant to the WebPack main environment from the Compiler object.

Compilation object

The compilation object contains the current module resources, compile-generated resources, file changes, and so on. When WebPack is running in development mode, a new Compilation is created whenever a file change is detected. A new set of compilation resources is generated.

Compiler objects differ from Compilation objects in that Compiler objects represent the entire Webpack lifecycle from startup to shutdown. The Compilation object represents only a new Compilation.

Compiler object event hooks: Here are some of the more common event hooks and their functions:

After setting a set of initialized plug-ins compiler sync after-resolvers After setting resolvers Compiler sync run Before reading the record CompilationParams sync compilation compilationParams sync compilation compilation before a new compilation is created Compilation Async after-emit Compile stats sync before generating resources and exporting them to directoryCopy the code

4. Common apis used in plug-ins

1. Read output resources, modules, and dependencies

When our emit hook event occurs, it means that the conversion and assembly of the source file has been completed and we can read the final output resource, code block, module and corresponding dependency file from the event hook. And we can also output the contents of the resource file. For example, the plug-in code looks like this:

class MyPlugin { apply(compiler) { compiler.plugin('emit', function(compilation, Callback) {// compilation.chunks are an array of code blocks, We need to traverse the compilation. Chunks. ForEach (function (the chunk) {/ * * the chunk represents a code block, block it is composed of multiple modules. */ chunk.forEachModule(function(module) {// module stands for a module. // module.fileDependencies Store all dependencies of the current module. It is an array module. FileDependencies. ForEach (function ({filepath). The console log (filepath); }); }); /* Webpack will generate output file resources according to chunk, each chunk corresponds to one or more output files. For example, if the Chunk contains CSS modules and the ExtractTextPlugin is used, */ chunk.files.forEach(function(filename) {// Compilation. assets is used to store all the current resources to be output. Const source = compile.assets [filename].source(); // Call a source() method to retrieve the contents of an output resource }); }); /* This event is an asynchronous event, so callback is called to notify the end of the Webpack event listening. If we do not call callback(); Then the Webpack will stay stuck and not be executed later. */ callback(); }}})Copy the code

2. Listen for file changes

When WebPack reads the file, it reads from the entry module and then finds all the dependent modules in turn. When an entry module or dependent module changes, a new Compilation is triggered.

When we develop a plug-in, we need to know which file changed to cause a new Compilation, so we can add the following code to listen for it.

Class MyPlugin {apply(compiler) {compiler.plugin('watch-run', (watching,) Callback) = > {/ / get the file list shifted const changedFiles = watching.com piler. WatchFileSystem. The watcher. Mtimes; If (changedFiles[filePath]!) if (changedFiles[filePath]! Callback () == undefined) {// Callback (); }); /* By default, Webpack only listens for changes to the entry file or its dependent modules, but in some cases, such as when the HTML file changes, Webpack listens for changes to the HTML file. Therefore, a new Compilation is not triggered again. So in order to listen for changes to the HTML file, we need to add the HTML file to the dependency list. So we need to add the following code: */ compiler.plugin('after-compile', (compilation, callback) => {/* The following parameter filePath is the path to the HTML file, we added the HTML file to the dependency table, Then we'll have to listen webpack HTML module file, HTML template file, at the time of change will restart next to compile a new Compilation. * / Compilation. FileDependencies. Push (filePath); callback(); }}})Copy the code

3. Modify output resources

We mentioned in the first point that when our EMIT hook event occurs, it means that the source file has been converted and assembled, and we can read the final output resource, code block, module and corresponding dependency file from the event hook. So if we want to modify the content of the output resource now, we can do so in the emit event. Compilation. assets is a key-value pair. The key is the file name that needs to be output, and the value is the corresponding content of the file. The following code:

class MyPlugin { apply(compiler) { compiler.plugin('emit', (compilation, Callback) => {// Set the output resource compilation named fileName. Assets [fileName] = {// Return the contents of the file source: Bufferreturn fileContent () => {// fileContent can be a string representing a text file or a binary file. }, // Return file size size: () => {return buffer.bytelength (fileContent, 'utf8'); }}; callback(); }); // Read the compilation.assets code as follows:  compiler.plugin('emit', (compilation, Callback) => {// Retrieve an output resource named fileName const asset = compile.assets [fileName]; // Get the contents of the output resource asset.source(); Asset-.size (); callback(); }); }}Copy the code

Writing plug-ins

Suppose our project now has a directory structure like this:

| webpack - plugin - demo | | - node_modules | | - js | | | -- - the main, js # js entry file | | - plugins | | | -- - Logwebpackplugin.js # webpack plugin, Main function is to print logs | | - styles | | - index. The HTML | | - package. The json | | - webpack. Config. JsCopy the code

1. Implement a LogWebpackPlugin to print logs

The code is as follows:

class LogWebpackPlugin { constructor(doneCallback, emitCallback) { this.emitCallback = emitCallback this.doneCallback = doneCallback } apply(compiler) { Compiler. Hooks. Emit. Tap (' LogWebpackPlugin '() = > {/ / in emit event callback emitCallbackthis emitCallback (); }); Compiler. Hooks. Done. Tap (' LogWebpackPlugin '(err) = > {/ / in the done event callback doneCallbackthis doneCallback (); }); compiler.hooks.compilation.tap('LogWebpackPlugin', Console. log("The compiler is starting a new compilation...") => {// compilation ("The compiler is starting a new compilation...") )}); compiler.hooks.compile.tap('LogWebpackPlugin', Console. log("The compiler is starting to compile...") => {// compile ("The compiler is starting to compile...") )}); Module.exports = LogWebpackPlugin;Copy the code

Let’s introduce the plug-in in WebPack; The following code:

/ / introduce LogWebpackPlugin plug-in const LogWebpackPlugin = the require (".. / public/plugins/LogWebpackPlugin); module.exports = { plugins: [new LogWebpackPlugin(() => {// Webpack module completed conversion success console.log('emit event happened, }, () => {// Webpack was built successfully, and the output of the file will be executed here. Console. log('done event happened, Build completed successfully ~')})]}Copy the code

2. Implement removing comments

class MyPlugin { constructor(options) { this.options = options; this.externalModules = {}; } apply(compiler) { var reg = /("([^\"]*(\.) ?). * ") | (' ([^ \] * (\.) ?). * ') | (/ {2}. *? (\r|\n))|(/*(\n|.) *? */)|(/******/)/g; compiler.hooks.emit.tap('CodeBeautify', (compilation) => { Object.keys(compilation.assets).forEach((data) => { console.log(data); let content = compilation.assets[data].source(); / / for processing text content = content. the replace (reg, function (word) {/ / remove comments after the text of the return / ^ / {2} /. The test (word) | | / ^ / *! /.test(word) || /^/*{3,}//.test(word) ? "" : word; }); compilation.assets[data] = { source() { return content; }, size() { return content.length; }}}); }); } } module.exports = MyPlugin;Copy the code

The above is to write a Webpack plug-in to know the content, welcome to discuss, thank you!