Learn more about Webpack - writing loader

Posted by Pryach on Wed, 09 Feb 2022 02:46:48 +0100

How to write a Loader

Official documentation:

Loader is just a JavaScript module exported as a function. This function is called when the loader converts resources. The given function will call loader API And accessed through this context. loader runner This function will be called, and then the result generated by the previous loader or resource file will be passed in. The {this} context of the function will be populated by webpack.

Note: loader is a function and cannot be written as arrow function module Exports = () = > {}, and use declarative module Exports = function() {}, otherwise there will be a problem with this pointing

Simple case: write a loader implementation to replace the world with camille:

//1,index.js: 
console.log('hello world');

//2,webpack.common.js:
module: {
      rules: [
          {
              test: /\.js$/,
              //include: path.resolve(__dirname, '../src'),
              loader: path.resolve(__dirname, '../loaders/replaceLoader.js')
          }
     ]
}

//3,replaceLoader.js
module.exports = function(source) {  //Source the source code (content) of the import file
    console.log('-------', source);
    return source.replace('world', 'camille');   //return source;
}

The output is as follows:

1. loader add configuration

There are two ways to get the configuration:

1)this.query: the options are configured when the loader is used, and the processed loader passes this Query gets the options object (note that the loader definition above uses the functional expression).

  1. If this loader is configured options Object, this Query , points to this option object.
  2. If there are no {options in the loader, but the query string is used as a parameter, this Query is an example of? String that starts with.

2) Loader utils: except this Query gets the options object. The official also recommends using the getOptions method provided in loader utils to extract the options of a given loader.

//1,webpack.commmon.js: 
{
    test: /\.js$/,
    loader: path.resolve(__dirname, '../loaders/replaceLoader.js'),
    options: {
        name: "Camille"
    }
}

//2,replaceLoader.js
//1)this.query
module.exports = function(source) {
    console.log('-------', this.query);
    return source.replace('world', this.query.name);
}

//2),loader-utils
//npm install loader-utils --save-dev
const loaderUtils = require("loader-utils");
module.exports = function(source) {
    const options = loaderUtils.getOptions(this);
    console.log('-------', options);

    return source.replace('world', options.name);
}

The output is as follows:

2,this.callback: a function that can be called synchronously or asynchronously and can return multiple results.

return only one resource can be returned. Sometimes sourcemap or other information may be returned together. That is, a single processing result can be returned directly in synchronization mode. If this is called, there must be more than one result callback().

this.callback(
  err: Error | null,   //The first parameter must be Error or null
  content: string | Buffer,  //The second parameter is a string or Buffer.
  sourceMap?: SourceMap,  //Optional: the third parameter must be a source map that can be parsed by this module
  meta?: any  //Optional: the fourth option, which will be ignored by webpack, can be anything (such as some metadata)
)

module.exports = function(content, map, meta) {
  this.callback(null, someSyncOperation(content), map, meta);
  return; // undefined is always returned when callback() is called
};

Modify the above case:

//replaceLoader.js: 
const loaderUtils = require("loader-utils");
module.exports = function(source) {
    const options = loaderUtils.getOptions(this);
    const result = source.replace('world', options.name);
    
    console.log("-----result-----", result);

    this.callback(null, result);  // return source.replace('world', options.name);
}

3,this.async

Modify the above case and rename it replaceloaderasync js:

//replaceLoaderAsync.js:
const loaderUtils = require("loader-utils");
module.exports = function(source) {
    const options = loaderUtils.getOptions(this);
    setTimeout(() => {
        const result = source.replace('dell', options.name);
        this.callback(null, result);  //return result;
    }, 1000)
}

Direct error reporting of operation results:

Solution: in asynchronous mode, you must call {this Async() to indicate loader runner Wait for the asynchronous result and it will return this Callback() callback function -- using this.async To get the callback function.

this.async: tell loader-runner The loader will call back asynchronously. Return this callback.

const loaderUtils = require("loader-utils");
module.exports = function(source) {
    const options = loaderUtils.getOptions(this);
    const callback = this.async();
    setTimeout(() => {
        const result = source.replace('world', options.name);
        console.log("-----result-----", result);

        callback(null, result);  
    }, 1000)
}

The above is only 1s, and the packing time is as follows:

Modify the asynchronous time above to 5000, and the packaging time is as follows:

Almost five times, that is, use this Async() will indicate loader runner Wait for asynchronous results.

 

4. Multiple loader order problems:

Use the above two loaders at the same time: synchronous and asynchronous loaders

use: [
          {
            loader: path.resolve(__dirname, "../loaders/replaceLoader.js"),
            options: {
              name: "Camille"
            }
          },
          {
            loader: path.resolve(__dirname, "../loaders/replaceLoaderAsync.js"),
            options: {
              name: "Vina"
            }
          }
]

The final output is as follows: after the asynchronous loader completes processing, the processed result is handed over to the synchronous loader, and the receiving source of the synchronous loader is the one processed by the previous loader rather than the original one

Optimization: the path introduced by the above loader is repeated in many places, and multiple loaders are matched (tested). You can use {resolveloader Modules} configuration, webpack will search these directories for these loaders.

module.exports = {
    resolveLoader: {
      modules: ["node_modules", "./loaders"]  //Note: priority from front to back
    },
      module: {
        rules: [
          {
            test: /\.js$/,
            include: path.resolve(__dirname, "../src"),
            use: [
              {
                loader: "replaceLoader",
                options: {
                  name: "camille"
                }
              },
              {
                loader: "replaceLoaderAsync",
                options: {
                  name: "vina"
                }
              }
            ]
          }
        ]
      }
}

5. Custom loader usage scenario:

1. If you encounter an error in the try function, you can add the following code to the try function in the business. If you do not need to modify the catch code, you can add the following code to the try function

module.exports = function () {
  try {
    function func() {
      /**.... */
    }
  } catch (e) {}
};

2. Switch between Chinese and English. Obtain the global variable to obtain the language as follows, so as to replace the placeholder with the corresponding content:

Topics: Front-end Webpack