preface

Modularity – this thing can be said in research and development circles, familiar, household name.

I’ve already said it. Why do you want to talk about it today?

As a front-end with just over a year of development experience,

Contact with ES6 import, NodeJS require, webpack import or require. So what’s the difference between them?

CommonJS has heard of nodeJS modular implementation? AMD has heard about CMD, has heard about CMD, has heard about ES6 modularity, WebPack modularity.

Do we really know, when we use it in a project, which specification you are using?

Most importantly, when we require() or import, we need to know what is happening.



The article is long and requires a bit of patience, but it covers everything from front-end modularity from its infancy to the current ES6 and WebPack field.



1 Why is the front-end modular?

Ps: You can skip the first chapter if you have a good foundation
To get to the bottom of modularity, we need to start with its origins in the first place.

1.1 Primitive age without modularization

Js was originally used as a scripting language for simple form validation, animation implementation and so on.

The code is just like this, it’s written directly into the

<script>
 if(true) {... }else{... }for(var i=0; i< 100; i++){
   ...
 }
 document.getElementById('button').onClick = function() {... } </script>Copy the code


1.2 Catastrophic problems caused by code explosion

Then, with the advent of Ajax asynchronous requests, the front-end could do more and more, and the amount of code grew rapidly.

There are also some problems exposed.



Global variable disaster

It’s easy to understand that everyone’s code is in the same scope, and variables defined by different people can be duplicated to create overwrites.

// Suppose Pumbaa defines a variable name ='pengpeng'; // Then we define name ='dingman'; // Later, Pumbaa began to use the variables he definedif(name === 'pengpeng'){
  ...
}Copy the code
This is bad.

The disaster of dependency management

<script type="text/javascript" src="a.js"></script>
<script type="text/javascript" src="b.js"></script>
<script type="text/javascript" src="c.js"></script>Copy the code
If C depends on B, and B depends on C, then the order in which script is introduced must come first. Imagine having dozens of files, and we have to figure out the file dependencies and manually import them in order.

1.3 Early solutions

(1) Closure

moduleA = function() {var a,b;return {
      add: function (c){
         return a + b + c;
      };
   }
}()Copy the code
In this way, the variables inside the function are hidden from the whole world, achieving the purpose of encapsulation, but the outermost module name is still exposed to the whole world. If the module speed increases, there will still be module name conflict.

(2) Namespace

Yahoo’s YUI gets up early

app.tools.moduleA.add = function(c){
   return app.tools.moduleA.a + c;
}Copy the code
There is no doubt that neither method is elegant enough.



So, what exactly does modularity need to solve? Let’s imagine the following possibilities

  • Securely wrap a module’s code to avoid global contamination
  • Uniquely identifies a module
  • Gracefully expose the module API
  • Easy to use modules

2 Server modularization

Nodejs has ushered in a new era of server code written in javascript, which must be modular on the server side.



2.1 Relationship between Nodejs and CommonJS

Here’s a bit about Nodejs and CommonJS.

  • Nodejs modularity can be a mature attitude due to the influence of CommonJS specification
  • On the server side, CommonJS can be written into the project code of various companies in a common manner, due to the excellent performance of Node
  • Node is not fully implemented as a specification, but has made some trade-offs from the module specification and added a few features of its own


The above three points are taken from Simple Nodejs by Ling Park.


2.2 CommonJS Specification Introduction

CommonJS module definition is very simple, mainly divided into three parts: module reference, module definition and module identification

(1) Module reference

var add = require('./add.js');
var config = require('config.js');
var http = require('http');Copy the code
(2) Module definition

module.exports.add = function() {... } module.exports =function () {
  return. }Copy the code
You can import modules in one file and export another

var add = require('./add.js');
module.exports.increment = function () {
  return add(val, 1);
}Copy the code
You may wonder where the module require attribute comes from without defining it. Nodejs modularity will be covered later in the module compilation section, which is briefly described here.

In fact, a file represents a module. A module has its own function scope and its outermost module scope. Module represents this module and exports are module properties. Require is also used in the context of this module to introduce external modules.

(3) Module identification

The module id is the argument to the require() function. The specification looks like this:

  • It has to be a string
  • Can be started with./.. / relative path at the beginning
  • It could be an absolute path
  • You can omit the suffix
CommonJS module specification definition is relatively simple, meaning that the clustering methods and variables are limited to the private scope, while supporting the introduction and export of upstream and downstream modules seamless connection, each module has an independent space, they do not interfere with each other.



2.3 Nodejs modular implementation



Node module in the implementation is not completely according to CommonJS, made a trade-off, added some of its own features.

A file in Node is a module — module

