Why do you need front-end modularity

With the rapid development of W3C standards and browsers, front-end modularization will gradually become the basic measure of development. With modular specifications, we can happily build wheels and use them seamlessly.

Based on this, some libraries, tools or special functions have been derived, such as Vite based on the ESModule modularization specification, route lazy loading of build tools, module lazy loading, code-split and so on.

Advantages of modular development

  • Avoid variable naming conflicts due to scope contamination.

    • Assume that The development tasks of Lao Li and Lao Wang are in the same scope

      Lao Li: I want to declare a pageData with ha

      Lao Wang: What a coincidence that I also want to declare a pageData

      Lao Li: Emmmm, Lao Wang, how did you cover the variables I declared if you don’t talk about wude!!

  • Module separation implements on-demand loading of modules to improve project performance.

    • Suppose your project has a performance problem with the first screen load

      Boss: Xiao Wang, our page why load time so long, what optimization plan?

      Xiao Wang: Sir, LET me have a try. I will implement lazy loading of routing page first, then use code-split to package the tool library which is not updated frequently separately into a bundle.js and do Http caching so that I don’t need to request resources repeatedly later.

      Boss: Not bad, Xiao Wang, the speed has really improved a lot. Big bonus at the end of the year

  • Module encapsulation of business functions improves code reusability.

  • Service maintenance is performed on a specific function module rather than the entire service code, improving project maintainability.

Front-end modular growth path

JavaScript is designed for simple logic like page interaction, form processing, and so on. However, with the development of Internet technology, front-end development is becoming more and more complicated. Naming conflicts, cumbersome dependency files and other problems related to modularization specifications began to appear. In order to solve these problems, front-end modularization began to explore the road.

IIFE mode

IIFE (Immediately Invoked Function Expression) invokes the Function Expression Immediately, due to a defect in the SCOPE aspect of JS. JS only has global scope and block-level scope. ES6 only has the concept of block-level scope. Prior to this, attempts were made to use IIFE to remedy this defect and achieve scope isolation.

// moduleA.js  
(function(window) {
 // Private variables
 var minAge = 18
 // Private function
 function goInternetBar(person) {
   if(person.age<minAge){
     console.wran("Minors are forbidden to go to Internet cafes.")}else {
     // Go to Internet cafes to play, but also want to study well, there are so many good things in the world
     person.grade--
   } 
 }
 // Expose module private functions
 window.plugTool = {goInternetBar}
})(window);

// moduleB.js  
(function(window,tool) {
  var student = {
    name:"Xiao Ming".age:19.grade:99
  }
  // Use the goInternetBar method exposed by moduleB
  tool.goInternetBar(student)
  console.log(After Xiao Ming went to an Internet cafe, his grades were:+student.grade)
})(window,plugTool)
Copy the code

Modular specification for elegant standards

The advent of IIFE solves the problem of scope-obfuscating pollution, but it still has many shortcomings and is not elegant enough to write

  • How to safely wrap a module’s code (achieve scope isolation without contaminating any code outside the module)
  • How to uniquely mark a module (to facilitate operations such as module caching)
  • How to elegantly expose module APIS instead of global variables
  • How to clearly show dependencies between modules

CommonJS

CommonJS was originally a modular specification for server applications in Node environments. It states that in Node, each file is a module with its own scope.

grammar

  • global: object that mounts global variables
  • module: object, the current module object
  • require: function, input dependency
  • module.exports: object, the interface exposed to the module
  • exports: object, pointing to by defaultmodule.exportsYou cannot overwrite the value of the property, only mount variables or methods on it
// moduleA.js  
function eat(weight, food) {
  if (food === 'rice') {
    weight++
  } else if (food === 'bread') {
    weight += 2
  }
  return weight
}
// Exposure mode 1:
module.exports = { eat }
// Exposure mode 2:
// exports.eat = eat

// Error exposure:
// exports = { eat }

