This is the 13th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

Loader

A loader is essentially a JavaScript module exported as a function. The Loader Runner calls this function and passes in the results or resource files generated by the previous loader. This function cannot be an arrow function because this in the function is populated by webpack as a context, and loader Runner contains some useful methods.

A file of the same type can have multiple Loaders, and the start loader has only one input parameter: the content of the resource file. Compiler expects the result of the processing produced by the last loader. The result should be of type String or Buffer (which can be converted to String), representing the module’s JavaScript source code.

For example, write the following demo:

// index.js
console.log('xiong ling');
Copy the code
// webpack.config.js
const path = require('path');
const webpack = require("webpack");

module.exports = {
    mode: 'development',
    entry: {
        index: './src/index.js',
    },
    devtool: 'inline-source-map',
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'dist'),
        clean: true,
    },
    resolveLoader: {
        modules: ['node_modules', './src/loaders']
    },
    module: {
        rules: [
            {
                test: /.js$/i,
                use: ['loader'],
            }
        ]
    }
};
Copy the code

ResolveLoader means to tell Webpack where to find the loader and execute it from left to right. There must be more than our custom loader in our project, so we need to add node_modules. When we meet the end of the.js file, we will use our custom loader to deal with it. Look under./ SRC /loaders. So we also need to see a./ SRC /loaders/loader.js file to write our custom loader code

// ./src/loaders/loader.js
module.exports = function (source) {
    return source.replace('xiong ling', "hello world");
}
Copy the code

In this function, we take a source argument, which is our source code (String), and we replace it. It’s as simple as that. This is the synchronized loader. Let’s try NPM run build and our content is successfully replaced.

We know that in the Webpack configuration, we can pass in the options configuration, pass some parameters into our loader, so how do we receive? To change the example again, modify the webpack.config.js configuration as follows

// webpack.config.js

module.exports = {
    module: {
        rules: [
            {
                test: /.js$/i,
                use: [
                    {
                        loader: 'loader',
                        options: {
                            name: "hello world --- options"
                        }
                    }
                ],
            }
        ]
    }
};
Copy the code

We added the options parameter here, how to get it in the loader.

// ./src/loaders/loader.js
module.exports = function (source) {
    const options = this.getOptions();
    return source.replace('xiong ling', options.name);
}
Copy the code

As of Webpack 5, this.getOptions can get loader context objects. It replaces the getOptions method from loader-utils. Loader-utils library. Once packaged, our code will also be changed to console.log(‘hello world — options — resolveLoader’);

What if we have some asynchronous operations? Or just return? If the loader does not return something, you can try it. Now let’s go ahead and modify the code

// ./src/loaders/loader.js module.exports = function (source) { const options = this.getOptions(); const callback = this.async() setTimeout(() => { const result = source.replace('xiong ling', options.name); callback(null, result) // return source.replace('xiong ling', options.name); }}, 1000)Copy the code

We can’t return directly in setTimeout, it will give an error, so we need to use async, which tells loader-runner that the loader will call back asynchronously. Return this. The callback. Webpack waits for the timer to complete and then calls the callback function to return the result.

At this point, our simple loader has been implemented, it can also be used for internationalization and other operations. Let’s look at how plugin, another important concept in WebPack, is implemented.

Plugin

Plug-ins are also an important part of the WebPack ecosystem and provide a powerful way to directly touch the WebPack compilation process. The plug-in can hook into key events issued during each compilation. At each stage of compilation, the plug-in has full access to the Compiler object and, when appropriate, to the current compilation object.

  • CompilerModules are the main engine of Webpack, which passesCLIorNode APIAll the options passed in create a Compilation instance that contains all the configuration information for the Webpack environment.
  • CompilationModule will beCompilerUsed to create a new compilation object (or a new build object).compilationInstances can access all modules and their dependencies (mostly circular dependencies), thisObject contains the current module resources, build resources, changing files, and so on.

Create the plug-in

What are the components of a WebPack plug-in? How do we use it when we use it? New XXXPlugin(); new XXXPlugin();

  • A JavaScript named function or JavaScript class.
  • Define one on the prototype of the plug-in functionapplyMethods.
  • inapplyWe can bind a webPack time hook to the webPack time hook, and then execute our requirements in the hook

The Apply method 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.

Next we can use an example to write our plugin. In the case of filelist. md, the requirement is that every time webpack is packed, a list of packed files is automatically generated, which is essentially a Markdown file that contains information about all the files in the packed folder Dist.

Analysis steps:

  1. To generate amarkdownFile, how to define the name of the file, do you need to pass in the parameters
  2. We’re doing our operations in that hook
  3. How did you make usmarkdownfile
  4. What kind ofmarkdownfile

So our code can be written as follows:

class FileList { static defaultOptions = { outputFile: 'assets.md', }; Constructor (options = {}) {// You can receive custom options, such as file names, to merge this.options = {... FileList.defaultOptions, ... options }; } apply(compiler) {// Execute in the emit hook, which is async, so we need to use tapAsync to register. And must call cb function compiler. Hooks. Emit. TapAsync (' FileList '(compilation, cb) = > {const fileListName = this. Options. The outputFile;  Let len = object.keys (compilation.assets).length; // let content = '# ${len} \n\n'; // Go through the resource file, For (let filename in compilation. Assets) {content += '- ${filename}\n'} // Add a new file compilation.assets[fileListName] = {// Compilation. assets function () { return content; }, // Size: function () {return content.length; } } cb() }) } } module.exports = FileList;Copy the code

So far we have a plug-in written, you can test it.

As a bonus, asynchronous hooks need to be registered with tapAsync, while synchronous hooks need to be registered with TAP and do not need to call cb. Whether the hook is synchronous or asynchronous depends on the webpack description.