Details of Front End Modularization (Full Edition)

  Front end, html, javascript, node.js, Programmer

Preface

In the early days of JavaScript development, it was only a few words to realize simple page interaction logic. Now the performance of CPU and browser has been greatly improved, and many page logic has migrated to the client (form verification, etc.). With the advent of the web2.0 era, Ajax technology has been widely used, jQuery and other front-end libraries are emerging one after another, and the front-end code is expanding day by day. At this time, JS will consider using modular specifications to manage.
The main contents of this article are to understand modularization, why modularization is needed, the advantages and disadvantages of modularization, and the modularization specifications, and to introduce the most popular CommonJS, AMD, ES6, CMD specifications in the next development. This article tries to introduce these boring concepts in a simple and easy-to-understand style from the point of view of Xiao Bai. I hope you will have a new understanding of modular programming after reading it!

It is suggested to download the source code of this article, knock it yourself, please stamp it fiercely.GitHub personal blog

模块化规范

I. Modular Understanding

1. What is a module?

  • A complex program is packaged into several blocks (files) according to certain rules (specifications) and combined together
  • The internal data and implementation of the block are private, only some interfaces (methods) are exposed to the outside to communicate with other modules outside.

2. Modular evolution process

  • Global function Mode: Encapsulates Different Functions into Different Global Functions

    • Coding: Encapsulate different functions into different global functions
    • Problem: Contaminated global namespace, easy to cause naming conflicts or unsafe data, and no direct relationship between module members.
function m1(){
 //  ...
 }
 function m2(){
 //  ...
 }
  • Namespace mode: simple object encapsulation

    • Role: Reduce global variables and resolve naming conflicts
    • Problem: Data is unsafe (data inside the module can be modified directly from outside)
let myModule = {
 data: 'www.baidu.com',
 foo() {
 console.log(`foo() ${this.data}`)
 },
 bar() {
 console.log(`bar() ${this.data}`)
 }
 }
 MyModule.data = 'other data' // can directly modify the data inside the module
 myModule.foo() // foo() other data

This writing will expose all module members and the internal status can be rewritten externally.

  • IIFE mode: anonymous function self-calling (closure)

    • Role: Data is private and can only be operated externally through exposed methods
    • Encoding: Encapsulates data and behavior into a function and exposes the interface by adding attributes to window
    • Question: What if this module currently depends on another module?
//index.html Document
 <script type="text/javascript" src="module.js"></script>
 <script type="text/javascript">
 myModule.foo()
 myModule.bar()
 Console.log (mymodule.data)//undefined cannot access module internal data
 MyModule.data = 'xxxx' // is not data inside the modified module
 MyModule.foo() // has not changed
 </script>
// module.js file
 (function(window) {
 let data = 'www.baidu.com'
 //Functions that Operate Data
 function foo() {
 //Used to expose functions
 console.log(`foo() ${data}`)
 }
 function bar() {
 //Used to expose functions
 console.log(`bar() ${data}`)
 Otherfund ()//internal call
 }
 function otherFun() {
 //Internal Private Functions
 console.log('otherFun()')
 }
 //Exposure Behavior
 Window.myModule = {foo, bar} //ES6
 })(window)

The final result is as follows:

  • IIFE mode enhancement: introducing dependency

This is the cornerstone of modern module implementation

// module.js file
 (function(window, $) {
 let data = 'www.baidu.com'
 //Functions that Operate Data
 function foo() {
 //Used to expose functions
 console.log(`foo() ${data}`)
 $('body').css('background', 'red')
 }
 function bar() {
 //Used to expose functions
 console.log(`bar() ${data}`)
 Otherfund ()//internal call
 }
 function otherFun() {
 //Internal Private Functions
 console.log('otherFun()')
 }
 //Exposure Behavior
 window.myModule = { foo, bar }
 })(window, jQuery)
//index.html Document
 <!  -js introduced must have a certain order->
 <script type="text/javascript" src="jquery-1.10.1.js"></script>
 <script type="text/javascript" src="module.js"></script>
 <script type="text/javascript">
 myModule.foo()
 </script>

In the above example, the background color of the page is changed to red by jquery method, so jQuery library must be introduced first, and this library is passed in as a parameter.This not only ensures the independence of the modules, but also makes the dependencies between the modules obvious..

3. Benefits of modularity

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

4. Introduce multiple<script>There was a problem afterwards.

  • Too many requests

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

  • Dependency ambiguity

We don’t know what their specific dependencies are, that is to say, it is easy to make mistakes in the loading sequence because we don’t know their dependencies.

  • Difficult to maintain

The above two reasons make it difficult to maintain, and it is likely that the whole body will be affected by one occurrence, resulting in serious problems in the project.
Modularization has many advantages, but the above problems will occur when a page needs to introduce multiple js files. These problems can be solved by modular specifications. The following are the most popular commonjs, AMD, ES6, CMD specifications in development.

