current position:Home>Explain in detail with multiple pictures and understand webpack loader at one time

Explain in detail with multiple pictures and understand webpack loader at one time

2021-08-23 07:50:15 Tong ouba

Webpack Is a modular packaging tool , It is widely used in most projects in the front-end field . utilize Webpack We can not only pack JS file , You can also package pictures 、CSS、 Font and other types of resource files . Packaging is not supported JS The properties of the file are based on Loader Mechanism to implement . So we should learn Webpack, We need to master Loader Mechanism . In this article, Po will take you to learn more Webpack Of Loader Mechanism , After reading this article, you will learn the following :

  • Loader What is the essence of ?
  • Normal Loader and Pitching Loader What is it? ?
  • Pitching Loader What is the role of ?
  • Loader How is it loaded ?
  • Loader How it works ?
  • Multiple Loader What is the order of execution of ?
  • Pitching Loader How to realize the circuit breaker mechanism ?
  • Normal Loader How functions are run ?
  • Loader On the object raw What's the use of attributes ?
  • Loader In the body of the function this.callback and this.async Where did the method come from ?
  • Loader How the final return result is processed ?

One 、Loader What is the essence of ?

It can be seen from the above figure ,Loader It is essentially a function derived from JavaScript modular . The exported function , It can be used to realize content conversion , This function supports the following 3 Parameters :

/**
 * @param {string|Buffer} content  The content of the source file 
 * @param {object} [map]  Can be  https://github.com/mozilla/source-map  The use of  SourceMap  data 
 * @param {any} [meta] meta  data , It can be anything 
 */
function webpackLoader(content, map, meta) {
  //  Yours webpack loader Code 
}
module.exports = webpackLoader;

After understanding the signature of the exported function , We can define a simple simpleLoader

function simpleLoader(content, map, meta) {
  console.log(" I am a  SimpleLoader");
  return content;
}
module.exports = simpleLoader;

The above simpleLoader The input will not be processed , Just when it's time Loader Output corresponding information during execution .Webpack Allows users to configure multiple different resources for certain resource files Loader, Like dealing with .css When you file , We used style-loader and css-loader, The specific configuration method is as follows :

webpack.config.js

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
     filename: 'bundle.js',
     path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },
};

Webpack The benefits of this design , Yes, it can ensure that every Loader A single responsibility . meanwhile , Also convenient for later Loader Combination and extension of . such as , You want Webpack Able to handle Scss file , You just need to install sass-loader, Then configure Scss File processing rules , Set up rule Object's use The attribute is ['style-loader', 'css-loader', 'sass-loader'] that will do .

Two 、Normal Loader and Pitching Loader What is it? ?

2.1 Normal Loader

Loader It is essentially a function derived from JavaScript modular , The function exported by this module ( if ES6 modular , Is the default exported function ) This is called Normal Loader. It should be noted that , Here we introduce Normal Loader And Webpack Loader Defined in the classification Loader It's different . stay Webpack in ,loader It can be divided into 4 class :pre In front of 、post After 、normal Ordinary and inline inline . among pre and post loader, Can pass rule Object's enforce Property to specify :

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

module.exports = {
  module: {
    rules: [
      {
        test: /\.txt$/i,
        use: ["a-loader"],
        enforce: "post", // post loader
      },
      {
        test: /\.txt$/i,
        use: ["b-loader"], // normal loader
      },
      {
        test: /\.txt$/i,
        use: ["c-loader"],
        enforce: "pre", // pre loader
      },
    ],
  },
};

To understand the Normal Loader After the concept of , Let's write Normal Loader. First, let's create a new directory :

$ mkdir webpack-loader-demo

Then go to the directory , Use npm init -y Command to perform initialization . After the command is executed successfully , A... Will be generated in the current directory package.json file :

