Complete high-frequency question bank warehouse address: github.com/hzfe/awesom…

The full frequency question bank reading address: febook.hzfe.org/

Issues related to

  • How does webPack Loader work
  • How to write a WebPack Loader

Answer key points

Transform the life cycle chunk

Webpack itself can only handle JavaScript and JSON files, and Loader adds the ability to handle other types of files to WebPack. Loader converts other types of files into valid Webpack modules (such as ESmodule, CommonJS, AMD) that WebPack can consume and add to the dependency diagram.

Loader is essentially a function that converts the received content and returns the converted result.

Common loaders are:

  • Raw-loader: loads the original content of the file.
  • File-loader: Outputs a reference file to a destination folder, referencing the output file through a relative path in the code.
  • Url-loader: Similar to file-loader, but can inject file content into code base64 if the file is too small.
  • Babel-loader: Converts the ES newer syntax into a browser-compatible syntax.
  • Style-loader: Inserts CSS code into JavaScript and loads CSS through DOM manipulation.
  • Css-loader: loads the CSS and supports features such as modularization, compression, and file import.

There are two ways to use loader:

  1. Configured in the webpack.config.js file, specify the loader to use by using test in module.rules to match the type of file to convert.
module.exports = {
  module: {
    rules: [{ test: /.ts$/, use: "ts-loader" }],
  },
};
Copy the code
  1. inline
import Styles from "style-loader! css-loader? modules! ./styles.css";Copy the code

Knowledge depth

1. Write webPack Loader

1.1 synchronous loader

After synchronizing the transformation, the result can be returned either by return or by calling this.callback.

export default function loader(content, map, meta) {
  return someSyncOperation(content);
}
Copy the code

This. Callback can return information other than content (such as sourcemap).