// moduleB.js 
const tool = require("./moduleA.js")
const person = {
  name: "Zhang".weight: 100
}
const weight = tool.eat(person.weight, "Steamed bread")
console.log(After xiao Ming ate steamed bread, his weight was: + weight)
Copy the code

features

  • The CommonJS modular specification modules are synchronous loading mechanism, load resources are on the local server, the time and speed are no problem. However, on the browser side, CommonJS is obviously not suitable for network and performance reasons.

  • Module exposed out of the value of shallow copy When it is the value of the simple data types, module data inside and outside is independent, even if the module internal data change exposure value is the same When the exposure value for complex data types, the module of internal and external references to the same object, modify the object inside the module properties will affect the external.

    // moduleA.js  
    // Complex data
    var person = { name: "Zhang".age: 18 }
    // Simple data
    var maxAge = 10
    function ageAdd() { person.age++ }
    function maxAgeAdd() {  maxAge++  }
    
    console.log("moduleA is loading");
    module.exports = {
      person, maxAge, ageAdd, maxAgeAdd
    }
    
    // main.js  
    var { person, maxAge, ageAdd, maxAgeAdd } = require("./moduleA.js")
    console.log(`============= init ============`);
    console.log("Person. Age:" + person.age + ", maxAge:" + maxAge);
    // Modify simple and complex data inside the module
    ageAdd()
    maxAgeAdd()
    console.log(`============= age add ============`);
    console.log("Person. Age:" + person.age + ", maxAge:" + maxAge);
    Copy the code

AMD

AMD(Asynchronous Module Definition): Asynchronous loading Module Definition. Given the nature of the browser environment, we need a modular specification for asynchronous loading mechanisms. AMD was one of the early adopters.

AMD syntax Specification

The statement module

Define ([dependency module],function(dependency module exposure object) {... })

// Declare a non-dependent AMD module
define(function(){
   returnExposed objects})// Declare dependent AMD modules
define(["jquery"].function($){
   returnExposed objects})Copy the code

Import dependence

Require ([dependent module],function(dependent module exposed object) {... })

// Reference the module, put the module in []
require(['jquery'.'math'].function($, math){
  var sum = math.add(10.20);
  $("#app").css("background"."pink")});Copy the code

require.js

Require.js is the best practice of the AMD specification, and it mainly addresses the following issues that occur when using the AMD specification:

  • Realize asynchronous loading of JS files to avoid losing the response of web pages
  • Manage dependencies between modules for easy code writing and maintenance.

AMD Specification features

  • Dependencies are front-loaded and executed ahead of time

    In AMD, input dependent statements are promoted to the front. And immediately execute all input dependent modules before anything else
    // moduleA.js  
    define(function () {
      console.log('modelA is loading');
      return {
        getName: function () {
          return 'modelA'; }}; });// moduleB.js  
    define(function () {
      console.log('modelB is loading');
      return {
        getName: function () {
          return 'modelB'; }}; });// main.js  
    define(function (require) {
      var modelA = require('./a');
      console.log(modelA.getName());
      var modelB = require('./b');
      console.log(modelB.getName());
    });
    
    // index.html  
    <body>
      <! Require.js -->
      <script src="https://cdn.bootcdn.net/ajax/libs/require.js/2.3.6/require.min.js"></script>
      <script>
        / / load the main js
        requirejs(['main']);
      </script>
    </body>
    Copy the code

CMD

CMD(Common Module Definition) : Common Module Definition. A file is a module, and you can write module code just like Node.js. It runs primarily in a browser, but it can also run in Node.js.

CMD syntax specification

The statement module

// Declare no dependent modules
define(function(require.exports.module){
  // Exposure mode 1:
  exports.xxx = value
  // Exposure mode 2:
  module.exports = value
})
Copy the code
// Declare dependent modules
define(function(require.exports.module) {
  // Import dependent modules (synchronization)
  var module2 = require('./module2')
  // Introduce dependency modules (asynchronous)
  require.async('./module3'.function (m3) {... })// Expose the module
  exports.xxx = value
})
Copy the code