{
  "name": "webpack-loader-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

Tips : The development environment used locally :Node v12.16.2;Npm 6.14.4;

Then we use the following command , Install it. webpack and webpack-cli Dependency package :

$ npm i webpack webpack-cli -D

After installing project dependencies , We add the corresponding directories and files according to the following directory structure :

├── dist #  Package output directory 
│   └── index.html
├── loaders # loaders Folder 
│   ├── a-loader.js
│   ├── b-loader.js
│   └── c-loader.js
├── node_modules
├── package-lock.json
├── package.json
├── src #  Source directory 
│   ├── data.txt #  Data files 
│   └── index.js #  Entrance file 
└── webpack.config.js # webpack The configuration file 

dist/index.html

<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Webpack Loader  Example </title>
</head>
<body>
    <h3>Webpack Loader  Example </h3>
    <p id="message"></p>
    <script src="./bundle.js"></script>
</body>
</html>

src/index.js

import Data from "./data.txt"

const msgElement = document.querySelector("#message");
msgElement.innerText = Data;

src/data.txt

 Hello everyone , I'm brother Bao 

loaders/a-loader.js

function aLoader(content, map, meta) {
  console.log(" Start execution aLoader Normal Loader");
  content += "aLoader]";
  return `module.exports = '${content}'`;
}

module.exports = aLoader;

stay aLoader Function , We will be right. content Change the content , Then return module.exports = '${content}' character string . So why put content Assign a value to module.exports Attribute? ? Let's not explain the specific reasons here , Let's analyze this problem later .

loaders/b-loader.js

function bLoader(content, map, meta) {
  console.log(" Start execution bLoader Normal Loader");
  return content + "bLoader->";
}

module.exports = bLoader;

loaders/c-loader.js

function cLoader(content, map, meta) {
  console.log(" Start execution cLoader Normal Loader");
  return content + "[cLoader->";
}

module.exports = cLoader;

stay loaders Under the table of contents , We defined the above 3 individual Normal Loader. these Loader The implementation of is relatively simple , It's just Loader Execution time content Add the current... To the parameter Loader Information about . In order to make Webpack Able to identify loaders Custom in the directory Loader, We still need to Webpack In the configuration file , Set up resolveLoader attribute , The specific configuration method is as follows :

webpack.config.js

const path = require("path");

module.exports = {
  entry: "./src/index.js",
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "dist"),
  },
  mode: "development",
  module: {
    rules: [
      {
        test: /\.txt$/i,
        use: ["a-loader", "b-loader", "c-loader"],
      },
    ],
  },
  resolveLoader: {
    modules: [
      path.resolve(__dirname, "node_modules"),
      path.resolve(__dirname, "loaders"),
    ],
  },
};

When the directory update is complete , stay webpack-loader-demo Run the project in the root directory npx webpack The command can start packaging . The following is the operation of Po ge npx webpack After the command , Console output :

 Start execution cLoader Normal Loader
 Start execution bLoader Normal Loader
 Start execution aLoader Normal Loader
asset bundle.js 4.55 KiB [emitted] (name: main)
runtime modules 937 bytes 4 modules
cacheable modules 187 bytes
  ./src/index.js 114 bytes [built] [code generated]
  ./src/data.txt 73 bytes [built] [code generated]
webpack 5.45.1 compiled successfully in 99 ms

By observing the above output , We can know Normal Loader The order of execution is from right to left . Besides , When the packaging is complete , We open it in the browser dist/index.html file , On the page you will see the following information :

Webpack Loader  Example 
 Hello everyone , I'm brother Bao [cLoader->bLoader->aLoader]

By the output information on the page ” Hello everyone , I'm brother Bao [cLoader->bLoader->aLoader]“ You know ,Loader In the process of execution, it is in the form of pipeline , Process the data , The specific processing process is shown in the figure below :

Now you know what it is Normal Loader And Normal Loader Execution order of , Next, let's introduce another Loader —— Pitching Loader.

2.2 Pitching Loader

Developing Loader when , We can add a... To the exported function pitch attribute , Its value is also a function . This function is called Pitching Loader, It supports 3 Parameters :

