preface
Front-end modularization is an old topic. Before writing this article, I fumbled through a lot of articles, found that the summary is very good, I want to take advantage of this opportunity to share their own experience, but also to master the knowledge to do a review, but also hope that you are looking at the front-end modular have a new understanding and understanding.
💠First Nugget of this article: A little bit of front-end modularity summary
This paper mainly involves the following knowledge points:
IIFE
CommonJS
AMD
/CMD
/UMD
ES6 differs from CommonJS
Module. exports/exports differs from export default/export
What is modularity
In a word: The process of gradually dividing a project into submodules in order to decouple business code
Here’s an example:
Suppose there is a XXX management platform, which involves a login function, we can treat it as a large module
And because the login function includes the login box, login account type distinction, login verification… Such as the widget
These widgets all serve the login function, so we can again think of these widgets as sub-modules under the larger login function
We call this process of gradually breaking up a large module into sub-modules modular
Why modularity
Historically, the front-end has become more and more complex, requiring higher coding efficiency and maintenance costs. Now with modularity, we can solve these problems in a targeted way
File splitting
To do modularity, the first thing that comes to mind is to start with files. We split large files into different smaller files, but there are problems with that:
Advantages: Modules are independent
Disadvantages: can only be introduced one by one through script tags, variables may conflict, scope problems can not be solved gracefully, low scalability, poor reuse
Solve the problem caused by file split IIFE
Invoked function expression immediately (Infrely-Invoked Function Expression, IIFE)
Calling function expressions immediately allows variables declared in their functions to bypass JavaScript’s variable-top declaration rules. It also prevents new variables from being interpreted as full-domain variables or function names from taking over global variable names
With IIFE, we can safely splice or combine all split files
But what if the files are interdependent? For example, if file B uses A function in file A, how would this dependency be expressed?
The emergence of Node.js and modularity specifications
The reason for the modular specification is partly to solve the dependency problem mentioned earlier, and partly to constrain everyone to write modules in the same way (to code according to the same specification).
Before ES6 came along, some communities developed a series of module loading schemes, typically CommonJS and AMD/CMD/UMD
CommonJS
CommonJS introduces the require mechanism, which allows you to load and use a module in the current file. The ability to import every module we need, right out of the box, helps us solve the problem of scope and module dependencies.
Node.js is the primary practitioner of the CommonJS specification
Features:
- CommonJS loads modules synchronously
- A single file is a module
- Exports within a module is used for exposure, require is used for import
- Used for server modules
Disadvantages:
- Synchronous loading does not work in the browser environment. Synchronization means blocking, and blocking causes the browser to freeze
- You cannot load multiple modules in parallel
// count.js
var msg = 'a + b = ';
function sum(a, b) {
var result = a + b
console.log(msg + result)
}
/ / exposure
module.exports = {
sum: sum
}
Copy the code
// main.js
/ / introduction
var count = require('./count')
count.sum(1.2) // a + b = 3
Copy the code
AMD
AMD (Asynchronous Module Definition) is designed for browser environments
It is, literally, an asynchronous loading module specification that works around the CommonJS problem of only loading synchronously
All statements that depend on this module are defined in a callback function that will not be executed until all the loading is complete
RequireJS is the typical class library that implements this specification, and in fact AMD is the canonical output in its promotion process
Features:
- Suitable for asynchronously loading modules in a browser environment
- Favoring pre-execution of dependencies (i.e., when defining modules, declare all modules that need to be relied on)
Disadvantages:
- All dependencies must be loaded in advance because of dependency preloading. Modules that do not use dependencies in callback functions will still be loaded
CMD
The CMD specification is similar to the AMD specification, except that AMD relies on front-loading and up-front execution, while CMD relies on nearby and delayed execution.
When you need it, you load it
Features:
- Rely on proximity and delay execution
- CMD is used to load dependencies on demand, and then require that module
Differences between CMD and AMD
, | AMD | CMD |
---|---|---|
Dependency handling | Dependencies are front-loaded and executed ahead of time | Rely on proximity and delay execution |
Loading way | asynchronous | asynchronous |
Module loading mode | After all dependencies have been loaded and executed, the callback function is entered to execute the main logic It’s worth notingThe order in which dependencies are written is not necessarily the order in which they are executed, but the main logic must not execute until all dependencies have been executed. |
A dependency is not executed immediately after being loaded. Instead, it executes the corresponding Module when it encounters the require statement,Modules are written in the same order as the execution sequence |
// AMD
define(['a'.'b'].function(a, b) {
a.todo()
// Module B is loaded ahead of time, although b.todo() is not executed
if (false) {
b.todo()
}
})
// CMD
define(function(require, exports, module) {
var a = require('./a') // Execute as needed
a.todo()
if (true) {
var b = require('./b')
b.todo()
}
})
Copy the code
UMD
UMD (Universal Module Definition), it is to make the Module compatible with AMD and CommonJS specifications and appeared, mostly used in the cross-platform solution of Module Definition, and some need to support the browser side and the server side reference of the third party library
First, UMD will determine whether AMD (define) is supported or not. If it is supported, AMD specifications will be used
Otherwise, check whether CommonJS (exports) is supported. If yes, use the CommonJS specification
If none is supported, expose it to Window.
If you look at the code below, it’s actually an IIFE
Factory is a dependency callback that executes after loading and returns a module each time it executes. {a, b, c}
; (function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery'.'lodash'], factory)
} else if (typeof exports === 'object') {
// CommonJS
module.exports = factory(require('jquery'), require('lodash'))}else {
// Browser global variables (root = window)
root.returnExports = factory(root.jQuery, root._)
}
}(this.function ($, _) {
function a(){}
function b(){}
function c(){}
return {
a: a,
b: b,
c: c
}
}));
Copy the code
ES6
ECMAScript6 adds a module architecture definition at the language standard level, which can replace the CommonJS, AMD, CMD and UMD specifications mentioned earlier.
It has become a common module solution for browser and server, and it is the most popular one at present
The modularity of the ES6 module makes it possible to handle module dependencies, as well as imported and exported variables, statically at compile time
For example, CommonJS can only determine these things at runtime.
/ / export
module.exports = {
a, b, c, d, e, f
}
Copy the code
/ / import
const { a, b, c } = require('xxx')
Copy the code
In essence, the whole XXX module was first loaded, and then an object XXX was assigned.
It then reads three properties a, B, and C from the XXX object.
So this loading is essentially"Runtime load"
Because this object is available only at run time, there is no way to do “static optimization on demand” at compile time
If not, the following code should make sense
// const {a, b, c} = require(' XXX ')
const xxx = require('xxx')
const a = xxx.a
const b = xxx.b
const c = xxx.c
Copy the code
With the ES6, it’s a whole different story:
/ / export
export const a = 1
export const b = 2
export const c = 3
export const d = 4
export const e = 4
Copy the code
/ / import
import { a, b, c } from 'xxx'
Copy the code
In essence, a, B and C attributes are loaded from the XXX module, while D and F will not be loaded at all
This loading is"Load at compile time"
That is, ES6 can load modules on demand at compile time, more efficiently than ES6CommonJS
Module loading mode is high
What’s the difference between ES6 and CommonJS?
Differences between ES6 and CommonJS
The CommonJS module exports a copy of the value, the ES6 module exports a reference to the value
The CommonJS module exports a copy of the value, meaning that if the exported file changes the value, the changes in the original module will not affect the value
ES6 modules operate differently from CommonJS.
When a JavaScript engine statically analyzes a script and encounters an import, it generates a read-only reference.
When the script is actually executed, it is evaluated in the loaded module based on this read-only reference.
In other words, if the original value of an IMPORT in ES6 changes, the import load value changes as well.
CommonJS does not. Therefore, ES6 modules are referred to dynamically and do not cache values. Variables in modules are bound to their modules
CommonJS modules are loaded at run time, ES6 modules are loaded at compile time
Runtime loading: CommonJS modules are objects;
That is, the whole module is loaded, an object is generated, and then properties/methods are read from the object. This type of loading is called “runtime loading”.
Load at compile time: ES6 modules are not objects, but code that is explicitly specified to be output through the export command
Import takes the form of static commands, that is, import can specify to load the value of an export rather than load the entire module, which is called “compile time loading”.
module.exports
, exports
和 export
.export default
The difference between
Module. exports, exports and export default
First we know:
module.exports
, exports
Belongs to the CommonJS specification
export
.export default
Belongs to ES6 specification
The module exports and exports
Test in node environment:
// Output: {}
console.log(exports)
Module {id: '.', exports: {},... }
console.log(module)
Copy the code
Module. exports and exports are {}
In fact, both objects point to the same address and exports is a global reference to module.exports
As you can see, every JS file has the following process after it is created:
var module = new Module(...)
var exports = module.exports
Copy the code
Exports and module.exports both point to the same object by default
But the use ofrequire
When you evaluate it, what actually works ismodule.exports
That is, there is nothing wrong with writing it like this:
// Write it correctly
module.exports = {
num: 100
}
Copy the code
If you write it this way, require will not get the value.
// Error
exports = {
num: 100
}
Copy the code
Why require can not get, can see the official explanation code:
function require(/ *... * /) {
const module = { exports: {}};((module, exports) = > {
// Module code here. In this example, define a function.
function someFunc() {}
exports = someFunc;
// At this point, exports is no longer a shortcut to module.exports, and
// this module will still export an empty default object.
module.exports = someFunc;
// At this point, the module will now export someFunc, instead of the
// default object.}) (module.module.exports);
return module.exports;
}
Copy the code
Exports was implemented before Module. exports, and module.exports was returned
If you modify the exports value itself, the module.exports value still points to the original someFunc
So this is the real reason why a new object is assigned to exports that require can’t get
For details, see the official documentation description: Node.js Exports Shortcut
So, the recommended way to do this is to simply assign an object to module.exports and put all the properties that need to be exposed into that object
module.exports = {
num1: 100.num2: 200
}
Copy the code
Or you can just mount the properties one by one to exports instead of assigning a new object to exports
exports.num1 = 100
exports.num2 = 200
Copy the code
The export and export default
When export Default is used
// animal.js
export default {
dog: 'wang wang'.cat: 'meow meow'
}
Copy the code
// main.js
import animal from './animal'
console.log(animal) // {dog: 'woof ', cat:' meow meow '}
// main.js
import { dog, cat } from './animal'
console.log(dog, cat) // undefined undefined
Copy the code
When using the export
// animal.js
export const dog = 'wang wang'
export const cat = 'meow meow'
Copy the code
// main.js
import animal from './animal'
console.log(animal) // undefined
// main.js
import { dog, cat } from './animal'
console.log(dog, cat) // Bark meow meow meow
// main.js
import * as animal from './foo'
console.log(animal) // Object [Module] { dog: [Getter], cat: [Getter] }
console.log(animal.dog) / / wang wang
Copy the code
As can be seen from the above examples:
Export Default can only obtain attributes by importing XXX form ‘XXX’, which cannot be obtained by deconstructing assignments
Export can get attributes by deconstructing assignments and specifying new objects, whereas import XXX form ‘XXX’ cannot
Export, export default and Module. exports
Module. exports and export Default are two different specifications, one CommonJS and one ES6
However, we usually use ES6 for development purposes, and if you use Babel, you may find that ES6 is converted to CommonJS syntax in the result of compiling with Babel
Babel online conversion
We can measure how they convert to each other by taking a look at the export:
Module export
// animal.js
export default {
dog: 'wang wang'.cat: 'meow meow'
}
Copy the code
Packaged results:
// __esModule indicates that this is an ESM module
Object.defineProperty(exports, "__esModule", {
value: true
});
// void 0 is a javascript function that returns undefined
exports["default"] = void 0;
var _default = {
dog: 'wang wang'.cat: 'meow meow'
};
exports["default"] = _default; // equivalent to module.exports. Default = _default
Copy the code
Export default adds the object to module.exports, and the key of the object is default
Module import
Let’s look at imports again:
// main.js
import animal from './animal'
console.log(animal)
console.log(animal.dog)
Copy the code
Packaged results:
var _animal = _interopRequireDefault(require("./animal"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
console.log(_animal["default"]);
console.log(_animal["default"].dog);
Copy the code
As you can see, import determines whether the exported module is from ES6, and if so, obtains the object directly
If it is already from CommonJS, then the object’s key is simply a layer of default
In the end, any code from CommonJS or ES6 will do a.[“default”] syntactic sugar conversion when you use it
conclusion
Const animal = require(‘./animal’).default
With import, the export can be written as module.exports, export default, or export
You see this, guest officer. Give me a thumbs up?
Refer to the article
Differences between ES6 modules and CommonJS modules
IIFE’s – Immediately invoked function expressions