preface

  • The structure of the article uses [point out the stage goal, and then to solve the problem as the entrance, to solve the idea as the means, small test to deepen the impression] to achieve the goal of this article, if you have a little inspiration, do not waste the heart of this article ^-^

The target

  • Understand how main.js (usually) works in Dist after WebPack compilation;
  • Learn how webPack modularity is implemented and how it is already compatible with multiple modularity standards;

How to package this will be covered in a later series

The key point

Modularity is nothing more than the formulation of import and export rules, Webpack is based on the commonJs specification, so the problem becomes

  1. CommonJS import and export implementation
  2. Es6 and other import/export compatibility (hammary), namely converted to commonJS

CommonJS import and export implementation

Look at the use of
## title.js
module.export = {
    a: 'title'
}
export.b = 'b'

## index.js

let title = require('title');
console.log(title);

Copy the code
The core problem
  1. How to mount module object, epxorts object, require function in module scope
  2. Implement module object, EPxorts object, require function according to commonJS specification
  3. Implement the caching effect of the require method, that is, a module that has been loaded once needs to be loaded again only by taking the previously calculated value
solution
  1. Implement assignment of module functions

    • Functions come naturally to the scope, so we can wrap the user-defined logic with a function (get the contents of the file from the path passed in require, and get a function using the eval package as the function body),

    • The mount is then implemented by calling the module’s corresponding function at module installation, passing three arguments: module, module.exports, and __webpack_require__ (our require)

  2. Implement the commonJS specification

    • Specifies that the Module Object is an Object with three attributes

      • I: indicates the ID of a module
      • L: Whether the module has been loaded
      • Exports: exported objects by modules
      Note: modules passed to Webpack have different descriptions in different versions of Webpack. In 3.xx, they are objects whose module ID is key and val is function. In 4.xx, they are functions directly and module ID is array index. In this paper, object version is used to describe, better understanding, the idea is consistent;Copy the code
    • Module. Exports epxorts is a reference to module.exports

    • The require function is the core module installation function, the core is to create module objects for module installation, as well as cache to avoid cyclic dependency problems, etc., the logic is relatively complicated;

  3. Implementing module caching

    • We can get a module ID for the module. When the module ID is in the cache object (var installedModules = {};), then directly remove; If no, install it.
      • The module Object defined in Webpack is of type Object and has three properties
        • I: indicates the ID of a module
        • L: Whether the module has been loaded
        • Exports: exported objects by modules
Exports is a module. Exports reference, which means that when the reference relationship changes, the assignment of exports may not be onCopy the code
Implementation logic:

First of all, if we go into the entry file, there will naturally be the implementation of the require function, and the require that we mentioned in front of _webpack_require, which is also our core method, which is to generate the corresponding module object,

Module, module.exports,__webpack__require__ is then passed to functions that wrap user-defined logic. This is why we have module, exports, require “globally”. At the same time, the logic assigns values to the derived values;

Finally, the __webpack__require__ function returns module.exports, and the importer gets the exported value of the dependent module.

Show the code (simplified version packaging results)

(function(modules) { // webpackBootstrap Webpack startup script
	// The module cache
	var installedModules = {};
	// The require function
	function __webpack_require__(moduleId) {
    // 1. If it has been loaded, it will return loaded
		// Check if module is in cache
		if(installedModules[moduleId]) {
			returninstalledModules[moduleId].exports; }}// 2. Not loaded
		var module = installedModules[moduleId] = {
			i: moduleId,  / / module id
			l: false.// Whether the load is complete
			exports: {} // Export objects
		};
		// Execute the function corresponding to the moduleId in the passed object
		modules[moduleId].call(module.exports, module.module.exports, __webpack_require__);
		module.l = true;
		return module.exports;
	}
	return __webpack_require__(__webpack_require__.s = "./src/index.js");
})
/ / the refs
({
 "./src/index.js":
(function(module.exports, __webpack_require__) {
  let title = __webpack_require__("./src/title.js");
  console.log(title);
}),

"./src/title.js":
(function(module.exports) {
    module.exports = {
          a: 'title'
    };
    exports.b = 'b'; })});Copy the code
A small test
## index.js
let title =  require('./title');
let {b} = title;
console.log(title,b);

