preface

In the early days of JavaScript development, it was all about simple page interaction logic, just a few words; With the advent of The Web2.0 era, Ajax technology is widely used, jQuery and other front-end libraries emerge in an endless stream, and front-end code is increasingly bloated. At this time, modular specification will be considered to manage JS. This article will focus on understanding modularity, why modularity is needed, the advantages and disadvantages of modularity, and the modularity specification. It will also introduce the CommonJS, AMD, ES6, and CMD specifications that are most popular in development. This article tries to stand in the small white Angle, with easy to understand the style of writing to introduce these boring concepts, I hope you read, modular programming has a new understanding and understanding!

It is recommended to download the source code of this article and tap it yourselfGitHub Personal Blog (Complete)

One, modular understanding

What is a module?

  • To encapsulate a complex program into several blocks (files) according to certain rules (specifications) and combine them together
  • The internal data and implementation of the block are private, exposing only some interfaces (methods) to communicate with other external modules

2. The evolution of modularity

  • Global function mode: Encapsulates different functions into different global functions
    • Coding: encapsulates different functions into different global functions
    • Problems: Contaminate the global namespace, cause naming conflicts or data insecurity, and no direct relationship between module members is visible
function m1() {/ /... }function m2() {/ /... }Copy the code
  • Namespace mode: simple object encapsulation
    • Effects: Reduces global variables and resolves name conflicts
    • Problem: Data is not secure (external can directly modify the data inside the module)
let myModule = {
  data: 'www.baidu.com'.foo() {
    console.log(`foo() ${this.data}`)},bar() {
    console.log(`bar() ${this.data}`)
  }
}
myModule.data = 'other data'Mymodule.foo () // foo() other dataCopy the code

Writing this way exposes all module members, and the internal state can be overwritten externally.

  • IIFE mode: Anonymous function self-call (closure)
    • Function: Data is private and can only be manipulated externally by exposed methods
    • Encoding: Encapsulates data and behavior inside a function, exposing the interface by adding attributes to window
    • Problem: What if the current module depends on another module?
// index.html file <scripttype="text/javascript" src="module.js"></script>
<script type="text/javascript"> mymodule.foo () mymodule.bar () console.log(mymodule.data) //undefined can't access the internal module data myModule.data ='xxxx'// not modifying the data myModule.foo() // not changing </script>Copy the code
// The module.js file (function(window) {
  let data = 'www.baidu.com'// Functions that manipulate datafunction foo() {// used to expose the function console.log(' foo()${data}`)}function bar() {// to expose the function console.log(' bar())${data}') otherFun() // internal call}function otherFun() {// the internal private function console.log()'otherFun()')} // Expose behavior window.myModule = {foo, bar} //ES6})(window)Copy the code

The final result is:

  • IIFE pattern enhancement: Introduce dependencies

This is the cornerstone of modern module implementation