/**
 * @remainingRequest  The remaining requests 
 * @precedingRequest  Pre request 
 * @data  Data objects 
 */
function (remainingRequest, precedingRequest, data) {
 // some code
};

among data Parameters , Can be used for data transfer . That is to say pitch In the function data Add data to the object , After the normal Function through this.data To read the added data . and remainingRequest and precedingRequest What are the parameters ? Here, let's update a-loader.js file :

function aLoader(content, map, meta) {
  //  Omitted code 
}

aLoader.pitch = function (remainingRequest, precedingRequest, data) {
  console.log(" Start execution aLoader Pitching Loader");
  console.log(remainingRequest, precedingRequest, data)
};

module.exports = aLoader;

In the above code , We are aLoader The function adds a pitch Property and set its value to a function object . In the body of a function , We output the parameters received by the function . next , We update... In the same way b-loader.js and c-loader.js file :

b-loader.js

function bLoader(content, map, meta) {
  //  Omitted code 
}

bLoader.pitch = function (remainingRequest, precedingRequest, data) {
  console.log(" Start execution bLoader Pitching Loader");
  console.log(remainingRequest, precedingRequest, data);
};

module.exports = bLoader;

c-loader.js

function cLoader(content, map, meta) {
  //  Omitted code 
}

cLoader.pitch = function (remainingRequest, precedingRequest, data) {
  console.log(" Start execution cLoader Pitching Loader");
  console.log(remainingRequest, precedingRequest, data);
};

module.exports = cLoader;

When all files are updated , We are webpack-loader-demo The root directory of the project executes again npx webpack After the command , The corresponding information will be output . Here we have b-loader.js Of pitch The output result of the function is taken as an example , So let's analyze that remainingRequest and precedingRequest Output result of parameter :

/Users/fer/webpack-loader-demo/loaders/c-loader.js!/Users/fer/webpack-loader-demo/src/data.txt # The remaining requests 
/Users/fer/webpack-loader-demo/loaders/a-loader.js # Pre request 
{} # Empty data object 

In addition to the above output information , We can also clearly see Pitching Loader and Normal Loader Execution order of :

 Start execution aLoader Pitching Loader
...
 Start execution bLoader Pitching Loader
...
 Start execution cLoader Pitching Loader
...
 Start execution cLoader Normal Loader
 Start execution bLoader Normal Loader
 Start execution aLoader Normal Loader

Obviously for our example ,Pitching Loader The order of execution is From left to right , and Normal Loader The order of execution is From right to left . The specific implementation process is shown in the figure below :

Tips :Webpack Internal use loader-runner This library to run the configured loaders.

Seeing some friends here, you may have questions ,Pitching Loader In addition to being able to run in advance , What else does it do ? In fact, when someone Pitching Loader Return non undefined When the value of , Will achieve the fusing effect . Here we update bLoader.pitch Method , Let it go back to "bLoader Pitching Loader->" character string :

bLoader.pitch = function (remainingRequest, precedingRequest, data) {
  console.log(" Start execution bLoader Pitching Loader");
  return "bLoader Pitching Loader->";
};

When the update is complete bLoader.pitch Method , Let's do it again npx webpack After the command , The console will output the following :

 Start execution aLoader Pitching Loader
 Start execution bLoader Pitching Loader
 Start execution aLoader Normal Loader
asset bundle.js 4.53 KiB [compared for emit] (name: main)
runtime modules 937 bytes 4 modules
...

It can be seen from the above output results that , When bLoader.pitch Method returns non undefined When the value of , Skip the rest loader. The specific implementation process is shown in the figure below :

Tips :Webpack Internal use loader-runner This library to run the configured loaders.

after , We open it again in the browser dist/index.html file . here , On the page you will see the following information :

Webpack Loader  Example 
bLoader Pitching Loader->aLoader]

Introduction after Normal Loader and Pitching Loader Knowledge about , Let's analyze Loader How it works .

