Front end modularization (full version)

Time:2021-4-26

preface

In the early development of JavaScript, it was to realize simple page interaction logic, just a few words; Nowadays, the performance of CPU and browser has been greatly improved, and a lot of page logic has been migrated to the client (form verification, etc.). With the advent of Web2.0 era, AJAX technology has been widely used, jQuery and other front-end libraries emerge in endlessly, and front-end code is expanding day by day. At this time, we will consider using modular specification to manage JS.
The main content of this paper is to understand modularization, why modularization, the advantages and disadvantages of modularization and modularization specification, and introduce the most popular common JS, AMD, ES6 and CMD specifications in the development. This paper attempts to stand in the perspective of Xiaobai, with easy to understand style to introduce these boring concepts, I hope you will have a new understanding and understanding of modular programming after reading!
Front end modularization (full version)

1、 Understanding of modularity

1. What is 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, but some interfaces (Methods) are exposed to communicate with other external modules

2. The evolution of modularity

  • Global function mode: encapsulate different functions into different global functions

    • Coding: encapsulating different functions into different global functions
    • Problem: polluting the global namespace is easy to cause naming conflict or data insecurity, and there is no direct relationship between module members
function m1(){
  //...
}
function m2(){
  //...
}
  • Namespace pattern: simple object encapsulation

    • Function: reduce global variables and solve naming conflicts
    • Problem: data is not secure (external data can be directly modified 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' // you can directly modify the internal data of the module
myModule.foo() // foo() other data

In this way, all module members will be exposed, and the internal state can be rewritten externally.

  • Iife mode: anonymous function self call (closure)

    • Function: the data is private and can only be operated by exposed methods
    • Coding: encapsulate the data and behavior into a function, and expose the interface by adding properties to the window
    • Question: what if the current module depends on another module?
//  index.html file
<script type="text/javascript"></script>
<script type="text/javascript">
    myModule.foo()
    myModule.bar()
    console.log ( myModule.data )// undefined cannot access the internal data of the module
    myModule.data  ='XXXX' // is not the internal data of the modified module
    myModule.foo () // no change
</script>
//  module.js file
(function(window) {
  let data = 'www.baidu.com'
  //Functions that manipulate data
  function foo() {
    //Used to expose existing functions
    console.log(`foo() ${data}`)
  }
  function bar() {
    //Used to expose existing functions
    console.log(`bar() ${data}`)
    Otherfun() // internal call
  }
  function otherFun() {
    //Internally private functions
    console.log('otherFun()')
  }
  //Exposure behavior
  window.myModule  =How to write {foo, bar} // ES6
})(window)

The results are as follows
Front end modularization (full version)

  • Iife pattern enhancement: introducing dependency

This is the cornerstone of modern module implementation

//  module.js file
(function(window, $) {
  let data = 'www.baidu.com'
  //Functions that manipulate data
  function foo() {
    //Used to expose existing functions
    console.log(`foo() ${data}`)
    $('body').css('background', 'red')
  }
  function bar() {
    //Used to expose existing functions
    console.log(`bar() ${data}`)
    Otherfun() // internal call
  }
  function otherFun() {
    //Internally private functions
    console.log('otherFun()')
  }
  //Exposure behavior
  window.myModule = { foo, bar }
})(window, jQuery)
//  index.html file
  <!--  JS must be introduced in a certain order -- >
  <script type="text/javascript"></script>
  <script type="text/javascript"></script>
  <script type="text/javascript">
    myModule.foo()
  </script>

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

3. Benefits of modularity

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

4. Introduce multiple<script>There was a problem after that

  • Too many requests

First of all, 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’s easy to make mistakes in loading sequence because we don’t understand their dependencies.

  • Difficult to maintain

The above two reasons lead to difficult maintenance, which may lead to serious problems in the project.
Modularization has many advantages, but a page needs to introduce multiple JS files, which will lead to the above problems. And these problems can be solved by modular specification. The following introduces the most popular common JS, AMD, ES6 and CMD specifications in development.

2、 Modular specification

1.CommonJS

(1) Overview

The node application is composed of modules and adopts the common JS module specification. Each file is a module with its own scope. Variables, functions and classes defined in one file are private and invisible to other files.On the server side, 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 be run once when it is loaded for the first time, and then the running results will be cached. When it is loaded later, the cached results will be read directly. For the module to run again, the cache must be cleared.
  • Modules are loaded in the order they appear in the code.

(3) Basic grammar

  • Exposure 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 exposed modules of commonjs?According to the common JS specification, within each module, the module variable represents the current module. This variable is an object whose exports property (i.e module.exports )It’s an external interface.To load a module is to load the module module.exports attribute

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

The above code passed module.exports Output variable x and function addx.

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

The require command is used to load the module file.The basic function of the require command is to read in and execute a JavaScript file, and then return the exports object of the module. 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, the internal changes of the module will not affect the value. There is a significant difference between this and ES6 modularization (which will be described below). Please see 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 that rewrites the 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 counter output, lib.js The changes inside the module will not affect the counter.This is because counter is a value of the original type and will be cached. Unless it is written as a function, we can get the value after internal change

(5) Server implementation

① Download and install node.js

② Create project structure

Note: generated automatically with NPM init package.json The package name cannot have Chinese and uppercase characters

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

③ Download the third party module

NPM install uniq -- save // for array de duplication

④ Define module code

//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 third-party libraries should be placed in the front
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 ]

