Before I learned about the packaging and compilation principle of Webpack, today I saw part of the principle of Loader in Webpack, take this opportunity to share with you

What does loader do?

Loaders in Webpack are used to convert code, meaning that source code is converted from one type of code to another

Therefore, loader can be written as follows:

module.exports = function loader(code) {
    // some transfrom operation... and return resultCode
    return resultCode;
}
Copy the code

Basic Loader Configuration

The following is one of the basic configurations of loader in Webpack. If you do not know the configuration, you need to refer to the configuration rules of Loader in WebPack


module.exports = { ... .module: {
        rules: [{test: /\.css/.// Regular expressions are used as matching rules for loader
                use: [
                    {
                        loader: "".// Loader path, similar to the rule of require
                        options: { / / loader parameters. ,}}]}]}}Copy the code

Loader Execution sequence of packaging

We all know what loader is, so loader for the source code operation is in order, how to order? We can test the following code

// webpack.config.js
const path = require("path");

const outputPath = path.join(__dirname, "dist");
module.exports = {
    mode: "development".entry: {
        index: path.join(__dirname, "src"."index.js")},output: {
        path: outputPath,
        filename: "[name].js".clean: true
    },
    module: {
        rules: [{test: /index.js$/,
                use: [
                    "./loaders/loader1"."./loaders/loader2",]}, {test: /.js$/,
                use: [
                    "./loaders/loader3"."./loaders/loader4",]}]}}// loaders/loader1.js
module.exports = function (code) {
    console.log("loader1");
    return code;
}

// loaders/loader2.js
module.exports = function (code) {
    console.log("loader2");
    return code;
}

// loaders/loader3.js
module.exports = function (code) {
    console.log("loader3");
    return code;
}

// loaders/loader4.js
module.exports = function (code) {
    console.log("loader4");
    return code;
}

Copy the code

After executing the command NPX webpack, the result is as follows:

The loader executes from back to front

SRC /index,js, SRC /a.js, SRC /a.js, SRC /a.js

// src/index.js
require("./a");

Copy the code

What is the output of the webpack loader?

Therefore, when the Loader of Webpack is executed, the import files are executed by Loader method first, and then the dependent files are executed by Loader method successively, and the dependencies are matched by Loader’s matching rules.

The sequence of loader packaging and AST syntax tree establishment

Change SRC /index.js to the following code

// src/index.js
require("./a"); Variables a =1;
Copy the code

NPX webpack cannot be packaged and an error is reported.

Next we modify the loader/loader4.js code as follows:

// loaders/loader4.js
module.exports = function (code) {
    console.log("loader4");
    const { changeVar } = this.getOptions();
    const reg = new RegExp(changeVar, "g");
    return code.replace(reg, "var");
}
Copy the code

Modify webpack.config.js loader configuration for loader4 as follows:

module.exports = { ... .module: {
        rules: [..., {test: /.js$/,
                use: [
                    "./loaders/loader3",
                    {
                        loader: "./loaders/loader4".// loader: converts source code to another source code to run before building the AST tree
                        options: {
                            changeVar: "Variable"}},]}]}}Copy the code

Then execute NPX webpack as follows:

Above we get the output and do not report an error. The execution sequence of loader packing is earlier than that of AST syntax tree, so the following figure shows the webPack packing process:

Some examples

Simple CSS – loader

The new loader/style – loader. Js

// loaders/style-loader
const path = require("path");
const loaderUtil = require("loader-utils");
module.exports = function (buffer) {
    const {embed} = this.getOptions();
    if (embed) {
        return exportEmbedStyle(this, buffer);
    }

    return exportFileStyle(this, buffer);

}

function exportFileStyle(context, buffer) {
    const {filePath, publicPath} = context.getOptions();
    const filename = getFileName(context, buffer);
    const fullPath = path.join(filePath, filename);
    const relativePath = path.relative(publicPath, fullPath).replace(/\\/g."/");
    return `const link = document.createElement("link");
            link.rel = "stylesheet";
            link.href = "${relativePath}";
            document.head.appendChild(link);
            module.exports = "${relativePath}"`;
}

function getFileName(context, buffer) {
    const ext = path.extname(context.resourcePath);
    const _name = path.basename(context.resourcePath);
    const filename = _name.slice(0, _name.length - ext.length);
    const hash = loaderUtil.interpolateName(context, "[contenthash]", {
        content: buffer
    });
    const fullName = filename + "." + hash + ext;
    context.emitFile(fullName, buffer);

    return fullName;
}

function exportEmbedStyle(context, buffer) {
    const code = buffer.toString();
    return `const style = document.createElement("style"); style.innerHTML = `${code}`; document.head.appendChild(style); module.exports = `${code}` `;
}

module.exports.raw = true; // Let the source file be passed in as buffer
Copy the code

Modify the WebPack configuration:

const outputPath = path.join(__dirname, "dist");
const publicPath = path.join(__dirname, "public");
module.exports = { ... .module: {
        rules: [..., {test: /.css$/,
                use: [
                    {
                        loader: "./loaders/style-loader".options: {
                            filePath: outputPath,
                            publicPath: publicPath,
                            embed: false}}]}}Copy the code

Simple img – loader

New loaders/img – loader. Js

// loaders/img-loader.js
const path = require("path");
const loaderUtil = require("loader-utils");

function loader(buffer) {
   console.log(this.context);
   const { limit } = this.getOptions();
   if (buffer.byteLength <= limit) {
      return exportBase64(this, buffer);
   }

   return exportFile(this, buffer);
}

function exportFile(context, buffer) {
   const filename = getFilePath(context, buffer);
   const { filePath, publicPath } = context.getOptions();
   const fullPath = path.join(filePath, filename);
   const relativePath = path.relative(publicPath, fullPath).replace(/\\/g."/");
   return `module.exports = "${relativePath}"`;
}

function exportBase64(context, buffer) {
   const ext = path.extname(context.resourcePath).slice(1);
   const content = base64Image(buffer, ext);
   return `module.exports = `${content}` `;
}

function getFilePath(context, buffer) {
   const ext = path.extname(context.resourcePath);
   const _name = path.basename(context.resourcePath);
   const filename = _name.slice(0, _name.length - ext.length);
   const hash = loaderUtil.interpolateName(context, "[contenthash]", {
      content: buffer
   });
   const fullName = filename + "." + hash + ext;
   context.emitFile(fullName, buffer);

   return fullName;
}

function base64Image(buffer, ext) {
   let base64String = buffer.toString("base64");
   return `data:image/${ext}; base64,${base64String}`;
}

loader.raw = true;

module.exports = loader;
Copy the code

Modify wenpack configuration:

const outputPath = path.join(__dirname, "dist");
const publicPath = path.join(__dirname, "public");
module.exports = { ... .module: {
        rules: [..., {test: /.(png|jpg|gif)$/gi,
                use: [
                    {
                        loader: "./loaders/img-loader".options: {
                            limit: 1024 * 100.// use the path for more than 10KB and base64 for less than 100KB
                            filePath: outputPath,
                            publicPath: publicPath
                        }
                    }
                ]
            }
        ]
    }
}
Copy the code

See more loader context: loader Interface | webpack

To sum up, this is my understanding and partial application of laoder packaging and modifying code. Welcome your comments