Web pack loader

Time:2020-2-18

Preface

Loader is one of the cores of Web pack. It is used to convert different types of files into modules recognized by webback. This article will try to explore the loader in Web pack, uncover its working principle, and how to develop a loader.

1、 How loader works

Webpack can only deal with JavaScript code directly. Any non JS file must be converted to JS code in advance before it can participate in packaging. Loader is such a code converter. It’s powered byloader runnerExecute the call, receive the original resource data as parameters (when multiple loaders are used jointly, the result of the previous loader will be passed to the next loader), and finally output JavaScript code (and optional source map) to further compile the webpack.

2、 Loader execution order

1. classification

  • Pre: pre loader
  • Normal: normal loader
  • Inline: inline loader
  • Post: Post loader

2. Execution priority

  • The execution superior level of class 4 loader is:pre > normal > inline > post
  • The order of loader execution with the same priority is:Right to left, bottom to top

3. Function of prefix

Inline loaders can skip other types of loaders by adding different prefixes.

  • !Skip normal loader.
  • -!Skip pre and normal loader.
  • !!Skip pre, normal, and post loader.

These prefixes are very useful in many scenarios.

Three, how to develop a loader

Loader is a node module that exports a function.

1. The simplest loader

When only one loader is applied to the resource file, it receives the source code as a parameter and outputs the converted JS code.

// loaders/simple-loader.js

module.exports = function loader (source) {
    console.log('simple-loader is working');
    return source;
}

This is the simplest loader. This loader does nothing but receive the source code and return as is. To prove that this loader has been called, I printed a sentence “simple loader is working” in it.

Test this loader:
The loader path needs to be configured first
If you use NPM to install a third-party loader, you can write the name of the loader directly. But now we use the local loader developed by ourselves. We need to manually configure the path and tell webpack where these loaders are.

// webpack.config.js

const path = require('path');
module.exports = {
  entry: {...},
  output: {...},
  module: {
    rules: [
      {
        test: /\.js$/,
        //Directly indicate the absolute path of the loader
        use: path.resolve(__dirname, 'loaders/simple-loader')
      }
    ]
  }
}

If you think it’s not elegant to configure the local loader like this, you can choose one of the four ways to configure the local loader in webpack.

Perform webpack compilation
As you can see, the console output “simple loader is working”. Description loader was called successfully.

webpack-loader1.jpg

2. Loader with pitch

pitchIs a method on the loader. Its function is to block the loader chain.

// loaders/simple-loader-with-pitch.js

module.exports = function (source) {  
    console.log('normal excution');   
    return source;
}

//Pitch method on loader, not required
module.exports.pitch =  function() { 
    console.log('pitching graph');
    // todo
}

The pitch method is not required. If there is a pitch, the implementation of the loader is divided into two stages:pitchStage andnormal executionStage. Webpack will first execute the pitch method (if any) on each loader in the loader chain from left to right, and then the normal loader method on each loader in the loader chain from right to left.

If the following loader chain is configured:

use: ['loader1', 'loader2', 'loader3']

The real loader execution process is:

webpack-loader-flow-with-pitch.png

In this process, if any pitch has a return value, the loader chain is blocked. Webpack will skip all the following pitches and loaders and go directly to the previous loader’snormal execution

Suppose a string is returned in the pitch of loader2, and the loader chain is blocked:

webpack-loader-flow-with-pitch2.png

3. Write a simple style loader

Style loader is usually not used alone, but with CSS loader. The return value of CSS loader is a JS module, roughly as follows:

//Print the return value of CSS loader

// Imports
var ___CSS_LOADER_API_IMPORT___ = require("../node_modules/css-loader/dist/runtime/api.js");
exports = ___CSS_LOADER_API_IMPORT___(false);
// Module
exports.push([module.id, "\nbody {\n    background: yellow;\n}\n", ""]);
// Exports
module.exports = exports;

This module returns after execution in the runtime contextcssCode"\nbody {\n background: yellow;\n}\n"

The function of style loader is tocssCode conversionstyleLabels inserting intohtmlOfheadMedium.