A Module is an instance of a Module

Module constructor in Node:

functionModule (id, parent) {this.id = id; this.exports = {}; this.parent = parent;if(parent && parent.children) {
    parent.children.push(this);
  }
  this.filename = null;
  this.loaded = false; this.children = []; Var module = new module (filename, parent);Copy the code
Exports is the API that the module is exposed to, parent is the parent module, loaded is loaded, CommonJS is loaded at runtime, loaded is loaded and returns an object.

2.4 Node Module Classification

As shown in the figure, Node module is generally divided into two core modules and file modules.

Figure 1 Module classification

Core modules – modules built into Node such as HTTP, PATH, etc. When the source code for Node is compiled, the core modules are compiled together into the binary executable file, and some of the core modules (built-in modules) are loaded directly into memory.



The introduction of a Node module generally goes through three steps

  • Path analysis
  • File location
  • Compile implementation
The core module will omit the file location and compilation execution of these two steps, and in the path analysis will give priority to judgment, loading faster than the general module.

File modules – external modules such as node_modules installed via NPM, or a js file or JSON file written in our project.

The file module introduction process must go through the above three steps.



2.5 How does NodeJS require path analysis, file location and compilation execution?



2.5.1 Path Analysis

As mentioned earlier, both core modules and file modules need to go through the path analysis step. When we require a module, how does Node distinguish between core modules and file modules and locate them?

Node supports the following forms of module identifiers to import modules:

// core module require('http') ---------------------------- // File module // to. The relative path at the beginning, (without the extension) require('./a.js') / /.. The relative path at the beginning, (without the extension) require('.. /b.js'// Absolute path starting with /, (can be without extension) require('/c.js'// External module name require('express'// external module require('codemirror/addon/merge/merge.js');Copy the code
So for this to be all strings,

  • Node preferentially searches memory for matching core modules and does not continue the search if a match is found
(1) For example, when requiring HTTP modules, it will give priority to successful matching from the core module

  • If the core module does not match, it is classified as a file module
(2) With… Require will convert the relative or absolute path to the real path based on the current file path, which is the most common path resolution

(3) the path to the file module form As’ express ‘and’ codemirror/addon/merge/merge. Js’, this module is a special kind of file module, commonly referred to as a custom module.

Finding custom modules takes the most time, because there is a module path for custom modules, and Node will recursively look up the module path in turn.



Module Paths — Node’s module paths are an array. The module paths are stored in the module.paths property.

You can find an NPM or YARN managed project and create a test.js file in the root directory with console.log(module.paths) as follows:

//test.js
console.log(module.paths);Copy the code
Then execute it in the root directory using Node

node test.jsCopy the code
You can see that we have printed out the module path.

Figure 2 Module path

You can see that the module path generation rule is as follows:

  • Node_modules directory in the current path
  • Node_modules directory in the parent directory
  • Parent directory Node_modules directory in the parent directory
  • Recurse up the path to the node_modules directory in the root directory
Custom files such as Express are recursively searched based on the module path.

File location while searching.

2.5.2 Locating files

  • Extension analysis
When we use require, we sometimes omit the extension, so how does Node locate the specific file?

In this case, Node will be matched in the order of.js,.json, and.node. (.node is a compiled C++ extension file.)

If the extension fails to match, it will be treated as a package, which I understand to be an NPM package

  • Packet processing
For packages, Node first looks for package.json (CommonJS package specification) in the current package directory and then parses the package description object through json.parse ().

If the file lacks an extension, it is located according to the extension analysis rule.

If main specifies the filename incorrectly or there is no package.json at all, Node will use index in the package directory as the default filename.

Js, index.json, and index.node.

If the preceding steps fail to locate the node, go to the node_modules directory in the parent directory of the next module until the node_modules directory in the root directory is found. If the node fails to locate the node, an exception is thrown indicating that the module fails to locate the node.

2.5.3 Module Compilation

  • .js file – the fs module reads the file synchronously and compiles it for execution
  • .node file — An extended file written in C/C++ that loads the files generated by the final compilation through the dlopen() method.
  • Json — After reading the file synchronously through the FS module, parse the returned result with json.parse ().
  • Other extension files. They’re all loaded as.js files.
Each compiled file is cached as an index on the module. _cache object to improve secondary import performance.

The CommonJS module includes require, exports, and module variables.

We also know that each Node module has two variables, __filename and __dirname.

In fact, during the compilation of JavaScript modules, Node wraps the contents of the JavaScript file in the header and tail. Added (function (exports, require, module, __filename, __dirname) {\n to the header, while added \n} to the tail); .

So a compiled JS module will be wrapped like this:

(function(exports, require, module, __filename, __dirname){
  var express = require('express'); exports.method =function (params){
   ...
  };
});Copy the code



3. Front-end modularization

The CommonJS specification is based on Node, so the CommonJS specification is for the server.

3.1 What is the difference between front-end and server modularity?

  • The server loads a module and reads it directly from disk or memory, taking negligible time
  • The browser needs to download this file from the server, so if you use CommonJS require to load the module, you need to wait for the code module to download and run before you get the required API.

3.2 Why does CommonJS not apply to front-end modules?

If we require a module in a code module using the CommonJS method, and the module needs to be retrieved from the server via HTTP request, if the network speed is slow and CommonJS is synchronous, the following code will be blocked, which will block the browser rendering page, causing the page to appear in a state of suspended animation.



Therefore, the following AMD specifications were put forward with the promotion of RequireJS. The introduction of asynchronous module loading, which does not block the implementation of the following code, solves the problem of asynchronous module loading of front-end modules.

3.3 Asynchronous Module Definition (AMD) & RequireJS

AMD — The main difference between the asynchronous module loading specification and CommonJS is that the asynchronous module loading process does not affect the subsequent code execution even if the require module is not obtained.

RequireJS – Implementation of the AMD specification. In fact, IT can also be said that AMD is the standardized output of RequireJS module definition in the promotion process.

Module definition:

(1) Independent module definition — a module definition that does not depend on other modules

// Separate module definition define({method1:function() {}
  method2: function() {}}); / / or define (function() {return {
    method1: function() {},
    method2: function() {},}});Copy the code
