What is the Loader?

Loader Loader Loader Loader Loader Loader Loader Loader Loader Loader Loader Loader Loader Module converter, that is, to install the content of the module into the new content according to the requirements, and each Loader has a single responsibility, only one conversion will be completed, so we generally process the source file, but also by a number of Loaders to perform the chain sequence to install and replace for many times, and then get the result we want.

Loader is a node.js module. This module exports a function (meaning that all node.js apis are available to us), as follows:

    module.exports = function (source) {
        // Do a series of conversions to source
        return source;
    }
Copy the code

Let’s introduce what API webpack provides for Loader to call, have a deep understanding of Loader, and then analyze the source code of Babel-loader, see how we commonly used Loader is written.

Get Loader options

    const loaderUtils = require('loader-utils');
    module.exports = function(source) {
        // Get the options passed in by the user for the current Loader
        console.log(loaderUtils.getOptions(this));
        return source;
    }
Copy the code

Return other results

As shown above, we return the transformed content, but in some cases, we need to return not only the transformed content, but also something else, such as the sourceMap or AST syntax tree. Then we can use the apithis. callback provided by WebPack. When this. Callback is used, we must return undefined in the Loader function to let WebPack know the result returned.

    this.callback(
        // Error when the original content cannot be installed
        err: Error || null.// Install the changed content, such as the above source
        content: string | Buffer,
        // The Source Map is used to derive the original content from the loaded content, which is convenient for debugging
        // We learned that SourceMap is only used in the development environment, so it becomes controllable,
        // Webpack also provides this.sourceMap to tell if you need to use sourceMap,
        // You can also use the loader option to determine, such as CSS-loadersourceMap? : SourceMap,// If the ast syntax tree is generated at the same time, the AST can also be returned to facilitate subsequent loader reuse of the AST, which can improve performance
        abstractSyntaxTree? AST
    );
Copy the code

Synchronous and asynchronous

AsyncAPI asyncAPI asyncAPI asyncAPI asyncAPI asyncAPI

    module.exports = async function (source) {
        const callback = this.async();
        const { err, content, sourceMap, AST } = await Func();
        callback(err, content, sourceMap, AST); // The same as the appeal 'this.callback' argument
    }
Copy the code

Working with binary data

If a loader such as file-loader processes binary data, you need to tell WebPack to pass binary data to loader.

    module.exports = function(source) {
        if (source instanceof Buffer) {
            // a series of operations
            return source; // Of course I can also return binary data for the next loader
        }
    }
    moudle.exports.raw = true; // If you do not set it, you will get the string
Copy the code

Pass moudle.exports. Raw = true; Tell WebPack that it needs binary data for itself.

Cache acceleration

This. Cacheable (Boolen), Boolen, Boolen, Boolen, Boolen, Boolen, Boolen

Other apis

Speaking of learning, of course, the more system the better, MORE API introduction, in addition to the above commonly used API, there are also the following commonly used API.

  • this.context: Directory where the file currently being converted resides
  • this.resource: Full request path to the file currently processing the transformation, including QueryString
  • this.resourcePath: Path to the file currently being converted
  • this.resourceQuery: QueryString for the file currently being processed
  • this.target: Target of the WebPack configuration
  • this.loadMoudleLoadMoudle (Request: string, callback: this. LoadMoudle (request: string, callback: Function (err, source, sourceMap, module)) to get the result of processing dependent files.
  • this.resolveResolve (context: string, request: string, callback: function(err, result: string))
  • this.addDependencyThis.adddependency (file: string) : Adds a dependency file to the currently working file so that Loader can be called to convert the dependency file if it changes.
  • this.addContextDependency: add dependent file directory for the current processing, to rely on the files in the directory changes to invoke the Loader convert this file, enclosing addContextDependency (dir: string)
  • this.clearDependencies: Clears all dependencies for the file currently being processed
  • this.emitFile: output to a file, use the method of enclosing emitFile (name: string, content: Buffer | string, sourceMap: {… })

Babel-loader source code brief analysis

The first line of the source code reads as follows:

    let babel;
    try {
        babel = require("@babel/core");
    } catch (err) {
        if (err.code === "MODULE_NOT_FOUND") {
            err.message +=
            "\n babel-loader@8 requires Babel 7.x (the package '@babel/core'). " +
            "If you'd like to use Babel 6.x ('babel-core'), you should install 'babel-loader@7'.";
        }
        throw err;
    }
Copy the code

Babel-loader relies on @babel/core, which is why @babel/core needs to be installed as well (babel-plugin-transform-Runtime, babe preset) L-runtime). The SRC /index.js file is organized in the same way that we used to write the Loader.

