This article uses webpack-heiry-starter to Quickly build a Webpack4 local learning environment. It is recommended to read the section “Writing a Plugin” in the Webpack documentation to learn how to develop simple plug-ins.

This article will take you from Webpack configuration engineer to Webpack development engineer by developing your first Webpack plug-in! Be your own wheel, and let others use it.

The complete code is at: github.com/pingan8787/…

I. Background introduction

This article is inspired by the summary of experience in the business. I am not afraid of the god-like products, but the single-minded development.

There is a problem in project packaging: “When the project is hosted to the CDN platform, it is hoped that the index.js in the project will not be cached”. Because we need to modify the contents of index.js, we don’t want the user to be cached.

Think about it for a while, there are several ideas:

  1. Filter the cache Settings of this file in the CDN platform;
  2. Find the DOM element and modify itscriptOf the labelsrcValue and add time stamp;
  3. Dynamic creation at packaging timescriptThe tag introduces the file and adds a time stamp.

(There are other ways for you to be smart. Welcome to discuss.)

Analysis of ideas:

  1. Obviously, if the CDN setting is modified, it will cure the symptoms rather than the root cause;
  2. In the template file, addscriptTag, perform get Webpack automatically addedscriptTag and for itsrcValue adds a timestamp. But the fact is that before you finish modifying, js files have been loaded, so give up
  3. Need to be inindex.htmlBefore generating, change the path of the JS file and add the timestamp.

So I’m going to use the third method, inindex.htmlComplete the following changes before generating:

Simple question, actually want to try to develop the Webpack Plugin.

Two, basic knowledge

Webpack uses staged build callbacks so developers can introduce their own behavior into the Webpack build process. Before developing, you need to understand the following Webpack concepts:

2.1 Webpack plug-in composition

Before we customize our plug-in, we need to understand what a Webpack plug-in consists of. Here is an excerpt from the documentation:

  • A named JavaScript function;
  • Define the apply method on its prototype;
  • Specify an event hook that touches Webpack itself;
  • Manipulate instance specific data within Webpack;
  • The callback provided by Webpack is called after the functionality is implemented.

2.2 Basic architecture of Webpack plug-in

The plug-in is instantiated by a constructor. The constructor defines the apply method, which is called once by the Webpack Compiler when the plug-in is installed. The Apply method can receive a reference to the Webpack Compiler object, which can be accessed in the callback function.

The official documentation provides a simple plug-in structure:

class HelloWorldPlugin {
  apply(compiler) {
    compiler.hooks.done.tap('Hello World Plugin'.(
      stats /* Stats is passed in as an argument when a hook is touched. * /
    ) = > {
      console.log('Hello World! '); }); }}module.exports = HelloWorldPlugin;
Copy the code

Using plug-ins:

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

module.exports = {
  / /... Here are the other configurations...
  plugins: [new HelloWorldPlugin({ options: true}})];Copy the code

2.3 introduce HtmlWebpackPlugin

The HtmlWebpackPlugin simplifies the creation of HTML files to serve your Webpack. This is especially useful for Webpack bundles that include a hash in their file name that changes each time they compile.

The basic function of the plug-in is summarized: generate HTML files.

The htML-webapck-plugin has two main functions:

  • Bring in external resources for HTML files (e.gscript / link) dynamically add hash after each compilation to prevent cache problems of reference files;
  • Create HTML entry files dynamically, as in a single page applicationindex.html File.

HTML – Webapck-plugin plugin principle introduction:

  • Read the WebpackentryConfiguration related entrychunk å’Œ extract-text-webpack-pluginCSS styles extracted by plug-ins;
  • Insert the style into the plug-in providedtemplate 或 templateContentConfigure the specified template file;
  • The insertion mode is throughlinkThe tag introduces styles throughscriptThe tag introduces the script file.

Iii. Development process

The principle of SetScriptTimestampPlugin developed in this paper is as follows: Before the HTML file is generated by HtmlWebpackPlugin, the reserved space of the template file is replaced with a script, in which time stamps are automatically added to reference the script file.

3.1 Operation mechanism of plug-ins

3.2 Initializing the plug-in file

New SetScriptTimestampPlugin js file, and refer to the basic structure of the plug-in in official document, initialize the plug-in code:

// SetScriptTimestampPlugin.js

class SetScriptTimestampPlugin {
  apply(compiler) {
    compiler.hooks.done.tap('SetScriptTimestampPlugin'.(compilation, callback) = > {
      console.log('SetScriptTimestampPlugin! '); }); }}module.exports = SetScriptTimestampPlugin;
Copy the code

The Apply method is the plug-in prototype method and accepts compiler as a parameter.

3.3 Selecting plug-in trigger timing

Choosing when to trigger the plug-in is actually a matter of choosing which compiler hooks the plug-in triggers. Compiler Hooks Webpack provides many Hooks. Here are a few.

  • entryOption: in the Webpack optionentryAfter the configuration items are processed, execute the plug-in.
  • afterPlugins: After setting up the initial plug-in, execute the plug-in.
  • compilationExecute the plug-in after compiling and creating, but before generating the file.
  • emit: Generates resources tooutputDirectory before.
  • done: The compilation is complete.

Our plugin is supposed to dynamically add script tags before the HTML output, so we hook into the compilation stage and modify the code:

// SetScriptTimestampPlugin.js

class SetScriptTimestampPlugin {
  apply(compiler) {
- compiler.hooks.done.tap('SetScriptTimestampPlugin',
+ compiler.hooks.compilation.tap('SetScriptTimestampPlugin',(compilation, callback) => { console.log('SetScriptTimestampPlugin! '); }); } } module.exports = SetScriptTimestampPlugin;Copy the code

Specifying the event hook function under Compiler.hooks executes the callback function when the hook is triggered. Webpack provides three ways to trigger hooks:

  • tapTo:synchronouslyTrigger hook;
  • tapAsyncTo:asynchronousTrigger hook;
  • tapPromiseTo:asynchronousTriggers the hook, which returns a Promise;

The compilation is also a different set of hook methods. Because the compilation is a SyncHook synchronized hook, the tap operation is used. The TAP method takes two parameters: the plug-in name and the callback function.

3.4 Adding a plug-in replacement entry

We basically take the template file, specify the replacement entry, and replace it with the script that needs to be executed.

So we add

<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Introduction to Webpack plug-in development</title>
</head>
<body>
  	<! -- other code -->
    <! --SetScriptTimestampPlugin inset script-->
</body>
</html>
Copy the code

3.5 Writing plug-in logic

At this point, start writing the logic for the plug-in. From the previous step, we know that the second parameter to tap is a callback function, and that the callback function has two parameters: compilation and callback.

Compilation inherits from Compiler, contains all the compiler content (as well as Webpack options), and also has plugin functions to access task points.

// SetScriptTimestampPlugin.js

class SetScriptTimestampPlugin {
  apply(compiler) {
    compiler.hooks.compilation.tap('SetScriptTimestampPlugin'.(compilation, callback) = > {
      	// The plugin logic calls the compilation plugin method
        compilation.plugin(
          "html-webpack-plugin-before-html-processing".function(htmlPluginData, callback) {
            // Read and modify the SRC list on script
            let jsScr = htmlPluginData.assets.js[0];
            htmlPluginData.assets.js = [];
            let result = `
                <script>
                    let scriptDOM = document.createElement("script");
                    let jsScr = "./${jsScr}";
                    scriptDOM.src = jsScr + "?" + new Date().getTime();
                    document.body.appendChild(scriptDOM)
                </script>
            `;
            let resultHTML = htmlPluginData.html.replace(
              "<! --SetScriptTimestampPlugin inset script-->", result
            );
            // Returns the modified resulthtmlPluginData.html = resultHTML; }); }); }}module.exports = SetScriptTimestampPlugin;
Copy the code

In the plugin logic above, we do these things:

  1. performcompilation.pluginMethod with two parameters: plug-in event and callback method.

The “plug-in events” are the events provided by the plug-in, which are used to monitor the status of the plug-in. Here are a few events provided by the HTmL-webpack-plugin (see htML-webpack-plugin for full details) : Async:

  • html-webpack-plugin-before-html-generation
  • html-webpack-plugin-before-html-processing
  • html-webpack-plugin-alter-asset-tags

Sync:

  • html-webpack-plugin-alter-chunks
  1. Get and clear the list of script file names.

In the callback method, use htmlplugindata.assets. js to get the list of script file names that need to be imported through script, copy it, and delete the original list.

  1. Write the replacement logic.

The replacement logic is to dynamically create a script tag, set its SRC value to the filename of the script read in the previous step, and concatenate the timestamp as a parameter.

  1. Insert the replacement logic.

Htmlplugindata.html can be used to get the string output of the template file. We just need to replace the entry
Replace with the replacement logic we wrote in the previous step.

  1. Returns the HTML file.

Finally, the modified HTML string is assigned to the original htmlplugindata.html to achieve the modification effect.

3.5 Using Plug-ins

Custom plugins to use, same as other plugins, instantiated in the plugins array:

// webpack.config.js

const SetScriptTimestampPlugin = require("./SetScriptTimestampPlugin.js");
module.exports = {
	/ /... Omit other configurations
  plugins: [
  	/ /... Omit other plug-ins
    new SetScriptTimestampPlugin()  
  ]
}
Copy the code

At this point, we have implemented the requirement “when the project is hosted on the CDN platform, we want to implement that the index.js in the project is not cached”.

Iv. Case expansion

Here we use the SetScriptTimestampPlugin plugin as an example to further expand.

4.1 Reading plug-in configuration parameters

Each plug-in is essentially a class, like an instantiation of a class, which can be instantiated by passing in configuration parameters that operate in the constructor:

// SetScriptTimestampPlugin.js

class SetScriptTimestampPlugin {
  constructor(options) {
    this.options = options;
  }
  apply(compiler) {
    console.log(this.options.filename); // "index.js"
    / /... Omit other code}}module.exports = SetScriptTimestampPlugin;
Copy the code

When using:

// webpack.config.js

const SetScriptTimestampPlugin = require("./SetScriptTimestampPlugin.js");
module.exports = {
	/ /... Omit other configurations
  plugins: [
  	/ /... Omit other plug-ins
    new SetScriptTimestampPlugin({
    	filename: "index.js"}})]Copy the code

4.2 Adding time stamps for Multiple script files

If we need to change the timestamp of multiple script files at the same time, we only need to adjust the parameter type and execution script. To modify the script, here is not the specific expansion, space is limited, you can think about the implementation of the ~ here is the use of plug-in parameters:

// webpack.config.js

const SetScriptTimestampPlugin = require("./SetScriptTimestampPlugin.js");
module.exports = {
	/ /... Omit other configurations
  plugins: [
  	/ /... Omit other plug-ins
    new SetScriptTimestampPlugin({
    	filename: ["index.js"."boundle.js"."pingan.js"]]}})Copy the code

Generate results:

<script src="./index.js? 1582425467655"></script>
<script src="./boundle.js? 1582425467655"></script>
<script src="./pingan.js? 1582425467655"></script>
Copy the code

Five, the summary

This article uses custom Webpack plug-ins to implement some of the trickier requirements of everyday life. This paper mainly introduces the basic composition and simple structure of Webpack plug-in, and also introduces HtmlWebpackPlugin plug-in. Based on these basic knowledge, an HTML text replacement plug-in is completed. Finally, two scenarios are used to expand the scope of the plug-in.

Finally, there is more knowledge to learn about Webpack plug-in development. It is recommended to read the official document “Writing a Plugin” to learn more.

This article is purely a summary of personal experience, if you have any objections, welcome to give advice.

Reference documentation

  1. “Writing a Plugin”
  2. HtmlWebpackPlugin – Webpack
  3. “Extending HtmlwebpackPlugin to Insert custom Scripts”