preface

In the in-depth analysis of Loader in the previous article [Webpack Advanced], the principle and operation mechanism of Loader was explored. This article will deepen the understanding of Loader by introducing the functions and implementation of common styles of Loader.

For a style file (less as an example), the most common Loader configuration is:

{
  module: {
    rules: [{test: /\.less$/,
        use: [
          'style-loader'.'css-loader'.'less-loader',],},],},}Copy the code

Let’s take a look at each one.

less-loader

Less is a CSS preprocessing language, extending the CSS language, adding variables, mixins, functions and other features. Less-loader is used to translate Less code into CSS code that can be recognized by the browser.

// demo.less
@base: #f938ab;

.box-shadow(@style.@c) when (iscolor(@c)) {
  -webkit-box-shadow: @style @c;
  box-shadow:         @style @c;
}
.box-shadow(@style.@alpha: 50%) when (isnumber(@alpha)) {
  .box-shadow(@style, rgba(0.0.0.@alpha));
}
.box {
  color: saturate(@base.5%);
  border-color: lighten(@base.30%);
  div { .box-shadow(0 0 5px.30%)}}Copy the code

The above less code will be translated by less-loader to:

// demo.css
.box {
  color: #fe33ac;
  border-color: #fdcdea;
}
.box div {
  -webkit-box-shadow: 0 0 5px rgba(0.0.0.0.3);
  box-shadow: 0 0 5px rgba(0.0.0.0.3);
}
Copy the code

Therefore, the principle of less-loader is very simple, which is to call the methods provided by the less library, translate the less syntax and output it as follows:

// Less-loader implementation (simplified)
const less = require('less');

module.exports = function(content) {
  const callback = this.async(); // Translation is time-consuming and asynchronous
  const options = this.getOptions(); // Obtain the options of less-loader in the configuration file
  
  less.render(
    content,
    createOptions(options), // less translation configuration
    (err, output) = > {
      callback(err, output.css); // Pass the generated CSS code to the next loader}); };Copy the code

css-loader

The purpose of CSS-loader is to parse @import and URL statements in Css files, process CSS-modules, and return the result as a JS module.

If we have a.c. S, b.c. S, c.

// a.css
@import './b.css'; / / import biggest ss

.a {
  font-size: 16px;
}

// b.css
@import './c.css'; / / import Arthur c. ss

.b {
  color: red;
}

// c.css
.c {
  font-weight: bolder;
}
Copy the code

Cs-loader () ¶

/ / CSS - loader output

exports = module.exports = require(".. /.. /.. /node_modules/css-loader/lib/css-base.js") (false);

// imports
// The file needs to rely on the JS module, which is empty

// module
exports.push([ // The module exports the content
  module.id, 
  ".src-components-Home-index__c--3riXS {\n  font-weight: bolder;\n}\n.src-components-Home-index__b--I-yI3 {\n  color: red;\n}\n.src-components-Home-index__a--3EFPE {\n  font-size: 16px;\n}\n".""
]); 

// exports
exports.locals = { // csS-modules class name mapping
  "c": "src-components-Home-index__c--3riXS"."b": "src-components-Home-index__b--I-yI3"."a": "src-components-Home-index__a--3EFPE"
};
Copy the code

It can be understood that CSS-loader concatenates the style content of A. CSS, B. CSS and C. CSS together in the form of strings and exports them as the content of JS modules.

// cS-loader source code (simplified)
// https://github.com/webpack-contrib/css-loader/blob/master/src/index.js
import postcss from 'postcss';

module.exports = async function (content, map, meta) {
  const options = this.getOptions(); // Get the configuration

  const plugins = []; // PostCSS plugin to translate source code
  shouldUseModulesPlugins(options, this) && plugins.push(modulesPlugins); / / handle CSS - modules
  shouldUseImportPlugin(options, this) && plugins.push(importPlugin); // process the @import statement
  shouldUseURLPlugin(options, this) && plugins.push(urlPlugin); // process the URL () statement
  shouldUseIcssPlugin(options, this) && plugins.push(icssPlugin); // Handle icSS related logic

  if (meta && meta.ast) { // Reuse CSS AST generated by previous loader (e.g. Postcss-loader)
    content = meta.ast.root;
  }

  const result = await postcss(plugins).process(content); // Use postcss to translate the source code

  const importCode = getImportCode(); // Dependency statements to import
  const moduleCode = getModuleCode(result); // The module exports the content
  const exportCode = getExportCode(); // Other information that needs to be exported, such as csS-modules class name mapping, etc

  const callback = this.async(); // Return asynchronously
  callback(null.`${importCode}${moduleCode}${exportCode}`);
};
Copy the code

style-loader

We have got the complete CSS style code after the csS-loader translation. The style-loader is used to insert the result into the DOM tree as a style tag.

It seems intuitive that we just need to return js code like this and insert the result returned by csS-loader into the DOM:

module.exports = function (content) {
  return `
    const style = document.createElement('style');
    style.innerHTML = '${content}';
    document.head.appendChild(style);
  `;
};
Copy the code

However, csS-loader does not return the text of CSS style code, but a JS module code, the js code directly into the style label is obviously not.

Let’s look at the style-loader implementation:

// style-loader
import loaderUtils from 'loader-utils';

module.exports = function (content) {
  // do nothing
};

module.exports.pitch = function (remainingRequest) {
  /* * Use the require statement to obtain the export of the JS module returned by csS-loader. The 'prefix skips the loader in the configuration to avoid repeating the * remainingRequest parameter to obtain the rest of the loader chain, In this case csS-loader, less-loader * uses the stringifyRequest method of loaderUtils to convert the absolute path in the request statement to a relative path */
  const requestPath = loaderUtils.stringifyRequest(this.'!!!!! ' + remainingRequest);

  // In this case, the requestPath is:
  / / '!!!!! . /node_modules/css-loader/index.js! . /node_modules/less-loader/dist/cjs.js! src/styles/index.less'

  return `
    const content = require(${requestPath})
    const style = document.createElement('style');
    style.innerHTML = content;
    document.head.appendChild(style);
  `;
};
Copy the code

Design of style-loader

  • CSS -loader returns styles that can only be obtained from the runtime of its JS module, so userequireStatements made
  • The normal method actually does nothing in the pitch method, rightInterrupts the execution of the Loader chain, and then inline calls the back loader to load the current less file
  • Putting the implementation in pitch into the normal method causes the Loader chain to execute twice
  • If there is no ‘!! The ‘prefix causes the loader chain to be called in an infinite loop

Style-loader implementation logic is quite round, is also a classic pitch application, understand its principle, can be said to be loader call chain, execution sequence and modular output have a more comprehensive understanding, recommended to experience.