/ / introduction package. Json
const pkg = require(".. /package.json");
/* If babel-loader is configured with cacheDirectory, babel-loader will cache the result of loader's execution. If true, babel-loader will use cache. The 'cache.js' file has read, write, filename, and handleCache methods for handling the cache. If you're interested, check it out. * /
const cache = require("./cache");
/* transfrom.js is used to transform content, and the Babel. Transform method is called internally. Babylon parses the ES6 / ES7 code into ast, babel-traverse interprets the AST, and the new AST is converted to ES5 via babel-Generator. Core method in @ Babel/core/lib/transformation/index of js ` runSync ` method, are interested can go to look at. * /
const transform = require("./transform");
const injectCaller = require("./injectCaller");
const path = require("path");
// Get Loader parameters options
const loaderUtils = require("loader-utils");

module.exports = makeLoader();
module.exports.custom = makeLoader;

function makeLoader(callback) {
  const overrides = callback ? callback(babel) : undefined;

  return function(source, inputSourceMap) {
    // This is an asynchronous Loader that performs an asynchronous reload
    const callback = this.async();

    loader
      .call(this, source, inputSourceMap, overrides)
      .then(args= > callback(null. args), err => callback(err)); }; }async function loader(source, inputSourceMap, overrides) {... }Copy the code

Module.exports = makeLoader(); Const callback = this.async(); const callback = this.async(); This is an asynchronous loader. It is not difficult to see the most important implementation in this step. Before reading the source code, it is best to take a look at the README of babel-Loader to get a basic understanding.

The loader(source, inputSourceMap, overrides) function has three input parameters: source=> code to be converted, inputSourceMap=> sourceMap processed by the previous loader. The whole block of source code can be divided into several parts.

  • let loaderOptions = loaderUtils.getOptions(this) || {};, gets options, and the path to the file currently processing the transformationthis.resourcePath.
  • To determine whether to customize the loader transformation, a series of judgments will be performed on options.customize, options.customize a relative path, and the loader function parameter overrides is nulllet override = require(loaderOptions.customize);, override will intervene in subsequent logic (such as transformation and option acquisition).
  • Merge function incoming arguments with LoaderOptions to give programmaticOptions.
  • callbabel.loadPartialConfigYou can take the Babel configuration and assign it to the config variable, essentially allowing the system to easily manipulate and validate the user’s configuration. This functionality resolves plugins and presets
  • Generate cacheIdentifier
  • Check whether options.cacheDirectory needs to cache the Loader conversion. If true, call the module.export cache method of cache.js (described above).
  • config.babelrcIf it is not empty, there is a.babelrc file, which depends on the.babelrc filethis.addDependency(config.babelrc);
  • MetadataSubscribers subscription metadata, which is used to subscribe to build metadata that will be added to the context of the Webpack. This is not usually used, but is probably used in some babel-plugins.
  • Finally, the processed result is returned

summary

The return value of each Loader is a Function, and the converted content is passed in to get the converted content. This article first introduces the basic concepts of Loader. And understand webpack for Loader to provide some common API, finally through the analysis of babel-loader source code, I think I should almost know how to write a simple Loader. My blog