How does webpack customize a loader?

In the development process of modern front-end, webpack is often used to package the code in a modular way. Usually, we directly use the configuration of webpack and the loader written by others. If you want to implement a loader, what do you need to do?

1. What is a loader?

Webpack is a modular packaging tool based on node. It can only handle JS and JSON files, and has no ability to handle CSS, pictures and other format files. Loader is equivalent to a translator that translates these files into a format that webpack can handle. In other words, loader gives webpack the ability to handle other files.

2. Use of loader

module.exports = {
  //...
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: ['style-loader', 'css-loader', {
            loader: 'sass-loader',
            options: {
                //...
            }
        }]
      }
    ]
  }
};
  • Single responsibility: each loader only completes one conversion. For example, sass loader only talks about converting sass into css.
  • Chain call: the first loader receives the contents of the resource file, and the subsequent loaders receive the processing results returned by the previous loader.
  • Call order: there is a pitch attribute in the loader. The call order is style loader (pitch), CSS loader (pitch), sass loader (pitch), sass loader, CSS loader and style loader. If one of the pitches returns a value, the subsequent call is stopped. To put it simply, first execute the pitch from left to right, and then execute the loader from right to left.

3. A basic loader

It's a mess of things written on it. It looks really boring. Now write an example that will be written when talking about custom loader, which is the same as writing hello world before writing code.

A replacement loader. Replace the NAME in the original JS code.
webpack.config.js

const path = require('path');
// Let's see how to use it first
module.exports = {
    //...
    module: {
        rules: [
            { 
                test: /\.js$/, 
                use: [{ 
                    // Local reference loader
                    loader: path.resolve('./replace-loader'),
                    options: {
                        // Replace the NAME with wei by configuring the incoming words
                        words: 'wei'
                    }
                }]
            }
        ]
    }
};

replace-loader.js

// Loader utils are some tool functions specially used to customize the loader
const { getOptions } = require('loader-utils');

module.exports = function(source) {
    const options = getOptions(this); // getOptions is used to get the configuration

    return source.replace(/NAME/g,     options.words);
}

As above, a simple loader is completed.

3. mini style loader

Just write a replace loader as if I didn't write anything. How can I write a loader without a todo list.

const { stringifyRequest, getOptions } = require('loader-utils');

function loader(source) {}

// The reason for using pitch here is that if you call style loader after CSS loader calls in the normal order, the style loader will receive a pile of code strings
// To avoid this problem, you need to execute style loader before CSS loader
loader.pitch = function(remainingRequest, precedingRequest, data) {
    const options = getOptions(this);

    // Use JSON string attributes
    // Concatenate strings as variables
    const attributes = JSON.stringify(options.attributes || {});
    const insert = options.insert === undefined 
                    ? '"head"' 
                    : typeof options.insert === 'string' ? JSON.stringify(options.insert) : options.insert.toString();

    // Standardization path
    const request = stringifyRequest(this, '!!' + remainingRequest);

    return `
        var style = document.createElement('style');
        var content = require(${ request }); // It is equivalent to require (CSS loader! Resource), and the returned content is the content processed by CSS loader
        var attributes = ${ attributes };
        var insert = ${ insert };
        // Traversal setting properties
        for (var key in attributes) {
            style.setAttribute(key, attributes[key]);
        }
        content = content.__esModule ? content.default : content;
        style.innerHTML = content;
        var insertElement;
        if (typeof insert === 'string') {
            var insertElement = document.querySelector(insert);
            insertElement && insertElement.appendChild(style);
        } else {
            insert(style);
        }
    `;
}

module.exports = loader;

4. Sass loader of mini version

const sass = require('node-sass');
const { getOptions } = require('loader-utils');

module.exports = function(source) {
    const options = getOptions(this);

    const sassOptions = options.sassOptions || {};

    const result = sass.renderSync({
        data: source,
        ...sassOptions
    });

    return result.css;
};

There are many knowledge related to loader, such as asynchrony (this.async()), processing binary data (loader.raw = true), disabling caching (this.cacheable(false)), and so on. You can refer to relevant documents during actual development.

more:

Tags: Webpack loader

Posted by sgoldenb on Wed, 18 May 2022 09:37:38 +0300