⑤ Running through node app.js

Command line inputnode app.js, run JS file

(6) Browser implementation (with browserify)

① Create project structure

|-js
  |-Dist // the directory where the generated files are packaged
  |-Src // source directory
    |-module1.js
    |-module2.js
    |-module3.js
    |- app.js  //Apply main source file
|- index.html  //Run on Browser
|-package.json
  {
    "name": "browserify-test",
    "version": "1.0.0"
  }

② Download browserify

  • Global: NPM install browse – G
  • Local: NPM install browse — save dev

③ Define module code (same as server)

be careful:index.htmlTo run a file on a browser, you need to use browserify toapp.jsFile package compilation, if directly in theindex.htmlintroduceapp.jsYou’ll report a mistake!

④ Package processing JS

Running in the root directorybrowserify js/src/app.js -o js/dist/bundle.js

⑤ Introduction of page usage

stay index.html File<script type="text/javascript"></script>

2.AMD

The loading module of commonjs specification is synchronous, that is to say, only after the loading is completed can the following operations be performed. Amd specification is an asynchronous loading module, which allows to specify callback function. because Node.js It is mainly used for server programming. Generally, module files already exist on the local hard disk, so it’s faster to load. There’s no need to consider the asynchronous loading method, so commonjs specification is more applicable. But,If it is a browser environment, to load the module from the server, asynchronous mode must be adopted, so amd specification is generally adopted in the browser。 In addition, amd specification is earlier than commonjs specification in browser implementation.

(1) Basic syntax of AMD specification

Define exposure module:

//Define modules that have no dependencies
define(function(){
   Return module
})
//Define dependent modules
define(['module1', 'module2'], function(m1, m2){
   Return module
})

Introduction of usage module:

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

(2) Unused amd specification and use require.js

By comparing the implementation methods of the two, the advantages of using amd specification 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 file
< div > < H1 > modular Demo 1: AMD not used( require.js )</h1></div>
<script type="text/javascript"></script>
<script type="text/javascript"></script>
<script type="text/javascript"></script>

The results are as follows
Front end modularization (full version)

The disadvantages of this method are obviousFirstly, multiple requests will be sent. Secondly, the order of the JS file should not be wrong, otherwise an error will be reported!

  • use require.js

Requirejs is a tool library, which is mainly used for client module management. Its module management complies with AMD specification,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 by the require method
Next, the implementation steps of AMD specification in browser are introduced

① Download require.js And introduce

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

And then require.js Import project: JS / LIBS/ require.js

② Create project structure

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

Definition require.js Module code for

//  dataService.js file
//Define modules that have no dependencies
define(function() {
  let msg = 'www.baidu.com'
  function getMsg() {
    return msg.toUpperCase()
  }
  Return {getmsg} // expose module
})
// alerter.js file
//Define dependent modules
define(['dataService'], function(dataService) {
  let name = 'Tom'
  function showMsg() {
    alert(dataService.getMsg() + ', ' + name)
  }
  //Exposure module
  return { showMsg }
})
//  main.js file
(function() {
  require.config({
    Baseurl: 'JS /' // the starting point of the basic path is in the root directory
    paths: {
      //Mapping: module ID Name: Path
      Alert: '. / modules / Alert' // cannot be written here alerter.js , will report an error
      dataService: './modules/dataService'
    }
  })
  require(['alerter'], function(alerter) {
    alerter.showMsg()
  })
})()
//  index.html file
<!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"></script>
  </body>
</html>

④ Page introduction require.js modular:

stay index.html introduce<script data-main="js/main"></script>

In addition, how to introduce the third-party library into the project?Just make a little modification 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')
  //Exposure module
  return { showMsg }
})
//  main.js file
(function() {
  require.config({
    Baseurl: 'JS /' // the starting point of the basic path is in the root directory
    paths: {
      //Custom module
      Alert: '. / modules / Alert' // cannot be written here alerter.js , will report an error
      dataService: './modules/dataService',
      //Third party library module
      JQuery: '. / LIBS / jquery-1.10.1' // Note: errors will be reported when writing jQuery
    }
  })
  require(['alerter'], function(alerter) {
    alerter.showMsg()
  })
})()