## title.js
module.exports = {
    a: '1'
}
exports.b = 2
Copy the code

What does the console print?

Hammary: es6

Look at the use of
## index.js
import title,{esB} from './title';
console.log(title,esB);

## title.js
export default {
    esA: 1
}
export const esB = 2;

Copy the code
The core problem
  1. A record of the source of the module, so that the importer can distinguish the loading mode
  2. Convert the import/export to commonJS import/export

solution

Key point: The biggest difference between ES and commonJs is that the exported value of define syntax in ES is not associated with the exported value of direct export. Exports and module.exports share the same object (address value), so we can do the following for compatibility

  1. Check whether there are any in the moduleimport export define Es6 module (add the __esModule attribute to true)
  2. When exported, hosted by the exports property of the module objectexportThe exported value of the syntax, and add the default attribute to the module object pointing to the exported value of export default;
  3. When importing, the __esModule attribute is evaluated. If it is an ES6 module, the default value of the module object exports is used. If it is commonJs, it is directly the exports of the module object itself
  4. At this point, we have achieved the modular hammary of ES6
Implementation logic
The premise
  • Define and record es module functions and functions, pass parameters and export values, and label the export values as ES module
/ * * * 1. Change the exports in the call Object. The prototype. ToString. Call the value of the said this is a module Object * 2. Add an __esModule attribute with a value of true to indicate that the module object is es module *@param {*} exports* /
__webpack_require__.r = function(exports) {
	if(typeof Symbol! = ='undefined' && Symbol.toStringTag) {
        // This syntax sets the return value of the toString function
		Object.defineProperty(exports.Symbol.toStringTag, { value: 'Module' });
	}
	Object.defineProperty(exports.'__esModule', { value: true });
};
Copy the code
  • Define value function n to distinguish values according to the exported value type of the module. CommonJS returns export itself, while ES module returns default of export.
    • Where the defined attribute can encapsulate a method D, first check whether there is such attribute (function o), if not, add;
// Determine whether an attribute exists on the object
__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };

// definePropetry defines attributes
__webpack_require__.d = function(exports, name, getter) {
	if(! __webpack_require__.o(exports, name)) {
		Object.defineProperty(exports, name, { enumerable: true.get: getter }); }}; __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;
};
Copy the code
implementation

In module functions when the module contains import Export define

Export module

Called when the export keyword is found

  • Define methods (webpack_require.r), two functions
    • Change in exports in the call Object. The prototype. ToString. Call the value of the said this is a module Object
    • Add an __esModule attribute with a value of true to indicate that the module object is an ES module
  • Define methods (webpack_requireD.), the role
    • willexport const b = 1;Similar syntax conversion defines the property name on the exports object of the module objectb, the value is 1;
    • willexport defaultThe exported value is mounted on the default property of the Exports object of the module object
The import module

When the import keyword is found

  • The __webpack_require__.r call identifies the module source as ES6
  • import xxx fromResolves to value from the default property of the exports object

Show the code

(function(modules){... ({})"./src/index.js":
  (function(module, __webpack_exports__, __webpack_require__) {
      "use strict";
      // Label this module as es module
      __webpack_require__.r(__webpack_exports__);
      var _title__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/ *! ./title */ "./src/title.js");
      // Take the value from the default attribute
      console.log(_title__WEBPACK_IMPORTED_MODULE_0__["default"],_title__WEBPACK_IMPORTED_MODULE_0__["esB"]);
}),

  "./src/title.js":
  (function(module, __webpack_exports__, __webpack_require__) {
      "use strict";
      // Label this module as es module
      __webpack_require__.r(__webpack_exports__);
      __webpack_require__.d(__webpack_exports__, "esB".function() { return esB; });
      // Define the exported value of 'export default' on the default property of the exported object
      __webpack_exports__["default"] = ({
            esA: 1
      });
      const esB = 2;
})})
Copy the code
A small test
// foo.js
const bar = require('./bar.js');
console.log('value of bar:', bar);
module.exports = 'This is foo.js';

// bar.js
const foo = require('./foo.js');
console.log('value of foo:', foo);
module.exports = 'This is bar.js';

// index.js
require('./foo.js');
Copy the code

Consider: What will be printed

The answer:

value of foo: undefined
value of bar: This is bar.js
Copy the code