Deep understanding of webpack require.context

Preface

require.context It's actually a very useful api.But 3-4 years later, many people still don't know how to use it.

And what does this api mainly do for us?It helps us dynamically load the files we want, and is very flexible and powerful (recursive directories).Can do things import can't do.Today I'll take you all to analyze how webpack requires.context is implemented.

Dead work

Before analyzing this api, we need to understand the simplest file that the webpack will compile into.

-- src
    -- index.ts
// index.ts
console.log(123)

After compiling, we can see that the webpack compiles into the following code

// Source https://github.com/MeCKodo/require-context-sourece/blob/master/simple-dist/bundle-only-index.js
 (function(modules) { // webpackBootstrap
     // The module cache
     var installedModules = {};
     // The require function
     function __webpack_require__(moduleId) {
         // Check if module is in cache
         if(installedModules[moduleId]) {
             return installedModules[moduleId].exports;
         }
         // Create a new module (and put it into the cache)
         var module = installedModules[moduleId] = {
             i: moduleId,
             l: false,
             exports: {}
         };
         // Execute the module function
         modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
         // Flag the module as loaded
         module.l = true;
         // Return the exports of the module
         return module.exports;
     }
     // expose the modules object (__webpack_modules__)
     __webpack_require__.m = modules;
     // expose the module cache
     __webpack_require__.c = installedModules;
     // define getter function for harmony exports
     __webpack_require__.d = function(exports, name, getter) {
         if(!__webpack_require__.o(exports, name)) {
             Object.defineProperty(exports, name, {
                 configurable: false,
                 enumerable: true,
                 get: getter
             });
         }
     };
     // define __esModule on exports
     __webpack_require__.r = function(exports) {
         Object.defineProperty(exports, '__esModule', { value: true });
     };
     // getDefaultExport function for compatibility with non-harmony modules
     __webpack_require__.n = function(module) {
         var getter = module && module.__esModule ?
             function getDefault() { return module['default']; } :
             function getModuleExports() { return module; };
         __webpack_require__.d(getter, 'a', getter);
         return getter;
     };
     // Object.prototype.hasOwnProperty.call
     __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
     // __webpack_public_path__
     __webpack_require__.p = "";
     // Load entry module and return exports
     return __webpack_require__(__webpack_require__.s = "./src/index.ts");
 })
 ({
 "./src/index.ts": (function(module, exports) {
      console.log('123');
    })
 });

At first glance it's messy, so in order to sort out the structure, I'll help you get rid of something that doesn't matter to this article.In fact, the main structure is just like this, the code is not much for later understanding, we must carefully look at each line

// Source address https://github.com/MeCKodo/require-context-sourece/blob/master/simple-dist/webpack-main.js

(function(modules) {
  // Cache all loaded modules (files)
  var installedModules = {};
  // Module (File) Loader moduleId is usually the file path
  function __webpack_require__(moduleId) {
    // Walk cache
    if (installedModules[moduleId]) {
      return installedModules[moduleId].exports;
    }
    // Create a new module (and put it into the cache) explained better than I did
    var module = (installedModules[moduleId] = {
      i: moduleId,
      l: false,
      exports: {}
    });
    // Executing our module (file) is now. /src/index.ts and passes in three parameters
    modules[moduleId].call(
      module.exports,
      module,
      module.exports,
      __webpack_require__
    );
    // Flag the module as loaded explained better than I did
    module.l = true;
    // Return the exports of the module explained better than I did
    return module.exports;
  }
  // Start loading entry file
  return __webpack_require__((__webpack_require__.s = './src/index.ts'));
})({
  './src/index.ts': function(module, exports, __webpack_require__) {
    console.log('123');
  }
});

__webpack_require__ Is a module loader, and all of our modules are read and loaded as objects

modules = {
    './src/index.ts': function(module, exports, __webpack_require__) {
       console.log('123');
    }
}

We temporarily refer to such a structure as a module structure object

Positive

Once you understand the body structure, you can write a require.context to see the effect.Let's first add two new TS files and modify our index.ts to test our dynamic loading.

--- src
    --- demos
        --- demo1.ts
        --- demo2.ts
    index.ts
// index.ts
// Later we'll look at why this was written through source analysis
function importAll(contextLoader: __WebpackModuleApi.RequireContext) {
  contextLoader.keys().forEach(id => console.log(contextLoader(id)));
}

const contextLoader = require.context('./demos', true, /\.ts/);
importAll(contextLoader);

Looking at our compiled source code, we found that there are more than one module structure object