Second, the modular specification

1.CommonJS

(1) Overview

Node application is composed of modules and adopts the CommonJS module specification. Each file is a module with its own scope. Variables, functions, and classes defined in one file are private and not visible to other files.On the server side, the modules are loaded synchronously at runtime. On the browser side, modules need to be compiled and packaged in advance.

(2) Characteristics

  • All code runs in the module scope and does not pollute the global scope.
  • The module can be loaded many times, but it will only run once during the first load, then the running result will be cached, and the cached result will be directly read after loading. If you want the module to run again, you must clear the cache.
  • Modules are loaded in the order in which they appear in the code.

(3) Basic Grammar

  • Exposed module:module.exports = valueOrexports.xxx = value
  • Introduction module:require(xxx), if it is a third-party module, xxx is the module name; If it is a custom module, xxx is the module file path.

Here we have a question:What are the modules exposed by CommonJS?The CommonJS specification stipulates that within each module, the module variable represents the current module. This variable is an object, and its exports attribute (module.exports) is an external interface.Loading a module is actually loading the module.exports property of the module..

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

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

var example = require('./example.js');  //If the parameter string starts with'./',it means that the load is on a relative path
 console.log(example.x);  // 5
 console.log(example.addX(1));  // 6

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

(4) module loading mechanism

The loading mechanism of the CommonJS module is that the input is a copy of the output value. In other words, once a value is output, changes within the module will not affect this value.. This is a significant difference from ES6 modularity (described below). Please look at the following example:

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

The above code outputs the internal variable counter and the internal method incCounter to rewrite this variable.

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

The above code shows that after the counter is output, the changes inside the lib.js module will not affect the counter.This is because counter is an original type value and will be cached. You cannot get the internally changed value unless you write a function..

(5) server-side implementation

(1) download and install node.js

(2) to create a project structure

Note: when npm init is used to automatically generate package.json, package name (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"
 }

(3) download the third party module

Npm installuniq-save//for array deduplication

④ Define module codes

//module1.js
 module.exports = {
 msg: 'module1',
 foo() {
 console.log(this.msg)
 }
 }
//module2.js
 module.exports = function() {
 console.log('module2')
 }
//module3.js
 exports.foo = function() {
 console.log('foo() module3')
 }
 exports.arr = [1, 2, 3, 3, 2]
// app.js file
 //The introduction of a third-party library should be placed first.
 let 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 ]

(5) running app.js through node

Command line inputnode app.jsTo run JS files

(6) browser-side implementation (via Browserify)

(1) to create a project structure

|-js
 |-dist // Package the directory of the generated file
 |-src // directory where the source code is located
 |-module1.js
 |-module2.js
 |-module3.js
 |-app.js // apply main source file
 |-index.html // runs on a browser
 |-package.json
 {
 "name": "browserify-test",
 "version": "1.0.0"
 }

(2) download browserify

  • Global: npInstallBrowser Ify-g
  • Local: npmsTALL BROWSERIFY-SAVE-DEV

(3) Define module code (same as server side)

Note:index.htmlTo run the file on a browser, you need to use browserify toapp.jsFile packaging compilation, if directly in theindex.htmlintroduceapp.jsYou’ll make a mistake!

④ Packing js

Run under root directorybrowserify js/src/app.js -o js/dist/bundle.js

⑤ Introduction of page usage

Introduced in index.html Document<script type="text/javascript" src="js/dist/bundle.js"></script>

2.AMD

The CommonJS specification loading module is synchronous, that is to say, only after the loading is completed can the following operations be performed. The AMD specification is a non-synchronous load module that allows callback functions to be specified. Because Node.js is mainly used for server programming, module files generally already exist on the local hard disk, so it loads faster without considering asynchronous loading methods, so the CommonJS specification is more applicable. But,If it is a browser environment, to load modules from the server side, asynchronous mode must be adopted, so the browser side generally adopts AMD specification. In addition, the AMD specification was implemented earlier on the browser side than the CommonJS specification.

(1)AMD Standard Basic Grammar

Define exposed modules:

//Define modules without dependencies
 define(function(){
 Return module
 })
//Define modules with dependencies
 define(['module1', 'module2'], function(m1, m2){
 Return module
 })

Introduction of usage module:

require(['module1', 'module2'], function(m1, m2){
 Use m1/m2
 })

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

By comparing the implementation methods of the two, the benefits of using AMD specifications are illustrated.

  • AMD specification not used
// dataService.js file
 (function (window) {
 let msg = 'www.baidu.com'
 function getMsg() {
 return msg.toUpperCase()
 }
 window.dataService = {getMsg}
 })(window)