3、 ... and 、Loader How it works ?

Make it clear Loader How it works , We can use the breakpoint debugging tool to find Loader The operation entrance of . Here we are familiar with Visual Studio Code For example , To introduce how to configure the breakpoint debugging environment :

When you follow the above steps , In the current project (webpack-loader-demo) Next , Automatically created .vscode Directory and automatically generate a launch.json file . next , We copy the following and directly replace launch.json The original content in .

{
    "version": "0.2.0",
    "configurations": [{
       "type": "node",
       "request": "launch",
       "name": "Webpack Debug",
       "cwd": "${workspaceFolder}",
       "runtimeExecutable": "npm",
       "runtimeArgs": ["run", "debug"],
       "port": 5858
    }]
}

Use the above configuration information , We created one Webpack Debug Debugging tasks for . When running the task , Will execute in the current working directory npm run debug command . therefore , And then we need to package.json Add to file debug command , The details are as follows :

// package.json
{  
  "scripts": {
    "debug": "node --inspect=5858 ./node_modules/.bin/webpack"
  },
}

After making the above preparations , We can do that a-loader Of pitch Add a breakpoint to the function . The corresponding call stack is as follows :

By observing the above call stack information , We can see the call runLoaders Method , The method comes from loader-runner modular . So make sure Loader How it works , We need to analyze runLoaders Method . Let's start by analyzing the loader-runner modular , Its version is 4.2.0. among runLoaders The method is defined in lib/LoaderRunner.js In file :

// loader-runner/lib/LoaderRunner.js
exports.runLoaders = function runLoaders(options, callback) {
  // read options
 var resource = options.resource || "";
 var loaders = options.loaders || [];
 var loaderContext = options.context || {}; // Loader Context object 
 var processResource = options.processResource || ((readResource, context, 
    resource, callback) => {
  context.addDependency(resource);
  readResource(resource, callback);
 }).bind(null, options.readResource || readFile);

 // prepare loader objects
 loaders = loaders.map(createLoaderObject);
  loaderContext.context = contextDirectory;
 loaderContext.loaderIndex = 0;
 loaderContext.loaders = loaders;
  
  //  Omit most of the code 
 var processOptions = {
  resourceBuffer: null,
  processResource: processResource
 };
  //  iteration PitchingLoaders
 iteratePitchingLoaders(processOptions, loaderContext, function(err, result) {
  // ...
 });
};

From the above code we can see , stay runLoaders Function , We'll start with options Get on the configuration object loaders Information , And then call createLoaderObject Function creation Loader object , After calling this method, it will return a message containing normalpitchraw and data Objects with equal attributes . At present, most attribute values of this object are null, In the subsequent processing flow , The corresponding attribute values will be filled .

// loader-runner/lib/LoaderRunner.js
function createLoaderObject(loader) {
 var obj = {
  path: null,
    query: null, 
    fragment: null,
  options: null, 
    ident: null,
  normal: null, 
    pitch: null,
  raw: null, 
    data: null,
  pitchExecuted: false,
  normalExecuted: false
 };
 //  Omitted code 
 obj.request = loader;
 if(Object.preventExtensions) {
  Object.preventExtensions(obj);
 }
 return obj;
}

After creating Loader Object and initialization loaderContext After object , Will call iteratePitchingLoaders The function starts to iterate Pitching Loader. In order to let everyone have a general understanding of the subsequent processing process , Let's look at the code first , Let's review the previous run txt loaders Call stack of :

With the corresponding runLoaders Functional options The object structure is as follows :

Based on the above call stack and related source code , Po also drew a corresponding flow chart :

After reading the above flowchart and call stack diagram , Next, let's analyze the core code of related functions in the flowchart . Here we first analyze iteratePitchingLoaders

