This article first appeared on blog.flqin.com. If you have any errors, please contact the author. Analysis code word is not easy, reproduced please indicate the source, thank you!

Initialize the module

Connected, in the resolver function callback, trigger normalModuleFactory. Hooks: after afterResolve callback to perform:

let createdModule = this.hooks.createModule.call(result); // result is the composite object data returned by resolver
if(! createdModule) {if(! result.request) {return callback(new Error('Empty dependency (no request)'));
  }

  createdModule = new NormalModule(result);
}
createdModule = this.hooks.module.call(createdModule, result);

return callback(null, createdModule);
Copy the code

Here trigger normalModuleFactory. Hooks: createModule, if there is no project configuration in the hook of the custom module, use the webpack generated module.

Get the module instance, and then trigger normalModuleFactory. Hooks: after the module, jump out of the factory function, after implementation of factory function callback depend on the cache, Exit the create function to execute the moduleFactory.create callback. Execute in callback:

const addModuleResult = this.addModule(module); // Save the 'module' to the global 'Compilation. Modules' array and' _modules' object, and check whether '_modules' has a flag for the module to set whether it is loaded or not
module = addModuleResult.module;

onModule(module); // Modules will also be saved to 'compilation. entries' if it is an entry file

dependency.module = module;
module.addReason(null, dependency); // Add which modules depend on this' module '
Copy the code

We then call this.buildModule to get to the build phase. This method made the callback after the cache, trigger compilation. Hooks: buildModule, then execute the module. The build ().

Build the module

In/node_modules/webpack/lib/NormalModule js file execution module. The build, set some attributes, directly call the enclosing doBuild.

This. CreateLoaderContext gets loaderContext, provides context for all loaders to share, and then calls runLoaders:

runLoaders(
  {
    resource: this.resource,
    loaders: this.loaders,
    context: loaderContext,
    readResource: fs.readFile.bind(fs),
  },
  (err, result) = > {
    / /...});Copy the code

loader-runner

This method comes from loader-runner, which processes the source code through various loaders to get a processed string or buffer (and possibly a sourcemap).

You can also parse custom loaders to write a loader.

The main process is:

RunLoaders -> iteratePitchingLoaders (require each loader in normal order) -> loadLoader (the corresponding loader export function assigned to Loadercontext.loader []. Normal, pitch Function assigned to loaderContext.loader[]. Pitch, Then execute pitch function (if any) -> processResource (convert buffer and set loaderIndex) -> iterateNormalLoaders (execute all loaders in reverse order) -> RunSyncOrAsync (synchronous or asynchronous execution loader)

Pitch function

  • Each loader can mount a pitch function that takes advantage of the Module’s request and does not actually process the Module’s content document.

  • Require loader and execute its pitch method (loadLoader). In the callback after execution, if there are parameters other than err, The iterateNormalLoaders execution passes the remaining unrequired loaders directly to the step of executing the loader. If you want no other parameters, execute iteratePitchingLoaders for the next loader require. As shown in the code:

    if (args.length > 0) {
      loaderContext.loaderIndex--;
      iterateNormalLoaders(options, loaderContext, args, callback);
    } else {
      iteratePitchingLoaders(options, loaderContext, callback);
    }
    Copy the code
  • Execute the normal method of each loader in reverse order.

Core code parsing

// This method requires each loader in order
function iteratePitchingLoaders(options, loaderContext, callback) {
  // Abort after last Loader The processResource method is executed
  if (loaderContext.loaderIndex >= loaderContext.loaders.length) return processResource(options, loaderContext, callback);

  var currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex]; // Select the first loader

  // Iterate recursively reads the next loader after the increment
  if (currentLoaderObject.pitchExecuted) {
    loaderContext.loaderIndex++;
    return iteratePitchingLoaders(options, loaderContext, callback);
  }

  // Load loader module Loads the Loader module
  // The function exported by the corresponding loader is assigned to loaderContext.loader[].normal
  loadLoader(currentLoaderObject, function (err) {
    if (err) {
      loaderContext.cacheable(false);
      return callback(err);
    }
    var fn = currentLoaderObject.pitch; //loadLoader assigns module to loader.normal, pitch to loader.pitch
    currentLoaderObject.pitchExecuted = true;
    if(! fn)return iteratePitchingLoaders(options, loaderContext, callback);

    // If so, start the pitch function and decide whether to continue reading the rest of the loader according to the parameters
    runSyncOrAsync(fn, loaderContext, [loaderContext.remainingRequest, loaderContext.previousRequest, (currentLoaderObject.data = {})], function (err) {
      if (err) return callback(err);
      var args = Array.prototype.slice.call(arguments.1);
      if (args.length > 0) {
        loaderContext.loaderIndex--;
        iterateNormalLoaders(options, loaderContext, args, callback);
      } else{ iteratePitchingLoaders(options, loaderContext, callback); }}); }); }// Convert buffer and set loaderIndex