// The module.js file (function(window, $) {
  let data = 'www.baidu.com'// Functions that manipulate datafunction foo() {// used to expose the function console.log(' foo()${data}`)
    $('body').css('background'.'red')}function bar() {// to expose the function console.log(' bar())${data}') otherFun() // internal call}function otherFun() {// the internal private function console.log()'otherFun()'Window. myModule = {foo, bar}})(window, jQuery)Copy the code
// index.html file <! -- Imported JS must have a certain order --> <scripttype="text/javascript" src="Jquery - 1.10.1. Js"></script>
  <script type="text/javascript" src="module.js"></script>
  <script type="text/javascript">
    myModule.foo()
  </script>
Copy the code

The above example uses the jquery method to change the background color of the page to red, so you must first introduce the jquery library, which you pass in as a parameter. In addition to ensuring module independence, this makes the dependencies between modules obvious.

3. Benefits of modularity

  • Avoid naming conflicts (reduce namespace pollution)
  • Better separation, load on demand
  • Higher reusability
  • High maintainability

4. Introduce multiple<script>After the emergence of problems

  • Request too much

First we have to rely on multiple modules, which will send multiple requests, resulting in too many requests

  • Relying on the fuzzy

We don’t know exactly what their dependencies are, which means it’s easy to get the load sequence wrong because we don’t know what their dependencies are.

  • Difficult to maintain

The above two reasons lead to difficult maintenance, which is likely to lead to serious problems in the project. Modularity has many benefits, but these problems arise when you need to introduce multiple JS files to a single page. These problems can be solved by modularity specifications. The commonJS, AMD, ES6, and CMD specifications are the most popular in development.

Two, modular specification

1.CommonJS

(1) overview

Node applications are composed of modules and use CommonJS module specifications. Each file is a module and has its own scope. Variables, functions, and classes defined in one file are private and not visible to other files. On the server side, modules are loaded synchronously at runtime. On the browser side, modules need to be pre-compiled and packaged.

(2)

  • All code runs in the module scope and does not contaminate the global scope.
  • A module can be loaded multiple times, but it is only run once on the first load, and then the result is cached, and later loads read the cached result directly. For the module to run again, the cache must be cleared.
  • Modules are loaded in the order in which they appear in the code.

(3) Basic grammar

  • Exposure module:module.exports = valueorexports.xxx = value
  • Import module:require(xxx)In the case of a third-party module, XXX is the module name; For a custom module, XXX indicates the path of the module file

Here we have a question: what exactly is the module that CommonJS exposes? The CommonJS specification states that within each module, the Module variable represents the current module. This variable is an object whose module.exports property is the external interface. If you load a module, you load the module.exports property of that module.

// example.js
var x = 5;
var addX = function (value) {
  return value + x;
};
module.exports.x = x;
module.exports.addX = addX;
Copy the code

The above code exports the variable x and function addX through module.exports.

var example = require('./example.js'); // If the parameter string starts with "./ ", it indicates that a relative path console.log(example.x) is loaded. // 5 console.log(example.addX(1)); / / 6Copy the code

The require command is used to load module files. The basic function of the require command is to read in and execute a JavaScript file, and then return the module’s exports object. If the specified module is not found, an error is reported.

(4) Module loading mechanism

The loading mechanism of the CommonJS module is that the input is a copy of the output value. That is, once a value is printed, changes inside the module cannot affect that value. This is a major difference from ES6 modularity (described below), as shown in the following example:

// lib.js
var counter = 3;
function incCounter() {
  counter++;
}
module.exports = {
  counter: counter,
  incCounter: incCounter,
};

Copy the code

The above code outputs the internal variable counter and the internal method incCounter that overwrites that variable.

// main.js
var counter = require('./lib').counter;
var incCounter = require('./lib').incCounter; console.log(counter); // 3 incCounter(); console.log(counter); / / 3Copy the code

The above code shows that after the counter is printed, the changes inside the lib.js module will not affect the counter. This is because counter is a primitive value and will be cached. You have to write it as a function to get the internal change.

(5) Server implementation

① Download and install Node.js

② Create the project structure

Note: When using NPM init to automatically generate package.json, the package name cannot be in Chinese or uppercase

|-modules
  |-module1.js
  |-module2.js
  |-module3.js
|-app.js
|-package.json
  {
    "name": "commonJS-node"."version": "1.0.0"
  }
Copy the code

③ Download the third-party module

NPM install uniq --save // for array deduplication

(4) Define module code

//module1.js
module.exports = {
  msg: 'module1'.foo() {
    console.log(this.msg)
  }
}
Copy the code
//module2.js
module.exports = function() {
  console.log('module2')}Copy the code
//module3.js
exports.foo = function() {
  console.log('foo() module3')
}
exports.arr = [1, 2, 3, 3, 2]
Copy the code
// app.js file // import third-party libraries, should be placed firstlet uniq = require('uniq')
let module1 = require('./modules/module1')
let module2 = require('./modules/module2')
let module3 = require('./modules/module3')

module1.foo() //module1
module2() //module2
module3.foo() //foo() module3
console.log(uniq(module3.arr)) //[ 1, 2, 3 ]
Copy the code

⑤ Run app.js through node

On the command line, enter node app.js to run the js file

(6) Browser-based implementation (with Browserify)

① Create a project structure

| - js | - dist / / packaging generated file directory | - SRC / / source directory | - module1. Js | - module2. Js | - module3. Js | - app. Js / / application main source file | - index. HTML / / run in the browser |-package.json {"name": "browserify-test"."version": "1.0.0"
  }
Copy the code

(2) download browserify

  • Global: NPM install browserify -g
  • Local: NPM install browserify –save-dev

(3) Define module code (same as server)

Note: to run the index.html file in the browser, you need to use Browserify to package the app.js file. If you introduce app.js directly in the index.html, you will get an error!

(4) Package js

Run browserify js/ SRC /app.js -o js/dist/bundle.js in the root directory

⑤ Page use introduction

2.AMD

The CommonJS specification loads modules synchronously, that is, only after the load is complete can the following operations be performed. The AMD specification loads modules asynchronously, allowing you to specify callback functions. Since Node.js is mainly used for server programming, the module files are usually already on the local disk, so they can be loaded quickly without the need for asynchronous loading, so the CommonJS specification is suitable. However, in a browser environment, to load modules from the server side, asynchronous mode must be used, so the BROWSER side generally uses the AMD specification. In addition, the AMD specification was implemented earlier in the browser than the CommonJS specification.

(1) Basic AMD specification syntax

Define the exposure module:

// Define modules with no dependenciesfunction() {returnModule})Copy the code
// Define the dependent module define(['module1'.'module2'].function(m1, m2){
   returnModule})Copy the code