// loader-runner/lib/LoaderRunner.js
function iteratePitchingLoaders(options, loaderContext, callback) {
 // abort after last loader
 if(loaderContext.loaderIndex >= loaderContext.loaders.length)
    //  stay processResource Within the function , Would call iterateNormalLoaders function 
    //  Start execution normal loader
  return processResource(options, loaderContext, callback);

  //  On first execution ,loaderContext.loaderIndex The value of is 0
 var currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex];

 //  If at present loader Object's pitch The function has been executed , Then the next one is executed loader Of pitch function 
 if(currentLoaderObject.pitchExecuted) {
  loaderContext.loaderIndex++;
  return iteratePitchingLoaders(options, loaderContext, callback);
 }

 //  load loader modular 
 loadLoader(currentLoaderObject, function(err) {
    if(err) {
   loaderContext.cacheable(false);
   return callback(err);
  }
    //  Get current loader On the object pitch function 
  var fn = currentLoaderObject.pitch;
    //  identification loader The object has been iteratePitchingLoaders Function 
  currentLoaderObject.pitchExecuted = true;
  if(!fn) return iteratePitchingLoaders(options, loaderContext, callback);

    //  Start execution pitch function 
  runSyncOrAsync(fn,loaderContext, ...);
  //  Omitted code 
 });
}

stay iteratePitchingLoaders Internal function , From the far left loader Object starts processing , And then call loadLoader Function starts loading loader modular . stay loadLoader Internal function , Will be based on loader The type of , Use different loading methods . For our current project , Will pass require(loader.path) The way to load loader modular . The specific code is as follows :

// loader-runner/lib/loadLoader.js
module.exports = function loadLoader(loader, callback) {
 if(loader.type === "module") {
  try {
    if(url === undefined) url = require("url");
   var loaderUrl = url.pathToFileURL(loader.path);
   var modulePromise = eval("import(" + JSON.stringify(loaderUrl.toString()) + ")");
   modulePromise.then(function(module) {
    handleResult(loader, module, callback);
   }, callback);
   return;
  } catch(e) {
   callback(e);
  }
 } else {
  try {
   var module = require(loader.path);
  } catch(e) {
   //  Omit the relevant code 
  }
    //  Process loaded modules 
  return handleResult(loader, module, callback);
 }
};

No matter which loading method is used , After successfully loading loader After the module , Will be called handleResult Function to handle the loaded module . The function is to , Get the exported function in the module and the function pitch and raw Property and assign it to the corresponding loader Object :

// loader-runner/lib/loadLoader.js
function handleResult(loader, module, callback) {
 if(typeof module !== "function" && typeof module !== "object") {
  return callback(new LoaderLoadingError(
   "Module '" + loader.path + "' is not a loader (export function or es6 module)"
  ));
 }
 loader.normal = typeof module === "function" ? module : module.default;
 loader.pitch = module.pitch;
 loader.raw = module.raw;
 if(typeof loader.normal !== "function" && typeof loader.pitch !== "function") {
  return callback(new LoaderLoadingError(
   "Module '" + loader.path + "' is not a loader (must have normal or pitch function)"
  ));
 }
 callback();
}

After processing the loaded loader After the module , Will continue to call the incoming callback Callback function . Within this callback function , Will be in the current loader Get... On object pitch function , And then call runSyncOrAsync Function to execute pitch function . For our project , It's going to execute aLoader.pitch function .

See the little friends here , Should have known loader How modules are loaded and loader Module pitch How functions are run . Due to limited space , Brother Po will not introduce it in detail loader-runner Other functions in the module . Next , We will continue to analyze through several questions loader-runner Functions provided by the module .

Four 、Pitching Loader How to realize the circuit breaker mechanism ?

