preface
With the booming development of Web technology and the improvement of the infrastructure on which it depends, the front-end domain gradually extends from the browser to the server side (Node.js), the desktop side (PC, Android, iOS), and even the Internet of Things devices (IoT), where JavaScript hosts the core part of these applications. As its scale and complexity increase exponentially, so does its software engineering system (collaborative development, unit testing, requirements and defect management, etc.), and the need for modular programming becomes more and more urgent.
JavaScript support for modular programming has not yet been standardized enough to be worthy of this task; For a time, the river’s lake chivalry to come forward, all the way, from slash-and-burn transition to future-oriented modular solutions;
concept
Modular programming is to realize functions by combining some relatively independent reusable modules __. The two core parts of __ are __ define module __ and __ introduce module __.
- When a module is defined, the execution logic inside each module is not perceived by the outside, and only some methods and data are exported (exposed).
- When a module is introduced, the code to be introduced is loaded synchronously/asynchronously, and the exposed methods and data are executed and retrieved.
This process
Although JavaScript language level does not provide modular solutions, but the use of its object-oriented __ language features, plus __ design pattern __, can realize some simple modular architecture; A classic case is the use of singleton mode mode to achieve modularization, can be better encapsulation of the module, only part of the information exposed to the need to use the module;
// Define a module
var moduleA = (function ($, doc) {
var methodA = function() {};
var dataA = {};
return {
methodA: methodA,
dataA: dataA
};
})(jQuery, document);
// Use a module
var result = moduleA.mehodA();
Copy the code
Intuitively, declaring dependencies and exporting data through immediate execution of functions (IIFE) is not radically different from today’s modular solutions, but it is fundamentally different and cannot meet some important features;
- When defining a module, the declared dependencies are not forced to be imported automatically, that is, before defining the module, the dependent module code must be imported manually.
- When a module is defined, its code has already been executed and cannot be loaded on demand.
- When using modules across files, you need to mount the module to a global variable (window).
AMD and CMD split the world
Off-topic: Due to the ages, these two modular schemes gradually fade out of the historical stage, the specific characteristics are no longer detailed;
In order to solve the “Slash-and-burn” era of the remaining needs, AMD and CMD modular specification came out, to solve the browser side of the asynchronous modular programming requirements, its core principle is through the dynamic loading script and event monitoring to load modules asynchronously;
The two most representative works of AMD and CMD correspond to require.js and sea-.js respectively. The main difference lies in the timing of dependency declaration and dependency loading. Require.js is executed at declaration time by default, while sea-.js advocates lazy loading and on-demand use. It is also worth noting that the CMD specification is written very similar to CommonJS and can be used in CommonJS with only minor modifications. Refer to the following Case for better understanding;
// AMD
define(['./a'.'./b'].function (moduleA, moduleB) {
// Rely on prefixes
moduleA.mehodA();
console.log(moduleB.dataB);
// Export data
return {};
});
// CMD
define(function (requie, exports, module) {
// Rely on nearby
var moduleA = require('./a');
moduleA.mehodA();
// Load as needed
if (needModuleB) {
var moduleB = requie('./b');
moduleB.methodB();
}
// Export data
exports = {};
});
Copy the code
CommonJS
In 2009, RY released the first version of Node.js. CommonJS, as one of the most core features, is suitable for scenarios on the server side. CommonJS is widely used in Node.js and browsers, thanks to the support of front-end engineering.
// Core Module
const cp = require('child_process');
// Npm Module
const axios = require('axios');
// Custom Module
const foo = require('./foo');
module.exports = { axios };
exports.foo = foo;
Copy the code
specification
- Module (Object): the module itself
- Exports (*): The exported part of a module, that is, the exposed content
- Require (Function): a Function that loads modules, obtains the exported value of the target module (base type is copy, reference type is shallow copy), and can load built-in modules, NPM modules, and custom modules
implementation
1. Module definition
By default, any.node.js. json file is a conforming module;
2. Introduce modules
The module is first read from the cache (require.cache), and if it misses the cache, the path is analyzed, and then the module is processed according to different types:
- Built-in modules, loaded directly from memory;
- External module, the first file addressing location, and then compile and execute, finally get the corresponding export value;
In the compilation process, Node wraps the content of the obtained JavaScript file at the beginning and end, resulting in the following:
(function (exports, require, module, __filename, __dirname) {
var circle = require('./circle.js');
console.log('The area of a circle of radius 4 is ' + circle.area(4));
});
Copy the code
Characteristics of summary
- Synchronous implementation of module declarations and import logic, analysis of some complex dependency references (such as circular dependencies) need to pay attention to;
- Cache mechanism, better performance, while limiting the memory footprint;
- Module Module can be modified with high flexibility, can realize some customization requirements (such as hot update, arbitrary file type Module support);
ES Module (Recommended)
ES Module is a modularization scheme at the language level, proposed by ES 2015. Compared with CommonJS, the exported value can be regarded as an object with multiple attributes or methods, which can be compatible with each other. But ES Module is more concise, similar to Python;
import fs from 'fs';
import color from 'color';
import service, { getArticles } from '.. /service';
export default service;
export const getArticles = getArticles;
Copy the code
The main differences are:
- ES Module does static code analysis, that is, loading modules at compile time, and determines dependencies before runtime (to solve the problem of circular references);
- ES Module keywords:
import
export
And uniquedefault
Keyword to determine the default export value; - The exported value in ES Module is one
A reference to a read-only value
In CommonJS, require copies of values, where complex types are shallow copies of values.
// a.js
export let a = 1;
export function caculate() {
a++;
};
// b.js
import { a, caculate } from 'a.js';
console.log(a); / / 1
caculate();
console.log(a); / / 2
a = 2; // Syntax Error: "a" is read-only
Copy the code
UMD
Through a layer of self-executing functions to compatible with a variety of modular specification writing, compatible with AMD/CMD/CommonJS and other modular specifications, paste code is better than thousands of words, need to pay special attention to is ES Module because of the static code analysis, so this runtime scheme can not be used, In this case, it is compatible with CommonJS;
(function (global, factory) {
if (typeof exports === 'object') {
module.exports = factory();
} else if (typeof define === 'function' && define.amd) {
define(factory);
} else {
this.eventUtil = factory();
}
})(this.function (exports) {
// Define Module
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = 42;
});
Copy the code
The implementation in the build tool
In order to run modular code in the browser environment, it is necessary to use some modular packaging tools for packaging (take Webpack as an example). After defining the project entry, dependency analysis will be carried out quickly, and then all dependent modules will be converted into browser-compatible implementations of the corresponding modular specification.
The foundation of modularity
From the above introduction, we have a certain understanding of its specification and implementation; Module/exports/require/global/CommonJS/module/exports/require/global Therefore, file positioning in the require process needs to be modified to load the corresponding JS fragment (webpack takes the approach of introducing dependencies through function passing). For details, see tiny-browser-require.
A snapshot of the code packaged by WebPack is shown below. Note the timing in the comments;
(function (modules) {
// The module cache
var installedModules = {};
// The require function
function __webpack_require__(moduleId) {}
return __webpack_require__(0); / / -- > 0({})0: function (module, exports, __webpack_require__) {
// Define module A
var moduleB = __webpack_require__(1); / / -- > 1
},
1: function (module, exports, __webpack_require__) {
// Define module B
exports = {}; / / -- > 2}});Copy the code
In fact, ES Module is handled in much the same way as CommonJS, except that the __esModule flag is handled when defining and importing modules to accommodate syntactic differences.
Asynchrony and extension
1. In the browser environment, network resources are greatly limited, so if the packaged file is huge, it will cause great loss of page performance. Therefore, it is necessary to split the constructed object file, and the module also needs to support dynamic loading;
Webpack provides two methods require.ensure() and import() (recommended) to dynamically load modules. Similar to AMD & CMD above, import() returns a Promise object, All it does is dynamically add script tags, which are further processed by the onload/onError event.
2. Since the require function is completely custom, we can implement more features in modularity, such as extending the supported file types by modifying require.resolve or module. _extensions. Make CSS /.jsx /.vue/images available for modularization;
Appendix 1: List of features
Modular specification | Loading way | Loading time | Runtime environment | note |
---|---|---|---|---|
AMD | asynchronous | The runtime | The browser | |
CMD | asynchronous | The runtime | The browser | Rely on based onStatic analysis, require already module ready |
CommonJS | Synchronous/asynchronous | The runtime | Browser/Node | |
ES Module | Synchronous/asynchronous | Compilation phase | Browser/Node | Asynchronous loading via import() |
Appendix 2: Reference
- AMD Modular specification: github.com/amdjs/amdjs…
- CMD module definition specification: github.com/seajs/seajs…
- Webpack module related documents: webpack.js.org/concepts/mo…
- Principle and implementation of browser loads CommonJS module: www.ruanyifeng.com/blog/2015/0…