export default function loader(content, map, meta) { this.callback(null, someSyncOperation(content), map, meta); return; // Always return undefined when callback() is calledCopy the code

1.2 the asynchronous loader

This.async lets you get the callback for an asynchronous operation and return the result in the callback.

export default function (content, map, meta) {
  const callback = this.async();
  someAsyncOperation(content, (err, result, sourceMaps, meta) => {
    if (err) return callback(err);
    callback(null, result, sourceMaps, meta);
  });
}
Copy the code

Unless calculations are small, use asynchronous Loaders whenever possible for single-threaded environments like Node.js.

1.3 Loader Development auxiliary tools and loaderContext

Loader-utils and schema-utils simplify getting and validating parameters passed to the loader.

import { getOptions } from "loader-utils";
import { validate } from "schema-utils";

const schema = {
  type: "object",
  properties: {
    test: {
      type: "string",
    },
  },
};

export default function (source) {
  const options = getOptions(this);

  validate(schema, options, {
    name: "Example Loader",
    baseDataPath: "options",
  });

  // Apply some transformations to the source...

  return `export default ${JSON.stringify(source)}`;
}
Copy the code

Loader-utils includes the following tools:

  • ParseQuery: Parses the Query parameter of the Loader and returns an object.

  • StringifyRequest: Converts the requested resource into a relative path string that can be used by require or import in loader-generated code, while avoiding absolute paths that cause hash recalculations.

    loaderUtils.stringifyRequest(this, "./test.js");
    // ""./test.js""
    Copy the code
  • UrlToRequest: Converts the requested resource path into a form that WebPack can handle.

    const url = "~path/to/module.js";
    const request = loaderUtils.urlToRequest(url); // "path/to/module.js"
    Copy the code
  • InterpolateName: Interpolates filename templates.

    // loaderContext.resourcePath = "/absolute/path/to/app/js/hzfe.js"
    loaderUtils.interpolateName(loaderContext, "js/[hash].script.[ext]", { content: ... });
    // => js/9473fdd0d880a43c21b7778d34872157.script.js
    Copy the code
  • GetHashDigest: obtains the hash value of the file content.

In the process of writing a Loader, you can also use the loaderContext object to obtain loader information and perform some advanced operations. The common properties and methods are:

  • this.addDependency: Adds a file as a dependency on the results produced by the Loader so that it can be listened for if any changes occur, triggering a recompile.
  • this.async: tells loader-runner that the loader will execute the callback asynchronously.
  • this.cacheable: By default, the result of loader processing is marked as cacheable. Passing false disables the caching capability of the Loader to process results.
  • this.fs: The inputFileSystem property used to access the compilation.
  • this.getOptions: Extracts loader configuration options. Starting with WebPack 5, you can get loader context objects to be used insteadloader-utilsThe getOptions method in.
  • this.mode: Webpack run mode, can be “development” or “production”.
  • this.query: Points to the options object if the Loader has configured it. If the loader does not have options and takes a query string as an argument, query is an argument?String at the beginning.

2. Working mechanism of WebPack Loader

2.1 Parsing Loader loading rules based on Module. rules

When Webpack processes a module, it uses Loader to process corresponding resources according to module.rules in the configuration file to obtain JavaScript modules available for Webpack.

Loaders have different types according to the configuration, and the execution sequence of loaders may be affected. The specific types are as follows:

Rules: [// pre pre loader {enforce: "pre", test: /.js$/, loader: "eslint-loader"}, // normal loader {test: /.js$/, loader: "babel-loader"}, // post post-loader {enforce: "post", test: /.js$/, loader: "eslint-loader"},];Copy the code

And inline loader for inline use:

import "style-loader! css-loader! sass-loader! ./hzfe.scss";Copy the code

In the normal execution process, the execution order of these different loader types is: pre -> normal -> inline -> POST. In the pitch process mentioned in the next section, these loaders are executed in reverse order: post -> inline -> Normal -> pre.

For inline loaders, you can notify the modifier prefix to change the execution order of the Loader:

/ /! Normal loader import {HZFE} from "! ./hzfe.js"; / / -! {HZFE} from "-! ./hzfe.js"; / /!!!!! Prefix disables pre, normal and post loader import {HZFE} from "!! ./hzfe.js";Copy the code

In general,! The prefix and inline loader are used together only in code generated by loaders such as style-loader. Webpack officially does not recommend that users use inline Loader and! Prefix.

Loaders configured in Webpack Rules can be multiple in series. In a normal process, the chain loader is executed from back to front.

  • The last loader executes first and receives the contents of the resource file.
  • The first loader executes last, returning JavaScript modules and an optional Source map.
  • The loader in the middle has no specific requirements for receiving and returning, as long as it can process the content returned by the previous loader and produce the content that the next loader can understand.

2.2 Loader-Runner Execution process

Webpack calls loader after the Compilation buildModule hook is triggered. Webpack will call runLoaders to run loader in normalModule. js:

RunLoaders ({resource: this.resource, // The path to the resource file, which can have a query string. Such as: '. / test. TXT? Query 'loaders: this. Loaders, // Loader path. Context: loaderContext, // The context passed to loader processResource: (loaderContext, resourcePath, callback) => {// Get resources by readResourceForScheme for files with Scheme, otherwise by fs.readfile. const resource = loaderContext.resource; const scheme = getScheme(resource); if (scheme) { hooks.readResourceForScheme .for(scheme) .callAsync(resource, this, (err, result) => { // ... return callback(null, result); }); } else { loaderContext.addDependency(resourcePath); fs.readFile(resourcePath, callback); }},}, (err, result) => {// When loader conversion is complete, the result will be returned to the Webpack to continue processing. processResult(err, result.result); });Copy the code

The runLoaders function comes from the loader-Runner package. Before introducing the specific process of runLoaders, let’s first introduce the pitch phase, the process of executing loader from back to front described in the previous section, which is generally called the Normal phase. In contrast, there is a process called the pitch stage.

A loader that hangs a method on the pitch property of an exported function will execute the method in pitch phase. The whole process is similar to the browser event model or onion model. The Loader is executed from front to back in pitch phase and then from back to front in Normal phase. Note that the pitch phase generally does not return a value. Once the Pitch phase has a loader return value, the normal phase is executed backwards from here.

Loader-runner process is as follows:

  1. Process the context received from Webpack and continue adding the necessary attributes and helper methods.

  2. IteratePitchingLoaders process pitch Loader.

    If we configure three Loaders to a module, each loader is configured with the pitch function:

    module.exports = {
      //...
      module: {
        rules: [
          {
            //...
            use: ["a-loader", "b-loader", "c-loader"],
          },
        ],
      },
    };
    Copy the code

    The process for handling this Module is as follows:

    |- a-loader `pitch` |- b-loader `pitch` |- c-loader `pitch` |- requested module is picked up as a dependency |- c-loader  normal execution |- b-loader normal execution |- a-loader normal executionCopy the code

    If b-loader returns a value in pitch ahead of time, the flow is as follows:

    |- a-loader `pitch`
      |- b-loader `pitch` returns a module
    |- a-loader normal execution
    Copy the code
  3. IterateNormalLoaders Processes the NormalLoader.

    After the process of PITCH Loader is finished, the process of Normal Loader is started. The process for working with a Normal Loader is similar to pitch Loader, except that it iterates back to front.

    Both iterateNormalLoaders and iteratePitchingLoaders call runSyncOrAsync to execute the loader. RunSyncOrAsync provides context.async, which is an async function that returns callback for asynchronous processing.

3. Common WebPack Loader principles

The loader itself is a function that converts other resources to JavaScript modules.

3.1 raw – loader analysis

The loader is a synchronization loader with very simple functions. Its core steps are to obtain serialized strings from the original content of files, fix the bug of JSON serialization of special characters, add export statements, and make it a JavaScript module.

This loader is deprecated in WebPack 5 and can be replaced with asset Modules. The loader source is as follows:

import { getOptions } from "loader-utils"; import { validate } from "schema-utils"; import schema from "./options.json"; export default function rawLoader(source) { const options = getOptions(this); validate(schema, options, { name: "Raw Loader", baseDataPath: "options", }); const json = JSON.stringify(source) .replace(/\u2028/g, "\u2028") .replace(/\u2029/g, "\u2029"); const esModule = typeof options.esModule ! == "undefined" ? options.esModule : true; return `${esModule ? "export default" : "module.exports ="} ${json}; `; }Copy the code

3.2 the Babel – loader analysis

The Babel loader is a hybrid synchronous and asynchronous loader that runs in asynchronous mode when using a cache configuration and synchronously otherwise. The loader’s main source code is as follows:

// imports ... / /... const transpile = function (source, options) { // ... let result; try { result = babel.transform(source, options); } catch (error) { // ... } / /... return { code: code, map: map, metadata: metadata, }; }; / /... module.exports = function (source, inputSourceMap) { // ... if (cacheDirectory) { const callback = this.async(); return cache( { directory: cacheDirectory, identifier: cacheIdentifier, source: source, options: options, transform: transpile, }, (err, { code, map, metadata } = {}) => { if (err) return callback(err); metadataSubscribers.forEach((s) => passMetadata(s, this, metadata)); return callback(null, code, map); }); } const { code, map, metadata } = transpile(source, options); this.callback(null, code, map); };Copy the code

Babel-loader passes the babel.transform code and source map through callback.

3.3 Style-loader and CSS-Loader Analysis

Style-loader is responsible for inserting the style into the DOM to make it work on the page. Css-loader handles external references such as import and URL paths.

Style-loader has only pitch functions. Css-loader is a Normal Module. The style-loader phase is executed first, and the style-loader phase is created. ./hzfe.css) is returned to webpack. Webpack will call CSS-loader again to process the style, and CSS-Loader will return the JS module containing the Runtime to WebPack for parsing. Style-loader injects require(!! ./hzfe.css), and also injected the code to add the style tag. This way, at runtime (in the browser), style-loader can insert csS-Loader styles into the page.

A common question is why style-loader and CSS-loader are not organized in normal mode.

First csS-loader returns code that looks like this:

import ___CSS_LOADER_API_IMPORT___ from ".. / node_modules / _css - [email protected] @ CSS - loader/dist/runtime API. Js "; var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(function (i) { return i[1]; }); // Module ___CSS_LOADER_EXPORT___.push([ module.id, ".hzfe{\r\n height: 100px;\r\n}", "", ]); // Exports export default ___CSS_LOADER_EXPORT___;Copy the code

Style-loader cannot obtain CSS-related content at compile time, because style-loader cannot handle runtime dependencies of csS-Loader generated results. Style-loader also cannot fetch CSS-related content at runtime, because no matter how the runtime code is concatenated, the CSS content cannot be retrieved.

As an alternative, style-loader adopts pitch scheme. The core functions of style-Loader are as follows:

Module.exports. pitch = function (request) {var result = [// generate require CSS file, Give csS-loader to parse to get JS module containing CSS content // which!! Is to avoid webpack parsing the recursive call style - loader ` var content = the require (" ${loaderUtils. StringifyRequest (this, `!!!!! ${request} `)} ") `, / / call at runtime addStyle inserted the CSS content to the DOM ` the require (" ${loaderUtils. StringifyRequest (this, `! ${path.join(__dirname, "add-style. Js ")} ')}")(content) '// If CSS modules are enabled, Exports "if(content.locals) module.exports = content.locals",]; return result.join(";" ); };Copy the code
module.exports = function (content) {
  var style = document.createElement("style");
  style.innerHTML = content;
  document.head.appendChild(style);
};
Copy the code

In the pitch phase, style-loader generates the required CSS and runtime injection code. The result is returned to WebPack for further parsing, and the result returned by CSS-loader is imported as a module at run time. At run time, the CSS content can be obtained, and then add-style.js is called to insert the CSS content into the DOM.

The resources

  1. writting a loader
  2. Loader Interface
  3. loader runner