// loader-runner/lib/LoaderRunner.js
function iteratePitchingLoaders(options, loaderContext, callback) {
 //  Omitted code 
 loadLoader(currentLoaderObject, function(err) {
  var fn = currentLoaderObject.pitch;
    //  Identify the current loader Has been dealt with 
  currentLoaderObject.pitchExecuted = true;
    //  If at present loader Not defined on object pitch function , Then process the next loader object 
  if(!fn) return iteratePitchingLoaders(options, loaderContext, callback);

    //  perform loader Module pitch function 
  runSyncOrAsync(
   fn,
   loaderContext, [loaderContext.remainingRequest, 
        loaderContext.previousRequest, currentLoaderObject.data = {}],
   function(err) {
    if(err) return callback(err);
    var args = Array.prototype.slice.call(arguments, 1);
    var hasArg = args.some(function(value) {
     return value !== undefined;
    });
    if(hasArg) {
     loaderContext.loaderIndex--;
     iterateNormalLoaders(options, loaderContext, args, callback);
    } else {
     iteratePitchingLoaders(options, loaderContext, callback);
    }
   }
  );
 });
}

In the above code ,runSyncOrAsync The callback function of the function , Will be based on the current loader object pitch Whether the return value of the function is undefined To perform different processing logic . If pitch The function returned a non undefined Value , A fuse will appear . That is, skip the subsequent execution process , Start executing the previous loader On the object normal loader function . The specific implementation method is also very simple , Namely loaderIndex The value of the reduction 1, And then call iterateNormalLoaders Function to implement . And if the pitch The function returns undefined, Then continue to call iteratePitchingLoaders Function to handle the next unhandled loader object .

5、 ... and 、Normal Loader How functions are run ?

// loader-runner/lib/LoaderRunner.js
function iterateNormalLoaders(options, loaderContext, args, callback) {
 if(loaderContext.loaderIndex < 0)
  return callback(null, args);

 var currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex];

 // normal loader The order of execution is from right to left 
 if(currentLoaderObject.normalExecuted) {
  loaderContext.loaderIndex--;
  return iterateNormalLoaders(options, loaderContext, args, callback);
 }

  //  Get current loader On the object normal function 
 var fn = currentLoaderObject.normal;
  //  identification loader The object has been iterateNormalLoaders Function 
 currentLoaderObject.normalExecuted = true;
 if(!fn) { //  At present loader Object is undefined normal function , Then continue with the previous loader object 
  return iterateNormalLoaders(options, loaderContext, args, callback);
 }

 convertArgs(args, currentLoaderObject.raw);

 runSyncOrAsync(fn, loaderContext, args, function(err) {
  if(err) return callback(err);

  var args = Array.prototype.slice.call(arguments, 1);
  iterateNormalLoaders(options, loaderContext, args, callback);
 });
}

From the above code we can see , stay loader-runner Inside the module, you can call iterateNormalLoaders function , To execute the loaded loader On the object normal loader function . And iteratePitchingLoaders The function is the same , stay iterateNormalLoaders The function is also called internally runSyncOrAsync Function to execute fn function . But in the call normal loader Function before , Will call first convertArgs Function to process parameters .

convertArgs The function will raw Attribute to args[0]( The content of the document ) To deal with , The specific implementation of this function is as follows :

// loader-runner/lib/LoaderRunner.js
function convertArgs(args, raw) {
 if(!raw && Buffer.isBuffer(args[0]))
  args[0] = utf8BufferToString(args[0]);
 else if(raw && typeof args[0] === "string")
  args[0] = Buffer.from(args[0], "utf-8");
}

//  hold buffer Object to utf-8 Format string 
function utf8BufferToString(buf) {
 var str = buf.toString("utf-8");
 if(str.charCodeAt(0) === 0xFEFF) {
  return str.substr(1);
 } else {
  return str;
 }
}

I believe after reading convertArgs After the relevant code of the function , You are right about raw A deeper understanding of the role of attributes .

6、 ... and 、Loader In the body of the function this.callback and this.async Where did the method come from ?

Loader It can be divided into synchronization Loader And asynchronous Loader, For synchronization Loader Come on , We can go through return Sentence or this.callback To synchronously return the converted results . It's just a comparison return sentence ,this.callback The method is more flexible , Because it allows multiple parameters to be passed .

sync-loader.js

