Modular development

advantages

  • In modular development, a file is usually a module, with its own scope, exposed only to specific variables and functions, and loaded on demand.
  • Rely on autoload, load on demand.
  • Improve code reuse rate, facilitate code management, make code management more clear and standard.
  • Reduced naming conflicts and eliminated global variables.
  • Popular JS modular specifications include CommonJS, AMD, CMD and ES6 module systems

Common modular specifications

  • CommonJs (Node.js)
  • AMD (RequireJS)
  • CMD (SeaJS)

CommonJS(Node.js)

CommonJS is the server module specification adopted by Node.js.

According to the CommonJS specification, a single file is a module, and each module is a separate scope. Variables defined in one file (including functions and classes) are private and invisible to other files.

The CommonJS specification loads modules synchronously, meaning that subsequent operations cannot be performed until the load is complete.

In CommonJS, modules are loaded using the require method. This method reads a file and executes, finally returning the exports object inside the file.

Node.js is mainly used for server programming, and the loaded module files are generally stored in the local hard disk, which is fast to load, without considering the asynchronous loading mode, so the CommonJS synchronous loading module specification is more applicable.

However, in a browser environment, to load modules from the server, this must be done in asynchronous mode. So there are AMD, CMD and other solutions.

var x = 5;
var addX = function(value) {
  returnvalue + x; }; module.exports.x = x; module.exports.addX = addX; Module. exports = {x: x, addX: addX,}; // module.exports = {x: x, addX: addX,};Copy the code
let math = require('./math.js');
console.log('math.x',math.x);
console.log('math.addX', math.addX(4));
Copy the code

AMD (RequireJS) asynchronous module definition

  • AMD = Asynchronous Module Definition, i.e.,Asynchronous module definition.
  • AMDCanonical module loading is asynchronous and allows function callbacks to be performed without waiting until all modules have been loaded.
  • AMDIn the userequireGet the dependency module usingexportsexportAPI.
// Specification API define(id? , dependencies? , factory); define.amd = {}; // Define a module without dependencies ({add:function(x,y){
        returnx + y; }}); // Define dependency module define(["alpha"].function(alpha){
    return {
        verb: function() {returnalpha.verb() + 1; }}});Copy the code

Asynchronous loading and callbacks

In require([module], callback), callback is a callback function after the module is loaded

// Load the math module and execute the callback function require(['math'].function(math) {   math.add(2, 3); });Copy the code

RequireJS

RequireJS is a front-end modular management tool library that follows the AMD specification. RequireJS is an elaboration of the AMD specification.

The basic idea of RequireJS is to load all required or dependent modules in a single function, and then return a new function (module). All subsequent business code for the new module operates inside this function.

RequireJS requires each module to be in a separate file, defined using define and invoked using the require method.

Depending on whether it depends on other modules, it can be divided into independent modules and non-independent modules.

  • Independent module, independent of other modules, directly defined
define({
    method1: function(){},
    method2: function() {}}); // equivalent to define(function() {
    return {
        method1: function(){},
        method2: function() {}}});Copy the code
  • An independent module that depends on other modules
define([ 'module1'.'module2'].function(m1, m2) { ... }); // equivalent to define(function(require) {
    var m1 = require('module1');
    var m2 = require('module2'); . });Copy the code
  • requireMethod call module
require(['foo'.'bar'].function(foo, bar) {
    foo.func();
    bar.func();
});
Copy the code

CMD (SeaJS)

CMD = Common Module Definition. CMD is the normalized output of SeaJS module definitions during the roll-out process.

The CMD specification is similar to AMD in that it runs primarily on the browser side and looks similar when written. The main difference is in module initialization timing

  • In AMD, modules are loaded and initialized whenever they are dependencies
  • In CMD, modules are initialized only when they are referenced as dependencies, otherwise they are only loaded.
  • CMDAdvocate relying on proximity,AMDRely on the front.
  • AMDAPIThe default is one when used multiple times,CMDRigid distinctions favour single responsibilities. For example,AMDrequireThere are global and local. There is no global in CMDrequireTo provideseajs.use()To realize the loading of the module system.CMDIn eachAPISimple and pure.