Introduction of use modules:

require(['module1'.'module2'].function(m1, m2){
   使用m1/m2
})
Copy the code

(2) Does not use AMD specification and use require.js

The benefits of using the AMD specification are illustrated by comparing the two implementations.

  • AMD specifications are not used
// dataservice.js file (function (window) {
  let msg = 'www.baidu.com'
  function getMsg() {
    return msg.toUpperCase()
  }
  window.dataService = {getMsg}
})(window)
Copy the code
// the alerter.js file (function (window, dataService) {
  let name = 'Tom'
  function showMsg() {
    alert(dataService.getMsg() + ', ' + name)
  }
  window.alerter = {showMsg}
})(window, dataService)
Copy the code
// The main.js file (function (alerter) {
  alerter.showMsg()
})(alerter)
Copy the code
<div><h1>Modular Demo 1: Not used AMD(require.js)</h1></div> <scripttype="text/javascript" src="js/modules/dataService.js"></script>
<script type="text/javascript" src="js/modules/alerter.js"></script>
<script type="text/javascript" src="js/main.js"></script>
Copy the code

Finally, the results are as follows:

The disadvantages of this way are obvious: first of all, it will send multiple requests, and secondly, the js file order introduced cannot be mistaken, otherwise it will report an error!

  • Using the require. Js

RequireJS is a tool library for client-side module management. Its module management complies with the AMD specification. The basic idea of RequireJS is to define code as a module through the define method; Through the require method, the code module loading. Here are the steps to implement the AMD specification in the browser:

① Download require.js and introduce it

  • Website:http://www.requirejs.cn/
  • github : https://github.com/requirejs/requirejs

Then import require.js into the project: js/libs/require.js

② Create the project structure

|-js
  |-libs
    |-require.js
  |-modules
    |-alerter.js
    |-dataService.js
  |-main.js
|-index.html
Copy the code

③ Define the module code of require.js

// dataservice.js file // define modules that have no dependencies on define(function() {
  let msg = 'www.baidu.com'
  function getMsg() {
    return msg.toUpperCase()
  }
  return{getMsg} // expose module})Copy the code
// the alerter.js file defines the dependent module define(['dataService'].function(dataService) {
  let name = 'Tom'
  function showMsg() {
    alert(dataService.getMsg() + ', '+ name)} // Expose modulereturn { showMsg }
})
Copy the code
// The main.js file (function() {
  require.config({
    baseUrl: 'js/'Paths: {// Mapping: module name: path alerter:'./modules/alerter'// dataService error:'./modules/dataService'
    }
  })
  require(['alerter'].function(alerter) {
    alerter.showMsg()
  })
})()
Copy the code
// index.html file <! DOCTYPE html> <html> <head> <title>Modular Demo</title> </head> <body> <! -- import require.js and specify the entry to the JS main file --> <script data-main="js/main" src="js/libs/require.js"></script>
  </body>
</html>
Copy the code