// alerter.js file
 (function (window, dataService) {
 let name = 'Tom'
 function showMsg() {
 alert(dataService.getMsg() + ', ' + name)
 }
 window.alerter = {showMsg}
 })(window, dataService)
// main.js file
 (function (alerter) {
 alerter.showMsg()
 })(alerter)
//index.html Document
 < div><h1>Modular Demo 1: AMD(require.js)</h1></div >
 <script type="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>

Finally, the following results are obtained:

The shortcoming of this method are obvious:First of all, multiple requests will be sent. Second, the order of js files introduced cannot be mistaken, otherwise an error will be reported!

  • Js

RequireJS is a tool library mainly used for module management of clients. Its module management complies with AMD specifications.The basic idea of RequireJS is to define the code as a module through the define method. The module loading of the code is realized through the require method..
Next, the steps of implementing AMD specification in browser are introduced:

(1) download require.js and introduce

  • Official website:http://www.requirejs.cn/
  • github :https://github.com/requirejs/requirejs

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

(2) to create a project structure

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

(3) define the module code of require.js

// dataService.js file
 //Define modules without dependencies
 define(function() {
 let msg = 'www.baidu.com'
 function getMsg() {
 return msg.toUpperCase()
 }
 Return {getMsg} // expose module
 })
//alerter.js file
 //Define modules with dependencies
 define(['dataService'], function(dataService) {
 let name = 'Tom'
 function showMsg() {
 alert(dataService.getMsg() + ', ' + name)
 }
 //Expose module
 return { showMsg }
 })
// main.js file
 (function() {
 require.config({
 BaseUrl: 'js/', // the starting point of the basic path is under the root directory.
 paths: {
 //Mapping: Module ID Name: Path
 Alerter: './modules/alerter', // cannot be written as alerter.js here, an error will be reported.
 dataService: './modules/dataService'
 }
 })
 require(['alerter'], function(alerter) {
 alerter.showMsg()
 })
 })()
//index.html Document
 <!  DOCTYPE html>
 <html>
 <head>
 <title>Modular Demo</title>
 </head>
 <body>
 <!  -introduce require.js and specify the entry of js main file->
 <script data-main="js/main" src="js/libs/require.js"></script>
 </body>
 </html>

(4) the page introduces the require.js module:

Introduced in index.html<script data-main="js/main" src="js/libs/require.js"></script>

In addition, how to introduce a third-party library into the project?Just make a few changes on the basis 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 module
 return { showMsg }
 })
// main.js file
 (function() {
 require.config({
 BaseUrl: 'js/', // the starting point of the basic path is under the root directory.
 paths: {
 //custom module
 Alerter: './modules/alerter', // cannot be written as alerter.js here, an error will be reported.
 dataService: './modules/dataService',
 //Third Party Library Module
 JQuery:'./libs/jQuery-1.10.1'//Note: writing jQuery will report an error
 }
 })
 require(['alerter'], function(alerter) {
 alerter.showMsg()
 })
 })()

The above example is to introduce jQuery third-party library into alerter.js file, and the main.js file should also have corresponding path configuration.
SummaryBy comparing the two, it can be concluded thatAMD module defines the method very clearly, does not pollute the global environment, and can clearly display the dependency relationship. AMD mode can be used in a browser environment and allows modules to be loaded asynchronously or dynamically as needed.

3.CMD

CMD specification is specially used for browser side. The module is loaded asynchronously and will only be loaded and executed when the module is used. CMD specification integrates the features of CommonJS and AMD specifications. In Sea.js, all JavaScript modules follow the CMD module definition specification.

(1)CMD specification basic syntax

Define exposed modules:

//Define modules without dependencies
 define(function(require, exports, module){
 exports.xxx = value
 module.exports = value
 })
//Define modules with dependencies
 define(function(require, exports, module){
 //Introduce dependency module (synchronization)
 var module2 = require('./module2')
 //Introduce Dependency Module (Asynchronous)
 require.async('./module3', function (m3) {
 })
 //Expose module
 exports.xxx = value
 })

Introduction of usage modules:

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

(2)sea.js simple use tutorial

(1) download sea.js and introduce

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

(2) to create a project structure

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

(3) define the module code of sea.js

// module1.js file
 define(function (require, exports, module) {
 //Internal variable data
 var data = 'atguigu.com'
 //internal function
 function show() {
 console.log('module1 show() ' + data)
 }
 //Expose outward
 exports.show = show
 })
// module2.js file
 define(function (require, exports, module) {
 module.exports = {
 msg: 'I Will Back'
 }
 })
// module3.js file
 define(function(require, exports, module) {
 const API_KEY = 'abc123'
 exports.API_KEY = API_KEY
 })