Design thinking

  1. Style loader needs to return ajsScripts: creating astyleTags will becssCode assigned tostyleLabel, put thisstyleLabel insertionhtmlOfheadMedium.
  2. The difficulty is gettingcssCode, because the return value of the CSS loader can only be executed in the context of the runtime, while the execution loader is in the compilation phase. In other words, the return value of CSS loader is not useful in style loader.
  3. The plan of saving the nation by using the curvecssThe expression of the code, and obtain CSS at run time (similar torequire('css-loader!index.css'))。
  4. Call again in the loader handling CSSinline loader require cssFile will cause the problem of circular execution of loader, so we need to use thepitchMethod, let style loaderpitchStage returns the script, skips the remaining loaders, and requires inline prefixes!!The blessing.

Note: pitch method has three parameters:

  • Remainingrequest: the absolute path of the loader and resource file in the loader chain!A string formed as a connector.
  • Preceedingrequest: the absolute path of the loader in front of itself in the loader chain!A string formed as a connector.
  • Data: a fixed field stored in the context in each loader, which can be used to pitch data to the loader.

Can make use ofremainingRequestParameter to get the rest of the loader chain.

Realization

// loaders/simple-style-loader.js

const loaderUtils = require('loader-utils');
module.exports = function(source) {
    // do nothing
}

module.exports.pitch = function(remainingRequest) {
  console.log('simple-style-loader is working');
    //Return script in pitch phase
    return (
      `
      //Create style label
      let style = document.createElement('style');
      
      /**
      *Using the remainingrequest parameter to get the rest of the loader chain
      *Skip other loaders with '!'prefix 
      *Using the stringifyrequest method of loaderutils to change the absolute path of the module to the relative path
      *Assign the require expression to the style tag to get CSS
      */
      style.innerHTML = require(${loaderUtils.stringifyRequest(this, '!!' + remainingRequest)});
      
      //Insert style label into head
      document.head.appendChild(style);
      `
    )
}

A simple style loader is done.

On trial

Webpack configuration

// webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  entry: {...},
  output: {...},
  //Manually configure the loader path
  resolveLoader: {
    modules: [path.resolve(__dirname, 'loaders'), 'node_modules']
  },
  module: {
    rules: [
      {
        //Configure the loader to handle CSS
        test: /\.css$/,
        use: ['simple-style-loader', 'css-loader']
      }
    ]
  },
  plugins: [
    //Rendering home page
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ]
}

Introduce a CSS style file in index.js

// src/index.js

require('./index.css');
console.log('Brovo!');

Set the background color of the body to yellow in the style file

// src/index.css

body {
  background-color: yellow;
}

Execute webpack

npm run build

You can see that the command line console printed ‘simple style loader is working’, indicating that webpack successfully called the loader we wrote.

webpack-loader3.jpg

Open the index.html page under dist in the browser, you can see that the style takes effect and is successfully inserted into the page header!

webpack-loader2.jpg

It shows that the loader we wrote works.

Success!

3、 Some tips

2 kits recommended

Necessary for developing loader:

1. loader-utils
Several methods are commonly used in this module:

  • Getoptions gets the configuration item of the loader.
  • Interpolatename handles the name of the generated file.
  • Stringifyrequest processes the absolute path to the relative path of the relative root.

2. schema-utils
This module can help you verify the validity of the loader option configuration.
Usage:

// loaders/simple-loader-with-validate.js

const loaderUtils = require('loader-utils');
const validate = require('schema-utils');
module.exports = function(source) {
  //Get loader configuration item
  let options = loaderUtils.getOptions(this) || {};
  //Define configuration item structure and type
  let schema = {
    type: 'object',
    properties: {
      name: {
        type: 'string'
      }
    }
  }
  //Verify configuration items meet requirements
  validate(schema, options);
  return source;
}

When the configuration item does not meet the requirements, the compilation will be interrupted and the error information will be printed on the console:

webpack-loader4.jpg

Develop asynchronous loader

For the development of asynchronous loader (for example, when there are some operations that need to read files), you need to get asynchronous callback through this. Async(), and then call it manually.
Usage:

// loaders/simple-async-loader.js

module.exports = function(source) {
    console.log('async loader');
    let cb = this.async();
    setTimeout(() => {
      console.log('ok');
      //Call CB manually in asynchronous callback to return processing result
      cb(null, source);
    }, 3000);
}

Note: the first parameter of asynchronous callback cb() iserror, the result to be returned is placed in the second parameter.

raw loader

If you are a loader that processes resources such as pictures and fonts, you need to set the raw property on the loader to true, so that the loader can support binary format resources (the default value of webpack isutf-8Read the contents of the file to the loader.
Usage:

// loaders/simple-raw-loader.js

module.exports = function(source) {
  //Binary data of buffer type will be output
  console.log(source);
  // todo handle source
  let result = 'results of processing source'
  return `
    module.exports = '${result}'
  `;
}
//Tell weback that the loader needs to receive binary data
module.exports.raw = true;

webpack-loader5.jpg

Note: the raw attribute is usually used in the loader with file output requirements.

output file

In the development of some loaders dealing with resource files (such as pictures, fonts, etc.), you need to copy or generate new files, and you can use the internalthis.emitFile()Method.
Usage:

// loaders/simple-file-loader.js

const loaderUtils = require('loader-utils');
module.exports = function(source) {
  //Get the configuration item of the loader
  let options = loaderUtils.getOptions(this) || {};
  //Get the file name set by the user or make a new file name
  //Note that the third parameter is the basis for calculating content hash
  let url = loaderUtils.interpolateName(this, options.filename || '[contenthash].[ext]', {content: source});
  //Output file
  this.emitFile(url, source);
  //Module script to return the address of the exported file
  return `module.exports = '${JSON.stringify(url)}'`;
}
module.exports.raw = true;

webpack-loader6.jpg

In this example, loader reads the picture content (buffer), rename it, and then invocation.this.emitFile()Output to the specified directory, and finally return a module, which exports the renamed image address. So whenrequireWhen a picture is taken, it is equivalent to requiring a module to get the final picture path. (this is the basic principle of file loader)

Development agreement

In order to make our loader have higher quality and reusability, remember to keep it simple. In other words, try to keep one loader focused on one thing. If you find that the loader you write is relatively large, try to split it into several loaders.

In the webpack community, there is a loader development guideline. We can refer to it to guide our loader design:

  • Keep it simple.
  • Utilize multiple loader chains.
  • Modular output.
  • Make sure the loader is stateless.
  • Use the loader utils package.
  • Tag the loader dependency.
  • Resolve module dependencies.
  • Extract common code.
  • Avoid absolute paths.
  • Use peerdependency peer dependency.

Four, summary

  1. The essence of loader is a node module, which exports a function. There may be a pitch method on this function.

  2. Knowing the nature of the loader and the execution mechanism of the loader chain, you already have the foundation of the loader development.

  3. It is not difficult to develop a loader, but to develop a high-quality loader, we still need to practice.

  4. Try to develop and maintain a small loader by yourself – maybe you can solve some practical problems in the project by writing your own loader later.

Article source: https://github.com/yc111/webpack-loader

Welcome to exchange ~

Happy New Year!

Reference resources
https://webpack.js.org/concepts/#loaders
https://webpack.js.org/api/loaders/
https://webpack.js.org/contribute/writing-a-loader/
https://github.com/webpack/webpack/blob/v4.41.5/lib/NormalModuleFactory.js
https://github.com/webpack-contrib/style-loader/blob/master/src/index.js
https://www.npmjs.com/package/loader-utils
https://www.npmjs.com/package/schema-utils

Welcome to reprint. Please indicate the source of Reprint:
https://champyin.com/2020/01/28/%E6%8F%AD%E7%A7%98webpack-loader/

Recommended Today

The application of USB camera in rk3399

The application of USB camera in rk3399 1, introduction UVCFull nameUSB Video Class, is a set of standard customized by usb-if. All USB interface cameras complying with this standard can almost be used directly under Windows Linux and other systems, achieving the similar effect of drive free. Of course, it doesn’t mean that there is […]