Import dependence

define(function (require) {
  var mA = require('./moduleA')
  var mB = require('./moduleB')
  mA.sayHello()
  mB.sayHello()
})
Copy the code

CMD specification features

  • Rely on nearby, delayed execution

In the current module, declare imports only when dependencies are needed. The current module code will not continue until the import is successful.

// moduleA.js
define(function (require.exports.module) {
  console.log("moduleA is loading");
  module.exports = {
    getName: function () {
      return "moduleA"}}});// moduleB.js
define(function (require.exports.module) {
  console.log("moduleB is loading");
  module.exports = {
    getName: function () {
      return "moduleB"}}});// main.js  
define(function (require.exports.module) {
  var moduleA = require("./moduleA")
  console.log(moduleA.getName());
  var moduleB = require("./moduleB")
  console.log(moduleB.getName());
});

// index.html
<body>
  <script src="https://cdn.bootcss.com/seajs/3.0.3/sea.js"></script>
  <script>
    seajs.use('./main.js')
  </script>
</body>
Copy the code

The end of the front-end modular era

Front-end modular by IIFE — AMD — CMD — UMD (his appearance in order to solve the compatibility problems of the previous several specifications, this article will not be introduced in detail), these modular specifications are the front-end developers themselves continue to explore the evolution, until ES6 launched the new modular standard ES Modules, Old modular specifications are becoming obsolete and weak.

Here are two interesting articles to read:

Front-end modular development that bit of history

The fall of the CMD specification

ES Modules

ES Modules (ESM) is the official standardized module system for JavaScript. As a JS ESMA standard it will gradually be supported by all browsers. It can run in NodeJS.

grammar

Module exposed

Export default: export by default export: export on demand

var num= 1
function numAdd() {
 num++
}
var count = 10
var name = "Zhang"
export default {num,numAdd}
export count
exportThe name orexport {name,count}
Copy the code

Import dependence

// Import by default
import tool from './moduleA.js';
// Import as needed
import { count } from './moduleA.js';
// compound usage
import tool,{ count } from './moduleA.js';
Copy the code

features

  • Import commands are statically parsed by the JavaScript engine, taking precedence over the rest of the module.

    // moduleB.js
    console.log("moduleB is loading");
    import * as tool from './moduleA.js';
    console.log("tool", tool);
    
    // moduleA.js  
    console.log("moduleA is loading");
    export var num = 1
    
    // index.html  
    <body>
      <script type="module" src="./moduleB.js"></script>
    </body>
    Copy the code

  • The exported value is a reference to the value in the module

// moduleA.js  
// Complex data
var person = { name: "Zhang".age: 18 }
// Simple data
var maxAge = 10
function ageAdd() { person.age++ }
function maxAgeAdd() { maxAge++ }

console.log("moduleA is loading");
export {
  person, maxAge, ageAdd, maxAgeAdd
}

// main.js  
import { person, maxAge, ageAdd, maxAgeAdd } from './moduleA.js';
console.log(`============= init ============`);
console.log("Person. Age:" + person.age + ", maxAge:" + maxAge);

ageAdd()
maxAgeAdd()
console.log(`============= age add ============`);
console.log("Person. Age:" + person.age + ", maxAge:" + maxAge);  

// index.html  
<body>
  <script type="module" src="./main.js"></script>
</body>
Copy the code

ES Modules differ from CommonJS

Differences in exposure values

ES Modules is exposed as a reference to a value. If you call a method outside the module to change the value inside the module, the value exposed in the module will change to a shallow copy of the value in the CommonJS. When the value is changed inside the module, the value exposed will not change if the value is simple. If the value is complex, the exposed value will change.

Different loading mechanisms

CommonJS modules are dynamically imported and loaded at run time. The whole module is loaded in, and then an object is generated to take the exposed data from that object. ES Modules are loaded statically when JS is compiled, and import statements take precedence. Import specifies exposed data to load at compile time