Front-end modularization is an indispensable part of front-end engineering.

preface

Unlike 10 years ago, when you could solve everything with jQuery alone, today’s front-end ecosystem is full of flowers. There are frameworks like Vue, React, Angular, and a bunch of familiar UI frameworks like Antd and ElementUI. The engineering files behind these projects are mostly thousands of snippets of code, and if we need to use them in our own projects, It is very convenient to import these modules as a whole with only a script tag or an import. This is the modularity of the front end.

A universal technology product must have a strict specification. Oshiba had been exposed to various specifications such as AMD, CMD, ES Module, etc., but never thought about the difference between these specifications, let alone what happened behind the require or import in the project. This article focuses on the specification and evolution of modularization, I hope you can have a more comprehensive understanding of front-end modularization after reading

First, the connotation of modularity

1. Why modularity?

Small firewood wrote a code, used to calculate the average score of the class, small ha also wrote a method to calculate the class performance index, small ha want to use the code of small firewood in their own method, this time how to do?

1.1 No Modularization

<script>
  // Kobayashi calculates the average score code
  function calAverageScore() {
    // ...
  };
  // Small hash code
  function calTotalScore() {
    // ...
    calAverageScore();
  }
</script><script type="text/javascript" src="calAverageScore.js"></script>
<script type="text/javascript" src="calTotalScore.js"></script>
Copy the code

In the age of unmodularity, developers who want to reference other people’s code can either copy their own code to their own code, or import JS files using tools such as Ajax. There are two problems:

  • Easy to cause global variable pollution
  • If there are dependencies, managing them can be a disaster

Our methods will inevitably define some global variables, when the third party to find the integration of two methods to define the same global variables on the tragedy, you need to manually change, this is undoubtedly an increase in trouble. The second reason is that assuming that A’s code depends on B, and B’s code depends on C, this requires developers to manage these code files in order.

1.2 Early solutions

  • The namespace
let myModule = {
  data: 'chaichaiCoding'.foo() {
    console.log(`foo() The ${this.data}`)},bar() {
    console.log(`bar() The ${this.data}`)}}Copy the code

The function of namespaces is to converge global variables into an object. This can solve the problem of global variable contamination, but the problem is also obvious, that internal variables can be changed externally, and the data is not secure. That’s when smart people come up with closures.

  • Closures (immediate execution of IIFE anonymous functions)
(function(window, $) {
  let data = 'chaichaiCoding'
  function foo() {}function bar() {
    otherFun() // Internal call
  }
  function otherFun() {
    // Internal private function
    console.log('otherFun()')}// Mount to window
  window.myModule = { foo, bar }
})(window, jQuery)

// IIFE enhancements to reference other libraries as dependencies and to ensure that internal variables cannot be changed externally due to the nature of closures
Copy the code

This completes the purpose of module encapsulation, as jQuery famously does. To sum up, some key points of front-end modularization are summarized as follows:

Two, modular specifications

The presentation of a module is nothing more than its definition and use. The following pages will explain the differences between CommonJS(CJS), AMD, CMD, and the latest ES Module(ESM) in order.

1.CommonJS and Node stuff

Thanks to Node, JavaScript can also write server-side code. Modularity is essential on the server side, so the CommonJS specification came into being.

  • 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

— Park Ling, Nodejs

In CommonJS, each file is treated as a separate module. For example, suppose a file named foo.js:

const circle = require('./circle.js');
console.log(`The area of a circle of radius 4 is ${circle.area(4)}`);
Copy the code

On the first line, foo.js loads the module circ.js in the same directory as foo.js.

Here’s what circle.js looks like:

const { PI } = Math;

exports.area = (r) = > PI * r ** 2;

exports.circumference = (r) = > 2 * PI * r;
Copy the code

The module circ.js has exported functions area() and circumference(). Add functions and objects to the root of the module by specifying additional properties on special exports objects. This allows us to import modules that calculate the circumference and area of a circle, allowing us to calculate relevant information in our own modules. In browsers, we can use packaging tools such as Browserify or WebPack to put various require files into a bundle for the browser to call.

2.AMD and RequreJS stuff

We talked about the CommonJS specification in Node above, but it’s important to note that this specification is inherently inappropriate for browsers because it’s synchronous. On the server all kinds of files in the local, restricted is hard to read and write speed, and in the browser, the browser every load a file, to send the request to get network, if the speed is slow, it is very time consuming, the browser will always require return, such as would have been stuck in there, behind the block code execution, thereby blocking the page rendering, Renders the page in a state of suspended animation. At this time, the network speed is limited. Hence the Asynchronous Module Definition (AMD)**.