module.exports = function(source) {
 return source + "-simple";
};

sync-loader-with-multiple-results.js

module.exports = function (source, map, meta) {
  this.callback(null, source + "-simple", map, meta);
  return; //  When calling  callback()  Function time , Always returns  undefined
};

It should be noted that this.callback Method support 4 Parameters , The specific functions of each parameter are as follows :

this.callback(
  err: Error | null,    //  error message 
  content: string | Buffer,    // content Information 
  sourceMap?: SourceMap,    // sourceMap
  meta?: any    //  Will be  webpack  Ignore , It could be anything 
);

And for asynchrony loader, We need to call this.async Method to get callback function :

async-loader.js

module.exports = function(source) {
 var callback = this.async();
 setTimeout(function() {
  callback(null, source + "-async-simple");
 }, 50);
};

So in the above example ,this.callback and this.async Where did the method come from ? With this question , Let's come from loader-runner Module source code , To find out .

this.async

// loader-runner/lib/LoaderRunner.js
function runSyncOrAsync(fn, context, args, callback) {
 var isSync = true; //  The default is synchronization type 
 var isDone = false; //  Has it been completed? 
 var isError = false; // internal error
 var reportedError = false;
  
 context.async = function async() {
  if(isDone) {
   if(reportedError) return; // ignore
   throw new Error("async(): The callback was already called.");
  }
  isSync = false;
  return innerCallback;
 };
}

We have already introduced runSyncOrAsync Function function , This function is used to execute Loader Module Normal Loader or Pitching Loader function . stay runSyncOrAsync Internal function , It will eventually pass fn.apply(context, args) Method call Loader function . Will pass apply Method setting Loader The execution context of the function .

Besides , From the above code we can see , When calling this.async After method , It will be set first. isSync The value of is false, Then return innerCallback function . In fact, this function is similar to this.callback All point to the same function .

this.callback

// loader-runner/lib/LoaderRunner.js
function runSyncOrAsync(fn, context, args, callback) {
  //  Omitted code 
 var innerCallback = context.callback = function() {
  if(isDone) {
   if(reportedError) return; // ignore
   throw new Error("callback(): The callback was already called.");
  }
  isDone = true;
  isSync = false;
  try {
   callback.apply(null, arguments);
  } catch(e) {
   isError = true;
   throw e;
  }
 };
}

If in Loader Function , It's through return Statement to return the processing result , that isSync Value is still the true, The following corresponding processing logic will be executed :

// loader-runner/lib/LoaderRunner.js
function runSyncOrAsync(fn, context, args, callback) {
  //  Omitted code 
 try {
  var result = (function LOADER_EXECUTION() {
   return fn.apply(context, args);
  }());
  if(isSync) { //  Use return Statement returns the result of processing 
   isDone = true;
   if(result === undefined)
    return callback();
   if(result && typeof result === "object" && typeof result.then === "function") {
    return result.then(function(r) {
     callback(null, r);
    }, callback);
   }
   return callback(null, result);
  }
 } catch(e) {
    //  Omit exception handling code 
 }
}

By looking at the above code , We can know in Loader Function , have access to return Statement returns directly Promise object , Like this way :

module.exports = function(source) {
 return Promise.resolve(source + "-promise-simple");
};

Now we know Loader How to return data , that Loader How is the final returned result processed ? Let's briefly introduce .

7、 ... and 、Loader How the final return result is processed ?

// webpack/lib/NormalModule.js(Webpack  edition :5.45.1)
build(options, compilation, resolver, fs, callback) {
    //  Omitted code 
  return this.doBuild(options, compilation, resolver, fs, err => {
   // if we have an error mark module as failed and exit
   if (err) {
    this.markModuleAsErrored(err);
    this._initBuildHash(compilation);
    return callback();
   }

      //  Omitted code 
   let result;
   try {
    result = this.parser.parse(this._ast || this._source.source(), {
     current: this,
     module: this,
     compilation: compilation,
     options: options
    });
   } catch (e) {
    handleParseError(e);
    return;
   }
   handleParseResult(result);
  });
}