The above example is in alerter.js The third-party jQuery library is introduced into the file, main.js The file should also have corresponding path configuration.
SummaryBy comparing the two, it can be concluded thatAmd module definition method is very clear, does not pollute the global environment, can clearly show the dependency. Amd mode can be used in browser environment, and can load modules asynchronously or dynamically as needed.

3.CMD

The CMD specification is specially used for the browser side, the module loading is asynchronous, and the module will be loaded and executed when it is used. The CMD specification integrates the characteristics of commonjs and AMD specifications. In Sea.js In, all JavaScript modules follow the CMD module definition specification.

(1) Basic syntax of CMD specification

Define exposure module:

//Define modules that have no dependencies
define(function(require, exports, module){
  exports.xxx = value
  module.exports = value
})
//Define dependent modules
define(function(require, exports, module){
  //Introducing dependent modules (synchronization)
  var module2 = require('./module2')
  //Introduction of dependent modules (asynchronous)
    require.async('./module3', function (m3) {
    })
  //Exposure module
  exports.xxx = value
})

Introduce the use module:

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

(2) sea.js Easy to use tutorial

① Download sea.js And introduce

And then sea.js Import project: JS / LIBS/ sea.js

② Create project structure

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

③ Definition sea.js Module code for

//Module1.js file
define(function (require, exports, module) {
  //Internal variable data
  var data = 'atguigu.com'
  //Internal function
  function show() {
    console.log('module1 show() ' + data)
  }
  //Outward exposure
  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) {
  //Introducing dependent modules (synchronization)
  var module2 = require('./module2')
  function show() {
    console.log('module4 show() ' + module2.msg)
  }
  exports.show = show
  //Introduction of dependent modules (asynchronous)
  require.async('./module3', function (m3) {
    console.log ('asynchronous introduction of dependent module 3 '+ m3. API)_ KEY)
  })
})
//  main.js file
define(function (require) {
  var m1 = require('./module1')
  var m4 = require('./module4')
  m1.show()
  m4.show()
})

④ In index.html Introduced in

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

The results are as follows
Front end modularization (full version)

4. ES6 modularization

The design idea of ES6 module is to be static as much as possible, so that the dependence 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 you must look up the object properties when you input.

(1) ES6 modular syntax

The export command is used to specify the external interface of the module, and the Import command is used to input 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);
}

As shown in the above example, 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, so that they can load the module without reading the document, we need to use the export default command 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'

When other modules load the module, the Import command can specify any name for the anonymous function.

(2) Differences between ES6 module and commonjs module

There are two major differences:

① The commonjs module outputs a copy of a value, while the ES6 module outputs a reference to a value

② The commonjs module is loaded at run time, and the ES6 module is the output interface at compile time

The second difference is that commonjs loads an object (i.e module.exports The object is generated only after the script has run. The ES6 module is not an object, its external interface is just a static definition, which will be generated in the static parsing phase of the code.

Let’s focus on the first difference. Let’s take the loading mechanism of the commonjs module as an example

// 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 that of commonjs.The 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 browserify tutorial

In a word:Use Babel to compile ES6 into Es5 code, and use browserify to compile and package JS

① Definition package.json file

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

② Install Babel cli, Babel preset es2015 and browse

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

③ Define. Babelrc file

  {
    "presets": ["es2015"]
  }

④ Define module code

//Module1.js file
//Separate exposure
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, and the data exposed is the data received
export default () => {
  console.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()

⑤ Compile and index.html Introduced in

  • Use Babel to compile ES6 into Es5 code (but with commonjs syntax):babel js/src -d js/lib
  • Use browserify to compile JS:browserify js/lib/app.js -o js/lib/bundle.js

And then in the index.html File

 <script type="text/javascript"></script>

The results are as follows
Front end modularization (full version)

In addition, how to introduce a third-party library (take jQuery as an example)
Install dependencies firstnpm install [email protected]
And then 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')

3、 Summary

  • Commonjs specification is mainly used for server programming, and the loading module is 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 standard loads modules asynchronously in browser environment, and can load multiple modules in parallel. However, the cost of AMD specification development is high, it is difficult to read and write code, and the semantics of module definition is not smooth.
  • The CMD specification is very similar to the AMD specification. Both of them are used for browser programming. They are dependent on proximity and delay execution. They can be easily implemented in the browser Node.js Run in. However, depending on SPM packaging, the module loading logic is biased
  • ES6 realizes the module function on the level of language standard, and it is quite simple. It can completely replace the common JS and AMD specifications and become a common module solution for browsers and servers

Author: sailing in waves
Link:Front end modularization (full version)
Source: GitHub
The copyright belongs to the author. For commercial reprint, please contact the author for authorization. For non-commercial reprint, please indicate the source.