④ Introduce the require.js module into the page:

Also, how do you introduce third-party libraries into your project? With just a few modifications to the base of the above code:

// alerter.js file define(['dataService'.'jquery'].function(dataService, $) {
  let name = 'Tom'
  function showMsg() {
    alert(dataService.getMsg() + ', ' + name)
  }
  $('body').css('background'.'green') // Expose the modulereturn { showMsg }
})
Copy the code
// The main.js file (function() {
  require.config({
    baseUrl: 'js/'Paths: {// custom module alerter:'./modules/alerter'// dataService error:'./modules/dataService'// third-party library module jquery:'/ libs/jquery - 1.10.1'}}) require(['alerter'].function(alerter) {
    alerter.showMsg()
  })
})()
Copy the code

In the previous example, the alerter.js file is used to import the jQuery third-party library, and the main.js file is also configured with the corresponding path. Summary: By comparing the two, it can be concluded that AMD module definition method is very clear, does not pollute the global environment, can clearly show dependencies. AMD mode can be used in a browser environment and allows modules to be loaded asynchronously or dynamically as needed.

3.CMD

The CMD specification is specifically for the browser side, and the module is loaded asynchronously until the module is in use. The CMD specification integrates features of the CommonJS and AMD specifications. In sea-.js, all JavaScript modules follow the CMD module definition specification.

(1) Basic syntax of CMD specification

Define the exposure module:

// Define modules with no dependenciesfunction(require, exports, module){
  exports.xxx = value
  module.exports = value
})
Copy the code
// Define the dependent module define(functionModule2 = require(exports, module){var module2 = require(exports, module)'./module2') // Introduce dependency modules (asynchronous) require.async('./module3'.function(m3) {}) // exposed module exports. XXX = value})Copy the code

Introduction of use modules:

define(function (require) {
  var m1 = require('./module1')
  var m4 = require('./module4')
  m1.show()
  m4.show()
})
Copy the code

(2) A simple tutorial to use sea-.js

(1) Download sea-.js and introduce it

  • Liverpoolfc.tv: seajs.org/
  • github : github.com/seajs/seajs

Then import sea-.js into the project: js/libs/ sea-.js

② Create the project structure

|-js
  |-libs
    |-sea.js
  |-modules
    |-module1.js
    |-module2.js
    |-module3.js
    |-module4.js
    |-main.js
|-index.html
Copy the code

(3) Define the module code of sea-.js

// module1.js file define(functionVar data = (require, exports, module) {//'atguigu.com'// Internal functionfunction show() {
    console.log('module1 show() '+ data)} // exports. Show = show})Copy the code
// module2.js file define(function (require, exports, module) {
  module.exports = {
    msg: 'I Will Back'}})Copy the code
// module3.js file define(function(require, exports, module) {
  const API_KEY = 'abc123'
  exports.API_KEY = API_KEY
})
Copy the code
// module4.js file define(functionModule2 = require(exports, module) {var module2 = require(exports, module)'./module2')
  function show() {
    console.log('module4 show() '+ module2.msg)} exports.show = show // import dependent module (async) require.async()'./module3'.function (m3) {
    console.log('Asynchronous introduction of dependency module 3' + m3.API_KEY)
  })
})
Copy the code
// main.js file define(function (require) {
  var m1 = require('./module1')
  var m4 = require('./module4')
  m1.show()
  m4.show()
})
Copy the code

(4) Introduced in index.html

<script type="text/javascript" src="js/libs/sea.js"></script>
<script type="text/javascript">
  seajs.use('./js/modules/main')
</script>
Copy the code

The final results are as follows:

4. ES6 modular

ES6 modules are designed to be as static as possible, so that the dependencies of the module and the variables in and out of the module can be determined at compile time. Both CommonJS and AMD modules can only determine these things at run time. For example, the CommonJS module is an object, and you must look for object properties when you input.

(1)ES6 modular syntax

The export command is used to specify the external interfaces of a module. The import command is used to enter the functions provided by other modules.

/** define module math.js **/ var basicNum = 0; var add =function (a, b) {
    return a + b;
};
export{ basicNum, add }; /** reference module **/ import {basicNum, add} from'./math';
function test(ele) {
    ele.textContent = add(99 + basicNum);
}
Copy the code