From the above code we can see , stay this.doBuild Method , Will use JavascriptParser The parser parses the returned content , And the bottom is through acorn This third-party library to achieve JavaScript Code parsing . And the result after analysis , Will continue to call handleParseResult Function for further processing . I don't want to introduce you here , Interested partners can read the relevant source code by themselves .

8、 ... and 、 Why do we have to content Assign a value to module.exports Attribute? ?

Finally, let's answer the previous questions —— stay a-loader.js Module , Why do we have to content Assign a value to module.exports Attribute? ? Answer that question , We will start from Webpack Generated bundle.js file ( Deleted comment information ) Find the answer to this question in :

__webpack_modules__

var __webpack_modules__ = ({
  "./src/data.txt":  ((module)=>{
    eval("module.exports = ' Hello everyone , I'm brother Bao [cLoader->bLoader->aLoader]'\n\n//# 
      sourceURL=webpack://webpack-loader-demo/./src/data.txt?");
   }),
 "./src/index.js":((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
    eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var 
     _data_txt__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./data.txt */ \"./src/data.txt\");...
    );
  })
});

__webpack_require__

// The module cache
var __webpack_module_cache__ = {};
// The require function
function __webpack_require__(moduleId) {
  // Check if module is in cache
  var cachedModule = __webpack_module_cache__[moduleId];
  if (cachedModule !== undefined) {
     return cachedModule.exports;
  }
 // Create a new module (and put it into the cache)
 var module = __webpack_module_cache__[moduleId] = {
   exports: {}
 };
 // Execute the module function
 __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
 // Return the exports of the module
 return module.exports;
}

In the generated bundle.js In file ,./src/index.js Inside the corresponding function , By calling __webpack_require__ Function to import ./src/data.txt The contents of the path . And in the __webpack_require__ The internal function will get... From the cache object first moduleId Corresponding module , If the module already exists , Will return to the module object exports The value of the property . If the cache object does not exist moduleId Corresponding module , A file containing exports Attribute module object , Then according to moduleId from __webpack_modules__ In the object , Get the corresponding function and call it with the corresponding parameters , Eventually return module.exports Value . So in a-loader.js In file , hold content Assign a value to module.exports The purpose of the attribute is to export the corresponding content .

Nine 、 summary

This paper introduces Webpack Loader The essence of 、Normal Loader and Pitching Loader Definition and use of Loader How it is run and so on , I hope after reading this article , You are right about Webpack Loader Mechanism can have a deeper understanding . In this article, brother Po only introduces loader-runner modular , Actually loader-utils(Loader Tool library ) and schema-utils(Loader Options Validation Library ) These two modules are also related to Loader Is closely linked . Writing Loader When , You may use them . If you know how to write a Loader interested , You can read writing-a-loader This document or Nuggets Hand to hand Webpack Loader This article .

Ten 、 The resources

  • Webpack Official website
  • Github — loader-runner

official account : Front end canteen

You know : Tong ouba

Nuggets : Tong ouba

This is a lifelong learning man , He's sticking to what he loves , Welcome to the front end canteen , Get fat with this man ~

“ If you think you can gain from reading this article, you can click here and let me see . Any questions during reading 、 Thoughts or feelings are also welcome to leave a message below , You can also reply to the communication group of the canteen in the background . Communication creates value , Sharing brings happiness . You are also welcome to share with the students in need , Altruism is the best self-interest . ”

This article is from WeChat official account. - Front end canteen (webcanteen)

The source and reprint of the original text are detailed in the text , If there is any infringement , Please contact the [email protected] Delete .

Original publication time : 2021-08-13

Participation of this paper Tencent cloud media sharing plan , You are welcome to join us , share .

copyright notice
author[Tong ouba],Please bring the original link to reprint, thank you.
https://en.qdmana.com/2021/08/20210823075012989d.html

Random recommended