preface

This article describes the composition and writing of the WebPack plug-in, and then implements a plug-in that uploads packaged static resources to Seven Cows.

The plugin

In Webpack, plugin has more functions than Loader. Plugin can manage resources, inject global variables, compress code and so on.

Components of the plug-in:

  1. A JS function or js class
  2. Define an apply method on the prototype of the plug-in function
  3. Bind webPack’s event hooks
  4. The data in the Webpack is processed in the hook function
  5. After processing the data, you need to invoke the callback provided by WebPack
// 1. A js function or js class
class MyPlugin {
    // 2. Define a apply method on the prototype of the plugin function
    apply(compiler) {
        // 3. Bind webPack's event hooks
        compiler.hooks.emit.tapAsync(
            'MyPlugin'.(compilation, callback) = > {
                // 4. Process webpack data in the hook function
                console.log('Processing data in Webpack');
                
                // 5. After processing the data, you need to invoke the callback provided by WebPackcallback(); }); }}Copy the code

The two most important resources in plug-in development are compiler and Compilation objects.

Compiler

The Compiler module is the main engine for WebPack and extends the Tapable class to register and invoke plug-ins. Most user-facing plug-ins are registered with Compiler first.

Compiler exists for the entire life of the WebPack until the Node process is shut down.

Compilation

The Compilation module is used by Compiler to create a Compilation instance using configuration data. The Compilation instance has access to all modules and their dependencies.

Compilation also extends from the Tapable class to provide lifecycle hooks during Compilation.

The compilation instance is recreated every time code is updated or packaged.

Hook functions in Webpack

Before we write a plug-in, we need to know when our plug-in will be called. What are the hook functions available in WebPack?

  1. Compiler.hooks.com pilation: start the compiler to create a trigger after compilation object
  2. Compiler.hooks. Make: triggered when compilation officially begins
  3. Compiler.hooks. Emit: Executes before printing resources to the output directory
  4. AfterEmit: Executes after printing resources to the output directory
  5. Compiler.hooks. Done: triggered after compilation is complete

Here are just a few of them, but if you want to see the full list, check out our website.

Custom plug-in

Now that we know how to write a plug-in, let’s customize a plug-in that uploads packaged static resources to Seven Cows. Let’s start by creating a Webpack project. For those of you who don’t know how to create a webpack project, see webPack 5 for details on building a project from 0 to 1.

Create the plug-in

Then create qiniu-s-webpack-plugin.js in the root directory and add the code:

const PLUGIN_NAME = 'qiniu-s-webpack-plugin';

class QiuniuPlugin {
    apply(compiler) {
        console.log('My turn!! '); }}module.exports = QiuniuPlugin;
Copy the code

Here we can introduce the plugin and try it out:

const QiniuWebpackPlugin = require('.. /qiniu-s-webpack-plugin');

module.exports = {
    ...
    plugins: [
        new QiniuWebpackPlugin(),
    ]
}
Copy the code

Then run it and you can see that the log printed in plugin is printed on the command line.

Bind hook function

Here we need to think about what hook function to bind. The function of our plugin is to upload static resources to seven Cows after packaging. We listed several hook functions above, and two of them fit our requirements:

  1. AfterEmit: Executes after printing resources to the output directory
  2. Compiler.hooks. Done: triggered after compilation is complete

The afterEmit hook function is used in this plugin, but we need to get the output file information. We cannot get the output file information in done, so we use the afterEmit hook function.

const PLUGIN_NAME = 'qiniu-s-webpack-plugin';

class QiuniuPlugin {
    apply(compiler) {
        compiler.hooks.afterEmit.tapAsync(
            PLUGIN_NAME, 
            async (compilation, callback) => {
              const fileNameAry = Object.keys(compilation.assets);
              constbuildPath = compiler.options.output.path; callback(); }); }}module.exports = QiuniuPlugin;
Copy the code

Here we get the output from the compilation. Assets file:

{
  'main-e6f758da.js': SizeOnlySource { _size: 3873 },
  'image/icon.d144096d.jpeg': SizeOnlySource { _size: 77904 },
  'main-e6f758da.js.map': SizeOnlySource { _size: 2679 },
  'index.html': SizeOnlySource { _size: 456}}Copy the code

We can see that we output an Object whose key is our file path, and then we extract the key through object.keys.

[
    'main-e6f758da.js'.'image/icon.d144096d.jpeg'.'main-e6f758da.js.map'.'index.html'
]
Copy the code

We want to upload a file, so we must get the absolute path of the file. In the webpack configuration file we configured the output.path property, which is the absolute path to our packaged output directory. We through compilation. The options. The output. The path for the path attribute.

path = 'user/xx/xx/project/dist'
Copy the code

Then we splice the two to obtain the complete file path for subsequent upload:

class QiuniuPlugin {
    apply(compiler) {
        compiler.hooks.afterEmit.tapAsync(
            PLUGIN_NAME, 
            async (compilation, callback) => {
              const fileNameAry = Object.keys(compilation.assets);
              const buildPath = compiler.options.output.path;
              const filePathAry = fileNameAry.map((filename) = > `${buildPath}/${filename}`); callback(); }); }}Copy the code

Upload to seven cows

Nodejs NPM package:

npm install qiniu
Copy the code

Then we create a new file qiniu.js that encapsulates the upload method of seven cows:

const qiniu = require('qiniu');

class Qiniu {
  options = {
    accessKey: ' '.secretKey: ' '.bucket: ' '};constructor(options) {
    const {accessKey, secretKey, bucket} = options;
    const mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
    const _options = {
      scope: bucket,
    };
    const putPolicy = new qiniu.rs.PutPolicy(_options);
    const config = new qiniu.conf.Config();
    this.options = options;
    this.uploadToken=putPolicy.uploadToken(mac);
    this.formUploader = new qiniu.form_up.FormUploader(config);
  }

  putFile(filePath) {
    const putExtra = new qiniu.form_up.PutExtra();

    return new Promise((resolve, reject) = > {
      this.formUploader.putFile(this.uploadToken, null, filePath, putExtra, function (respErr, respBody, respInfo) {
        if (respErr) {
          throw respErr;
        }
        if (respInfo.statusCode == 200) {
          resolve();
        } else {
          console.log(respInfo.statusCode);
          console.log(respBody); reject(respBody); }}); }); }}module.exports = Qiniu;
Copy the code

Seven cow operation files must be uploaded with three parameters:

  1. AccessKey: Obtained in Personal Center -> Key Management
  2. SecretKey: Obtain it in Personal Center -> Key Management
  3. Bucket: indicates the space name

We then pass these three parameters in the plugin’s constructor, instantiate Qiniu, and call the upload file method:

const Qiniu = require('./qiniu');

class QiuniuPlugin {
    constructor(options) {
        this.qiniu = new Qiniu(options);
    }
  
    apply(compiler) {
        compiler.hooks.afterEmit.tapAsync(
            PLUGIN_NAME, 
            async (compilation, callback) => {
              const fileNameAry = Object.keys(compilation.assets);
              const buildPath = compiler.options.output.path;
              const filePathAry = fileNameAry.map((filename) = > `${buildPath}/${filename}`);
              
              // Upload the file
              filePathAry.forEach(async (filePath) => {
                  await this.qiniu.putFile(filePath); }); callback(); }); }}Copy the code

At this point the plug-in is complete.

conclusion

The WebPack plug-in itself is a constructor that needs to define the Apply method within the function. When WebPack is initialized, the plug-in’s Apply method is called, passing in the Compiler object through which the plug-in registers the hook function. Webpack calls the corresponding hook functions at various stages of compilation to call the plug-in to process webPack’s internal data. During the compilation process, the Compilation object can be accessed from which all modules, their dependencies, and configured properties can be accessed. Finally, you need to call the webPack callback to let the rest of the hook functions continue.

Click here to view the source code.