Take a look at the implementation of the AMD specification:

<script src="require.js"></script>
<script src="a.js"></script>
Copy the code

First we introduce require.js library, which provides functions for defining modules, loading modules, and so on. It provides a global define function to define modules. So any other files imported after require.js can use define to define the module.

define(id? , dependencies? , factory)Copy the code
  • Id: This parameter is optional and defines the id of the module.

  • Dependencies: An optional parameter. It is an array of dependencies of the current module.

  • Factory: Factory method, callback function or object executed after module initialization. If it is a function, it should be executed only once, returning the value that the module exports. If it is an object, this object should be the output value of the module.

    So module A can be defined this way

// a.js
define(function(){
    var PI = 3.14159
    return {
        PI,
        getArea: (r) = > PI * r ** 2}})// b.js
define(['a.js'].function(a){
    var name = 'chaichaiCoding'
    console.log(a.PI) / / '3.14159'
    console.log(a.getArea(4)) 
    return {
        name,
    }
})
Copy the code

The module is loaded asynchronously. In other words, the callback function is executed only after all the modules corresponding to the dependencies array are loaded. The module loading does not affect the following statements.

The basic idea of RequireJS is to define code as a module through the define method. When the module is required, it starts loading its dependent modules, and when all dependent modules have been loaded, it starts executing the callback function. Unlike the SeaJs mentioned below, it relies on the front end.

CMD and SeaJS

Like AMD, CMD is the normalized output of the module definition in the promotion process of Sea-js. Sea.js was written by Ali’s Jade uncle. It was born after RequireJS, and Yub felt that the AMD specification was asynchronous and the organization of modules was not natural and intuitive. So he pursued a writing form like CommonJS. So you have CMD.

Take a look at the implementation of the CMD specification:

<script src="sea.js"></script>
<script src="a.js"></script>
Copy the code

Similar to require.js, we introduce a library of sea.js tools.

// All modules are defined with define
define(function(require.exports.module) {
  // introduce dependencies via require
  var a = require('xxx')
  var b = require('yyy')
  // Provides interfaces through exports
  exports.doSomething = ...
  // The entire interface is available through module.exports
  module.exports = ...
})
// a.js
define(function(require.exports.module){
    var name = 'chaichaiCoding'
    var PI = 3.14159
    exports.name = name
    exports.getArea = (r) = > PI * r ** 2
})
// b.js
define(function(require.exports.module){
    var name = 'xiaoha'
    var r = 4
    var a = require('a.js')

    console.log(a.name) // 'chaichaiCoding'
    console.log(a.getArea(r)) // Calculate the area with radius 4

    exports.name = name
    exports.getArea = () = > a.getArea(r)
})
Copy the code

The secret of sea-js being able to write module code synchronously like CommonsJS is that when b.js modules arerequireWhen b.js is loaded, sea-js will scan b.js code and findrequireThis keyword extracts all dependencies and loads them. When all dependencies are loaded, the callback function is executedrequire('a.js')At the time of this line of code, A.js is already loaded into memory

4. The ESM thing

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.

Take a look at the implementation of the ESM specification:

// a.js

export const name = 'ChaiChaiCoding'
const PI = 3.14159
export function getArea (r) {
    return PI * r ** 2
}

/ / equivalent to the
const name = 'ChaiChaiCoding'
const PI = 3.14159
export function getArea (r) {
    return PI * r ** 2
}
export {
    PI,
    getArea
}
Copy the code

Once the module’s external interface is defined using the export command, other JavaScript files can load the module with the import command.

// b.js
import { PI, getArea } from 'a.js'
const area = getArea(4)
console.log(area) // PI * 4 ** 2

/ / equivalent to the
import * as a from 'a.js'
console.log(a.PI) / / '3.14159'
const area = getArea(4)
console.log(area) // PI * r ** 2
Copy the code

In addition to specifying that an output value is loaded, you can also use global loading, where you specify an object with an asterisk (*) on which all output values are loaded.

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 module
let { 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.

Third, summary

1.CommonJS is proposed to solve the modularity problem in Node.

2.AMD is in order to solve the browser can not like the server side synchronous loading proposed asynchronous specification, a very important feature is to rely on the front.

3.CMD is a standard proposed by domestic leaders in order to write like CommonJS. It has a very important feature that is the principle of proximity.

4. All three of the above specifications are runtime, while only ESM is compile-time. Fully capable of replacing CommonJS and AMD specifications as a common module solution for browsers and servers.

Welcome to search and follow chai Chai Love Coding wechat official account, where there are free learning resources, a full range of advanced routes, various job interview resources, programming source code will Coding you ~