(2) Dependent modules — module definitions that depend on other modules

define(['math'.'graph'].function(math, graph){
  ...
});Copy the code
Module reference:

require(['a'.'b'].function(a, b){
  a.method();
  b.method();
})Copy the code


3.4 CommonJS and AMD comparison:

  • CommonJS is generally used on the server side and AMD is generally used on the browser client side
  • Both CommonJS and AMD are run time loaded


3.5 What is Run-time loading?

I think there are two points to understand:

  • Both CommonJS and AMD modules can only determine dependencies between modules at run time
  • When require a module, the module is executed first and returns an object that is loaded as a whole
/ / CommonJS modulelet { basename, dirname, parse } = require('path'); / / equivalent to thelet _path = require('path');
let basename = _path.basename, dirname = _path.dirname, parse = _path.parse;Copy the code
The above code essentially loads the path module as a whole, i.e. loads all the methods of path, generates an object, and then reads three methods from that object. This type of loading is called “runtime loading”.

Consider the following example from AMD:

//a.js
define(function(){
  console.log(A. 's execution');
  return {
    hello: function(){
      console.log('hello, a.js'); }}}); //b.js require(['a'].function(a){
  console.log('b.j s execution');
  a.hello();
  $('#b').click(function(){
    b.hello();
  });
});Copy the code
Results obtained when running B.js:

//a.js execute //b.js execute //hello, a.jsCopy the code
It can be seen that when running b.js, the a.js module will be executed first because B.js requires the A.js module. Verify the previous statement that “when require a module, the module will be executed first”.



3.6 CMD(Common Module Definition) & SeaJS

CMD – general module specification, proposed by the domestic yubo.

SeaJS — The implementation of CMD. In fact, CMD can also be said to be the normalized output of module definition in SeaJS promotion process.

The main difference from the AMD specification is in the section that defines modules and dependencies introduced. AMD needs to specify all dependencies when declaring a module, passing the dependencies to the module contents with parameters:

define(['dep1'.'dep2'].function(dep1, dep2){
  return function() {}; })Copy the code
CMD modules are closer to Node’s definition of the CommonJS specification than the AMD module specification:

define(factory);Copy the code
In the dependencies section, CMD supports dynamic import, where require, exports, and module are passed to modules with parameters. If a module needs to be imported, you can call require() at any time, as shown in the following example:

define(function(require, exports, module){var a = require(exports, module)'./a'); // call the a.method() method of module A; })Copy the code
That is to say, compared with AMD, CMD advocates dependence nearby, AMD advocates dependence front.



3.7 Universal Module Definition (UMD) Universal Module specifications

Here is how the codemirror module lib/ codemiror.js is defined:

(function (global, factory) {
   typeof exports === 'object'&& typeof module ! = ='undefined' 
       ? module.exports = factory()          // Node , CommonJS
       : typeof define === 'function'&& define.amd ? define(factory) //AMD CMD : (global.CodeMirror = factory()); }(this, ()function() {... })Copy the code
It can be seen that the so-called compatibility mode is compatible with several common module definition methods.



So far, several common modular specifications for the front end have been mentioned, and one of the modular introductions and exports that we use a lot in our projects is ES6 modularity.



3.8 ES6 module

As mentioned earlier, both CommonJS and AMD are run time loaded. ES6 implements module functions at the level of language specifications and is loaded at compile time. It can completely replace the existing CommonJS and AMD specifications and become a common module solution for browsers and servers. Here is the ES6 module used a lot in our project, so we will explain it in detail.

The ES6 module uses — export

(1) Export a variable

export var name = 'pengpeng';Copy the code
(2) Export a function

export function foo(x, y){}Copy the code
(3) Common export methods (recommended)

// person.js
const name = 'dingman';
const age = '18';
const addr = 'The Forest of Calster';

export { firstName, lastName, year };Copy the code
(4)

const s = 1;
export {
  s as t,
  s as m, 
}Copy the code
The module can be output multiple times using AS.

ES6 modules use — import

(1) General usage

import { name, age } from './person.js';Copy the code
2

import { name as personName } from './person.js';Copy the code
The import command is promoted to the header of the entire module and is executed first. The following command does not generate an error:

getName();

import { getName } from 'person_module';Copy the code
(3) Overall module loading *

//person.js
export name = 'xixi';
exportage = 23; // load import {age, name} from one by one'./person.js'; // load import * as person from'./person.js';
console.log(person.name);
console.log(person.age);Copy the code
The ES6 module uses — export default

In fact, export default is often used in projects. Usually, a Vue component or React component uses the export default command. It should be noted that when export default is used, import does not need to be added with {}. When export default is not used, import must be added with {}, as shown in the following example:

//person.js
export function getName() {... } //my_module import {getName} from'./person.js'; -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- comparison -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - / / person. Jsexport default function getName(){
 ...
}
//my_module
import getName from './person.js';Copy the code
Export default actually exports a variable named default, so it cannot be followed by a variable declaration statement.

/ / errorexport default var a = 1;Copy the code
It is worth noting that we can use both export and export default

//person.js
export name = 'dingman';
export default function getName(){
  ...
}

//my_module
import getName, { name } from './person.js';Copy the code


As mentioned earlier, CommonJS is loaded at runtime, ES6 is loaded at compile time, so what is the essential difference between the two?

3.9 Difference between ES6 module and CommonJS module loading

The ES6 module is designed to be as static as possible, so that the dependencies of the module, as well as the input and output variables, can be determined at compile time. ES6 modules are loaded at compile time. Unlike CommonJS runtime loading (which actually loads an entire object), ES6 modules are not objects. Instead, they are explicitly output using the export command, which also takes the form of static commands:

//ES6 module import {basename, dirname, parse} from'path'; / / CommonJS modulelet { basename, dirname, parse } = require('path');Copy the code
How does this differ from CommonJS module loading?

  • When requiring the path module, CommonJS will actually run the path module once and return an object that will be cached. This object contains all the APIS for the path module. Any subsequent loading of the module will take the cached value, which is the result of the first run, unless manually cleared.
  • ES6 loads only three methods from the Path module and no others, which is called compile-time loading. ES6 can load modules at compile time. When ES6 encounters an import, it does not execute the module as CommonJS does. Instead, it generates a dynamic read-only reference that is evaluated in the module when it is really needed.
Because the CommonJS module outputs a copy of the value, changes in the value within the module do not affect the output value. Do the following based on Node:

//person.js
var age = 18;
module.exports ={
  age: age,
  addAge: function () {
    age++;
  }
} 

//my_module
var person = require('./person.js'); console.log(person.age); person.addAge(); console.log(person.age); // The output is 18, 18Copy the code
You can see that internal age changes do not affect the value of Person. age because the value of Person. age is always a copy of the result from the first run.

Then look at ES6

//person.js
export let age = 18;
export function addAge(){
  age++;
}

//my_module
import { age, addAge } from './person.js'; console.log(age); addAge(); console.log(age); // The output is 18, 19Copy the code

conclusion

Front-end modularity specifications include CommonJS/ AMD/CMD/ES6 modularity. We may only know one of these but we don’t fully understand their history, usage and differences, and what happens when we use require and import. This article gives you a comprehensive summary (I’m just a porter).



PS: Because the article is too long in order to facilitate reading, front-end modular Webpack actual project explanation, in the continuation of front-end modular two – webpack actual project modularization



reference

1. Getting started with ECMAScript 6

2. Piao Ling (2013) Simple And Profound Node.js. Posts and Telecommunications Press, Beijing.

3. JavaScript Standard Reference Tutorial (Alpha)