//AMD
define(['./a'.'./b'].function(a, b) {// rely on a.test(); b.test(); }); //CMD define(function{// requie, exports, module) {var a = require(requie, exports, module) {'./a'); a.test(); . / / soft dependencyif (status) {
        var b = requie('./b'); b.test(); }});Copy the code

Sea.js

  • Sea.js Github Page
  • SeaJS is the biggest difference from RequireJS

With sea-js, you need to follow the CMD (Common Module Definition) Module Definition specification when writing files. A file is a module.

usage

  • throughexportsExpose the interface. This means there is no need for namespaces, let alone global variables. This is a radical naming conflict resolution solution.
  • throughrequireIntroduce dependencies. This allows dependencies to be built in, and developers only need to care about the dependencies of the current module, among other thingsSea.jsIt takes care of itself. For module developers, this is a good separation of attention and allows programmers to enjoy coding more.
  • throughdefineDefine modules for more detailsInstitute of SeasJS | geek.

The sample

For example, for the following util.js code

var org = {};
org.CoolSite = {};
org.CoolSite.Utils = {};

org.CoolSite.Utils.each = function(arr) {// implementation code}; org.CoolSite.Utils.log =function(STR) {// implementation code};Copy the code

This can be rewritten using SeaJS as


define(function(require, exports) {
  exports.each = function(arr) {// implementation code}; exports.log =function(STR) {// implementation code}; });Copy the code

Exports provide interfaces to the outside world. Exports exposed through util. Js can be accessed by requiring (‘./util. Js ‘). Here require can be thought of as a syntactic keyword added to the JavaScript language by sea.js. Require can be used to obtain interfaces provided by other modules.

define(function(require, exports) {
  var util = require('./util.js');
  exports.init = function() {// implement code}; });Copy the code

SeaJS is different from RequireJS

The main difference between the two is module initialization timing

  • AMD (RequireJS) loads and initializes modules whenever they are dependencies. That is, execute (rely on) modules as early as possible. This means that all require are written in advance, and the order in which modules are executed may not be 100% the order in which require is written.

  • In CMD (SeaJS), modules are initialized only when they are referenced as dependencies, otherwise they are only loaded. That is, the module is initialized only when it is really needed. Modules are loaded in exactly the order written by require.

In terms of specifications, AMD is more simple and rigorous, with wider applicability. Driven by RequireJS, it has almost become a de facto asynchronous module standard in foreign countries, and various kinds of libraries also support AMD specifications one after another.

But from the perspective of SeaJS and CMD, there are a lot of good things: 1, relatively natural dependency declaration style 2, small and beautiful internal implementation 3, thoughtful peripheral feature design 4, better Chinese community support.

UMD

  • UMD = Universal Module Definition, the generic module definition.UMDAMDCommonJSIn the mix.

AMD modules evolve on a browser-first basis, loading modules asynchronously. CommonJS modules evolve with server first principles and choose synchronous loading. Its modules need not be unwrapped. This forced people to come up with another more generic pattern, UMD (Universal Module Definition), to implement a cross-platform solution.

  • UMDCheck whether support is supportedNode.jsThe module (exports) If yes, useNode.jsModule mode. Then decide whether to support itAMD(defineIf yes, useAMDMode to load modules.
(function (window, factory) {
    if (typeof exports === 'object') {
     
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
     
        define(factory);
    } else {
     
        window.eventUtil = factory();
    }
})(this, function () {
    //module ...
});
Copy the code

ES6 module

ES6 modules are different from CommonJS

  • The ES6 module outputs a reference to a value, the output interface is dynamically bound, andCommonJSThe output is a copy of the value.
  • CommonJSModules are loaded at run time and ES6 modules are compile-time output interfaces.

CommonJS copy of the output value

The CommonJS module outputs a copy of a value (analogous to assignment for primitive and reference types). For base types, once printed, changes within the module do not affect this value. For a reference type, the effect is the same as the assignment operation for the reference type.

// lib.js
var counter = 3;
var obj = {
    name: 'David'
};

function changeValue() {
    counter++;
    obj.name = 'Peter';
};

module.exports = {
    counter: counter,
    obj: obj,
    changeValue: changeValue,
};
Copy the code
// main.js
var mod = require('./lib');

console.log(mod.counter);  // 3
console.log(mod.obj.name);  //  'David'
mod.changeValue();
console.log(mod.counter);  // 3
console.log(mod.obj.name);  //  'Peter'

// Or
console.log(require('./lib').counter);  // 3
console.log(require('./lib').obj.name);  //  'Peter'
Copy the code
  • counterIs a basic type value. Changes in the value inside the module do not affect changes in the output value.
  • objIs a reference type value. Changes in values within the module affect changes in the output value.
  • These two differences are analogous to the assignment operations for primitive and reference types.

You can also use getters to convert counter to a value of reference type, with the following effect.

Inside a class, you can use the get and set keywords to set the memory and value functions on a property and intercept the access behavior of that property. – class | nguyen

// lib.js
var counter = 3;
function incCounter() {
  counter++;
}
module.exports = {
  get counter() {
    return counter
  },
  incCounter: incCounter,
};
Copy the code
// main.js
var mod = require('./lib'); console.log(mod.counter); // 3 mod.incCounter(); console.log(mod.counter); / / 4Copy the code

References to ES6 output values

ES6 modules are dynamically associated with the values in the module, the output is worthy of reference. If the original value changes, the import loaded value changes with it.

ES6 modules operate differently from CommonJS. When the JS engine statically analyzes a script and encounters the module load command import, it generates a read-only reference. When the script is actually executed, it will be evaluated in the loaded module based on the read-only reference. In ES6 modules, when the original value changes, the import load value also changes. Therefore, ES6 modules are referenced dynamically and do not cache values. – ES6 Module loaded to implement | nguyen

// lib.js
export let counter = 3;
export function incCounter() {
  counter++;
}

// main.js
import { counter, incCounter } from './lib'; console.log(counter); // 3 incCounter(); console.log(counter); / / 4Copy the code

The CommonJS runtime loads ES6 static compilation

The CommonJS module is run time loaded, and the ES6 module is compile time output interface.

This is because CommonJS loads an object (that is, the module.exports property) that is generated only after the script runs. An ES6 module is not an object, and its external interface is a static definition that is generated during the code static parsing phase.

ES6 module is a compile-time output interface, so it has the following two characteristics

  • importCommands are statically parsed by the JS engine, taking precedence over other content in the module
  • exportCommands have the effect of variable declaration enhancement
Import takes precedence

Importing an import module anywhere in the file will be brought forward to the top of the file

// a.js
console.log('a.js')
import { foo } from './b';

// b.js
export let foo = 1;
console.log('B.js executed first'); // Result: // b.js Is executed first // a.jsCopy the code

Although import is introduced later in module A than console.log(‘a’), it is statically analyzed by the JS engine to the front of the module execution, which is superior to other parts of the module execution.

Export Command variables to improve the effect

Because import and export are statically executed, import and export have variable promotion effects. That is, the position of import and export commands in the module does not affect the output of the program.

// a.js
import { foo } from './b';
console.log('a.js');
export const bar = 1;
export const bar2 = () => {
  console.log('bar2');
}
export function bar3() {
  console.log('bar3');
}

// b.js
export let foo = 1;
import * as a from './a';
console.log(a);

// 执行结果:
// { bar: undefined, bar2: undefined, bar3: [Function: bar3] }
// a.js
Copy the code

Module A references module B, and module B also references module A. The variable declared by export is also superior to the execution of other contents of the module. Assigning a value to a variable will have to wait until the corresponding code is executed.

ES6 modules are similar to CommonJS

Modules are not executed repeatedly

When the same module is introduced repeatedly, the module is executed only once.

Circular dependencies

CommonJS module cyclic dependencies

An important feature of the CommonJS module is that it is executed on load, meaning that when the script code is required, it will all be executed. Once a module is “looping”, only the parts that have been executed are printed, and the parts that have not been executed are not printed.

Demo 1
//a.js
exports.done = false;
var b = require('./b.js');
console.log('in a.js, b.tone = %j', b.done);
exports.done = true;
console.log('A.js completed');
Copy the code

In the above code, the A.js script outputs a done variable and then loads another script file, b.js. Notice that at this point, the A. js code stops here and waits for the completion of b.js execution before proceeding to the next step.

Look again at the code of B. js.

//b.js
exports.done = false;
var a = require('./a.js');
console.log('在 b.js 之中,a.done = %j', a.done);
exports.done = true;
console.log('B.js completed');
Copy the code

In the code above, b.js executes to the second line and loads A.js, at which point “cyclic loading” occurs. The system returns the value of the exports property of the corresponding object of the module A. js. Because a. jS has not been executed, only the executed part can be retrieved from the exports property, not the last value.

The part of a.js that has been executed is only one line.

exports.done = false;
Copy the code

Therefore, for B. js, it enters only one variable done from A. js with a value of false.

Then, B. JS will continue to execute, wait until all the execution is completed, and then return the execution power to A. JS. So, A. JS continues to execute until the execution is complete. Let’s write a script main.js to verify this process.

var a = require('./a.js');
var b = require('./b.js');
console.log('在 main.js 之中, a.done=%j, b.done=%j', a.done, b.done);
Copy the code

Execute main.js, and the result is as follows.

$node main.js in b.js, a.dot =falseB. Js Execution completed in A. Js, b. Don=trueIn main.js, a.stone =true, b.done=true
Copy the code

The above code proves two things

  • inb.js,a.jsOnly the first line is executed
  • main.jsThe second line is not executed againb.jsIt is output cachedb.jsThe execution result of, its fourth line.
exports.done = true;
Copy the code

In general, CommonJS inputs a copy of the output value, not a reference.

Also, because CommonJS modules encounter loop-loading and return the value of the portion of the code that has been executed, rather than the value of the code that has been executed, there may be a difference. So you have to be very careful when you enter variables.

var a = require('a'); Var foo = require(var foo = require('a').foo; // exports.good =function (arg) {
  return a.foo('good', arg); // use the latest value of a.foo}; exports.bad =function (arg) {
  return foo('bad', arg); // use a partially loaded value};Copy the code

In the code above, the value of require(‘a’).foo would probably be overwritten later if looping occurred, and it would be safer to use require(‘a’) instead.

Demo 2
// a.js
console.log('a starting');
exports.done = false;
const b = require('./b');
console.log('in a, b.done =', b.done);
exports.done = true;
console.log('a done');

// b.js
console.log('b starting');
exports.done = false;
const a = require('./a');
console.log('in b, a.done =', a.done);
exports.done = true;
console.log('b done'); // node a.js // Result: // a starting // b starting //in b, a.done = false
// b done
// in a, b.done = true
// a done
Copy the code

In the CommonJS specification, when a require() statement is encountered, the code in the Require module will be executed, and the result of execution will be cached. The next load will not be repeated, but will directly fetch the cached result. Because of this, there is no case of infinite loop calls when loop dependencies occur.

ES6 modules have cyclic dependencies

As with CommonJS modules, ES6 no longer executes re-loaded modules, and thanks to the dynamic output binding feature of ES6, ES6 ensures that the current values of other modules are available at any time.

Dynamic import ()

ES6 modules are statically analyzed at compile time, taking precedence over the rest of the module, which makes it impossible to write code like the one below

if(some condition) {
  import a from './a';
}else {
  import b from './b';
}

// or 
import a from (str + 'b');
Copy the code

Because of compile-time static analysis, we could not concatenate string modules in conditional statements or because these are results that need to be determined at runtime in ES6 modules, so dynamic import() was introduced.

Import () allows you to dynamically import ES6 modules at runtime. When you think of this, you might also think of the require.ensure syntax, but their purposes are quite different.

Require. Ensure was created as a webpack outgrowth because browsers needed an asynchronous mechanism to load modules asynchronously to reduce the size of the initial load file, so requiring. Ensure was useless on the server side. Because the server does not load modules asynchronously, modules can be loaded synchronously to meet the application scenario. The CommonJS module can verify that the module is loaded at run time.

Import (), on the other hand, is designed to address the fact that ES6 modules cannot determine module references at runtime, so import() is introduced.

Let’s see how it’s used

  • dynamicimport()Provide a basis forPromiseAPI
  • dynamicimport()It can be used anywhere in the scriptimport()Accepts string literals, and specifiers can be constructed as needed
// a.js
const str = './b';
const flag = true;
if(flag) {
  import('./b').then(({foo}) => {
    console.log(foo);
  })
}
import(str).then(({foo}) => {
  console.log(foo);
})

// b.js
export const foo = 'foo'; // babel-node a.js // execution result // foo // fooCopy the code

Of course, if the import() function on the browser side becomes more versatile, such as asynchronously loading modules on demand, it would be similar to require.ensure.

Since it’s Promise based, if you want to load multiple modules at the same time, you can use promise.all for parallel asynchronous loading.

Promise.all([
  import('./a.js'),
  import('./b.js'),
  import('./c.js'),
]).then(([a, {default: b}, {c}]) => {
    console.log('a.js is loaded dynamically');
    console.log('b.js is loaded dynamically');
    console.log('c.js is loaded dynamically');
});
Copy the code

There’s also the promise.race method, which checks which Promise is resolved or reject first. We can use import() to check which CDN is faster:

const CDNs = [
  {
    name: 'jQuery.com',
    url: 'https://code.jquery.com/jquery-3.1.1.min.js'
  },
  {
    name: 'googleapis.com',
    url: 'https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js'}]; console.log(`------`); console.log(`jQuery is:${window.jQuery}`);

Promise.race([
  import(CDNs[0].url).then(()=>console.log(CDNs[0].name, 'loaded')),
  import(CDNs[1].url).then(()=>console.log(CDNs[1].name, 'loaded'))
]).then(()=> {
  console.log(`jQuery version: ${window.jQuery.fn.jquery}`);
});
Copy the code

Of course, if you think this is not elegant enough, you can also use it with async/await syntactic sugar.

async function main() {
  const myModule = await import('./myModule.js');
  const {export1, export2} = await import('./myModule.js');
  const [module1, module2, module3] =
    await Promise.all([
      import('./module1.js'),
      import('./module2.js'),
      import('./module3.js'),]); }Copy the code

Dynamic import() gives us the additional capability of using the ES module in an asynchronous manner.

Loading them dynamically or conditionally according to our needs allows us to create more advantageous applications faster and better.

Webpack | loading three module in grammar

Webpack allows different module types, but the underlying implementation must be the same. All modules can run directly out of the box.

  • ES6 module
import MyModule from './MyModule.js';
Copy the code
  • CommonJS(Require)
var MyModule = require('./MyModule.js');
Copy the code
  • AMD
define(['./MyModule.js'].function (MyModule) {
});
Copy the code

The resources

  • AMD, CMD, CommonJS and UMD | Segmentfault
  • JS modular loading of CommonJS, AMD, CMD, ES6
  • ES6 module loading and implement | nguyen
  • Front end modular development scheme small contrast