// Compiled code address https://github.com/MeCKodo/require-context-sourece/blob/master/simple-dist/contex-sync.js#L82-L113
{
'./src/demos sync recursive \\.ts': function(module, exports, __webpack_require__) {
  var map = {
    './demo1.ts': './src/demos/demo1.ts',
    './demo2.ts': './src/demos/demo2.ts'
  };

  // context loader, which loads modules (files) from previous module loaders 
  function webpackContext(req) {
    var id = webpackContextResolve(req);
    var module = __webpack_require__(id);
    return module;
  }
  
  // Find the true path of a module (file) by moduleId
  // Individuals don't like some variable naming inside a webpack. moduleId compiles it as request
  function webpackContextResolve(req) {
    // id is the real file path
    var id = map[req];
    // To tell you the truth, this wave of operations did not understand, the current guess is that the webpack will compile into a file like 0.js 1.js and if it can't find a misload, send an error
    if (!(id + 1)) {
      // check for number or string
      var e = new Error('Cannot find module "' + req + '".');
      e.code = 'MODULE_NOT_FOUND';
      throw e;
    }
    return id;
  }
  
  // Traverse to get all moduleId s
  webpackContext.keys = function webpackContextKeys() {
    return Object.keys(map);
  };
  // Get File True Path Method
  webpackContext.resolve = webpackContextResolve;
  // The module returns a context loader
  module.exports = webpackContext;
  // moduleId for the module loader
  webpackContext.id = './src/demos sync recursive \\.ts';
}

I wrote detailed comments in the source code.After reading this code, it's easy to understand that the document says require.context returns a function with three API s (webpackContext).

Next let's look at the source code for compiled index.ts

'./src/index.ts': function(module, exports, __webpack_require__) {
  function importAll(contextLoader) {
    contextLoader.keys().forEach(function(id) {
      // Get all moduleId s and load each module through the context loader
      return console.log(contextLoader(id));
    });
  }
  var contextLoader = __webpack_require__(
    './src/demos sync recursive \\.ts'
  );
  importAll(contextLoader);
}

Simply, you can find that require.context is compiled as a u webpack_require_u loader and loads modules with id. /src/demos sync recursive \.ts, sync indicates that we are loading these modules synchronously (we will introduce this parameter later), and recursive indicates that recursive directory lookup is required.From then on, we can fully understand how the webpack builds all the modules and loads them dynamically.

Advanced Deep Exploration of webpack Sources

We know that after webpack version 2.6, when loading modules, you can specify the webpackMode l module loading mode, and there are several ways we can control which modules we want to load.The common model is sync lazy lazy-once eager

So the same applies to require.context, so if you look at @types/webpack-env, it's not difficult to see that it has a fourth parameter.

Briefly speaking

  • sync packages directly into the current file, loads and executes synchronously
  • lazy delayed loading separates individual chunk files
  • lazy-once delayed loading separates individual chunk files and loads code directly in memory the next time.
  • eager does not detach a separate chunk file, but returns promise, which executes code only when promise is called, which can be understood as loading code first, but we can control the delay in executing that part of the code.

The document is here https://webpack.docschina.org....

This part of the documentation is obscure, and the documentation group may not keep up with it, so if we look at the source of the webpack, we can see that there are actually six mode s.

Model type definition
https://github.com/webpack/we...

So how does a webpack actually achieve recursive access to our files?We can find such a line of code in the source address just above.

It's about finding the modules we need.So we follow this line to find specific sources.

This is how require.context loads into our file.It's just fs.readdir.Our module structure object is generated through the context loader after the last file is obtained.Such code is responsible for generating our sync-type context loader.You can see the other five types in detail.

Six types of loading logic and generating module structure objects for context loaders
https://github.com/webpack/we...

summary

1. Learn how the webpack organizes loading a module, how the webpack loader works, and finally how to generate compiled code.

2. Just to find out how require.context works, we found that its third parameter has six mode s, which are not available in the webpack documentation.

3. Starting from a practical API, we explored the implementation principle of the api, and read some webpack source code together.

4. Exploring the nature is much more important than being a porter of the API.You can discover the secrets of the world only if you keep searching for the essence.

Finally, follow this line of thought to learn the code compiled by another six mode ls.

The compiled code in this article is here >>> https://github.com/MeCKodo/re...

Personal Site >>> http://www.meckodo.com

Last perennial recruitment

Xiamen RingCenter Foreign Enterprises, the Top Benefit in Xiamen

5:30 Off Work 5.30 Off Work 5.30 Off Work

Contact me if necessary~

Keywords: Javascript Webpack github

Added by Nulletz on Fri, 26 Jul 2019 06:07:02 +0300