// module4.js file
 define(function (require, exports, module) {
 //Introduce dependency module (synchronization)
 var module2 = require('./module2')
 function show() {
 console.log('module4 show() ' + module2.msg)
 }
 exports.show = show
 //Introduce Dependency Module (Asynchronous)
 require.async('./module3', function (m3) {
 Log ('asynchronous introduction of dependency module 3'+m3.API_KEY)
 })
 })
// main.js file
 define(function (require) {
 var m1 = require('./module1')
 var m4 = require('./module4')
 m1.show()
 m4.show()
 })

④ Introducing into index.html

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

The final results are as follows:

4.ES6 modularization

The design idea of ES6 module is to be as static as possible, so that the dependency of the module and the input and output variables can be determined at compile time. CommonJS and AMD modules can only determine these things at runtime. For example, the CommonJS module is an object, and the object attributes must be found during input.

(1)ES6 Modular Grammar

The export command is used to specify the module’s external interface, and the import command is used to input functions provided by other modules.

/* * definition 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);
 }

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. In order to provide convenience for users and enable them to load the module without reading the document, the export default command is used to specify the default output for the module.

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

The default output of the module. When other modules load the module, the import command can specify any name for the anonymous function.

(2) Differences between 2)ES6 module and CommonJS module

They have two major differences:

(1) Common JS module outputs a copy of the value, while ES6 module outputs a reference of the value.

(2) Common JS module is loaded at runtime and ES6 module is the output interface at compile time.

The second difference is that CommonJS loads an object (that is, the module.exports property), which will not be generated until the script is finished. While ES6 module is not an object, its external interface is only a static definition, which will be generated in the static analysis phase of the code.

The following focuses on the first difference. Let’s give an example of the loading mechanism of the CommonJS module 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);  // 4

The operation mechanism of ES6 module is different from CommonJS.ES6 module is a dynamic reference and does not cache values. Variables in the module are bound to the module in which they are located..

(3) es6-babel-browserif tutorial

In short, in one sentence:Use Babel to compile ES6 into ES5 code and Browserify to compile packaged js.

(1) define package.json file

{
 "name" : "es6-babel-browserify",
 "version" : "1.0.0"
 }

(2) install babel-cli, babel-preset-es2015 and browserify.

    • npm install babel-cli browserify -g
    • npm install babel-preset-es2015 –save-dev
    • Preset preset (package all plug-ins that convert es6 to es5)

    (3) definition.

    {
     "presets": ["es2015"]
     }

    ④ Define module codes

    //module1.js file
     //Expose separately
     export function foo() {
     console.log('foo() module1')
     }
     export function bar() {
     console.log('bar() module1')
     }
    //module2.js file
     //unified exposure
     function fun1() {
     console.log('fun1() module2')
     }
     function fun2() {
     console.log('fun2() module2')
     }
     export { fun1, fun2 }
    //module3.js file
     //Default exposure can expose any data class item, expose any data and receive any data.
     export default () => {
     Log ('default exposure')
     }
    // app.js file
     import { foo, bar } from './module1'
     import { fun1, fun2 } from './module2'
     import module3 from './module3'
     foo()
     bar()
     fun1()
     fun2()
     module3()

    ⑤ Compiled and Introduced into index.html

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

    It was then introduced into the index.html document.

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

    Finally, the following results are obtained:

    In addition, how to introduce the third-party library (jQuery, for example)?
    First install dependenciesnpm install jquery@1
    It is then introduced into 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')

    III. Summary

    • The CommonJS specification is mainly used for server-side programming, and the loading modules are synchronous, which is not suitable for browser environment, because synchronization means blocking loading, and browser resources are loaded asynchronously, so AMD CMD solution is available.
    • AMD specifications load modules asynchronously in a browser environment and can load multiple modules in parallel. However, AMD specification development cost is high, code reading and writing are difficult, and the semantics of module definition are not smooth.
    • CMD specification is very similar to AMD specification, both of which are used for browser programming, rely on proximity, delay execution, and can be easily run in Node.js However, depending on SPM packaging, the module loading logic is biased
    • ES6 has realized the module function on the level of language standard, and it is quite simple to realize. It can completely replace the CommonJS and AMD specifications and become a common module solution for browsers and servers..

    Postscript

    It took a long time (> 10h) to finally explain “JS modularity” clearly. my understanding of modularity has deepened one step. in fact, it is not difficult to understand a thing. the difficult thing is how to share a thing with others in a popular way and let others gain something. I have always asked myself the same way! If there are any mistakes and inaccuracies in the article, please correct and criticize it. At the same time, I hope you will give me more support. I will have more creative motivation!

    Reference article

    The History of Front-end Modular Development

    CommonJS, AMD, CMD

    What are the differences between AMD and CMD?

    Javascript modular programming

    Javascript standard reference tutorial

    CMD module definition specification

    Understanding CommonJS, AMD and CMD