As shown in the example above, when using the import command, the user needs to know the name of the variable or function to be loaded, otherwise it cannot be loaded. To make it easy for the user to load the module without having to read the documentation, specify the default output for the module using the export default command.

// export-default.js
export default function () {
  console.log('foo');
}
Copy the code
// import-default.js
import customName from './export-default';
customName(); // 'foo'
Copy the code

Module outputs by default, and the import command can specify any name for the anonymous function when other modules load the module.

(2) Differences between ES6 module and CommonJS module

There are two major differences:

The CommonJS module outputs a copy of the value, while the ES6 module outputs a reference to the value.

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

The second difference is because CommonJS loads an object (the module.exports property) that will only be generated after the script has run. While an ES6 module is not an object, its external interface is just a static definition, which is generated during the static code parsing phase.

To illustrate the first difference, let’s use the CommonJS module loading mechanism example above:

// 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 ES6 module operates differently from CommonJS. ES6 modules are dynamically referenced and do not cache values, and variables within a module are bound to the module in which they reside.

(3) Es6-Babel-Browserify use tutorial

In a nutshell: use Babel to compile ES6 to ES5 code and Browserify to compile and package JS.

① Define a package.json file

 {
   "name" : "es6-babel-browserify"."version" : "1.0.0"
 }
Copy the code

② Install babel-CLI, Babel-PRESET – ES2015 and Browserify

  • npm install babel-cli browserify -g
  • npm install babel-preset-es2015 –save-dev
  • Preset preset (all plug-in packaging for converting ES6 to ES5)

③ Define the. Babelrc file

  {
    "presets": ["es2015"]}Copy the code

(4) Define module code

// Module1.js file // exposed separatelyexport function foo() {
  console.log('foo() module1')}export function bar() {
  console.log('bar() module1')}Copy the code
// Module2.js file // Unified exposurefunction fun1() {
  console.log('fun1() module2')}function fun2() {
  console.log('fun2() module2')}export { fun1, fun2 }
Copy the code
// The default exposure can expose any data class item. What data is exposed, what data is receivedexport default () => {
  console.log('Default exposure')}Copy the code
// app.js file import {foo, bar} from'./module1'
import { fun1, fun2 } from './module2'
import module3 from './module3'
foo()
bar()
fun1()
fun2()
module3()
Copy the code

⑤ Compile and introduce it in index.html

  • Use Babel to compile ES6 to ES5 code (but with CommonJS syntax) :babel js/src -d js/lib
  • Compiling JS with Browserify:browserify js/lib/app.js -o js/lib/bundle.js

Then import it in the index. HTML file

 <script type="text/javascript" src="js/lib/bundle.js"></script>
Copy the code

Finally, the results are as follows:

What about third-party libraries (like jQuery)? First install the dependency NPM install jquery@1 and then import it in the app.js file

//app.js file import {foo, bar} from'./module1'
import { fun1, fun2 } from './module2'
import module3 from './module3'
import $ from 'jquery'

foo()
bar()
fun1()
fun2()
module3()
$('body').css('background'.'green')
Copy the code

Third, summary

  • The CommonJS specification is primarily used for server-side programming, and the loading modules are synchronous, which is not appropriate in a browser environment because synchronization means blocking loading, and browser resources are loaded asynchronously, hence the AMD CMD solution.
  • The AMD specification loads modules asynchronously in the browser environment, and multiple modules can be loaded in parallel. However, AMD specifications are expensive to develop, the code is difficult to read and write, and the semantics of the way modules are defined are not smooth.
  • The CMD specification is similar to the AMD specification in that it is used for browser programming, relies on proximity, delays execution, and can be easily run in Node.js. However, depending on SPM packaging, the module loading logic is heavy
  • ES6 implements modular functionality at the language standard level and is fairly simple enough to replace CommonJS and AMD specifications as a universal modular solution for browsers and servers.

Refer to the article

The history of front-end modular development

CommonJS, AMD, CMD difference

What are the differences between AMD and CMD?

Javascript Modular Programming

Javascript Standard Reference tutorial

The CMD module defines the specification

Understand CommonJS, AMD, CMD three specifications