function processResource(options, loaderContext, callback) {
  // set loader index to last Loader Obtains the index of the last loader
  loaderContext.loaderIndex = loaderContext.loaders.length - 1;

  var resourcePath = loaderContext.resourcePath;
  if (resourcePath) {
    loaderContext.addDependency(resourcePath);
    // Convert to buffer
    options.readResource(resourcePath, function (err, buffer) {
      if (err) return callback(err);
      options.resourceBuffer = buffer; //得到buffer
      iterateNormalLoaders(options, loaderContext, [buffer], callback);
    });
  } else {
    iterateNormalLoaders(options, loaderContext, [null], callback); }}// Execute all loaders in reverse order
function iterateNormalLoaders(options, loaderContext, args, callback) {
  if (loaderContext.loaderIndex < 0) return callback(null, args); // Exit after executing all loaders to execute the iteratePitchingLoaders callback, i.e. the runLoaders callback

  var currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex]; // Obtain the corresponding loader

  // Iterate after subtraction executes the next loader recursively
  if (currentLoaderObject.normalExecuted) {
    loaderContext.loaderIndex--;
    return iterateNormalLoaders(options, loaderContext, args, callback);
  }

  var fn = currentLoaderObject.normal;
  currentLoaderObject.normalExecuted = true;
  if(! fn) {return iterateNormalLoaders(options, loaderContext, args, callback);
  }

  convertArgs(args, currentLoaderObject.raw);

  // Execute the loader function
  runSyncOrAsync(fn, loaderContext, args, function (err) {
    // Loader executes the result callback
    if (err) return callback(err);

    var args = Array.prototype.slice.call(arguments.1); // arg:[] Converts the result for loader (string or buffer+ sourcemap if possible)
    iterateNormalLoaders(options, loaderContext, args, callback); // the result of the transformation is passed in
  });
}

// Execute pitch/loader functions synchronously or asynchronously
function runSyncOrAsync(fn, context, args, callback) {
  var isSync = true;
  var isDone = false;
  var isError = false; // internal error
  var reportedError = false;
  // Asynchronous processing
  context.async = function async() {
    if (isDone) {
      if (reportedError) return; // ignore
      throw new Error('async(): The callback was already called.');
    }
    isSync = false;
    return innerCallback;
  };
  // This method is executed after asynchron, and the result of the loader is passed as an argument
  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); // arguments is loader result, the first value is null, the second is string or buffer, and the third is SourceMap
    } catch (e) {
      / /...}});try {
    var result = (function LOADER_EXECUTION() {
      return fn.apply(context, args); //*** entry: executes the loader function, passing the previous loader execution result ***}) ();if (isSync) {
      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) {
    / /...}}Copy the code

The summary of this chapter

  1. instantiationNormalModuleI get initializedmodule(Method chain:Modulefactory. create callback ->buildModule->module.build->module.doBuild->runLoaders), and thenbuildIn the process of firstrun loaderProcess the source code to get a compiled string orbuffer.
  2. inrun loaderThe procedure is pre – ordered execution of eachloaderpitch“, and then executes each in reverse orderloadernormal.