Front-end modularization

Modularization is the subdivision of a system into small units, which are abstract, extensible, reusable logical code. Modularization is the foundation of engineering. Only modularization of code and reasonable unit structure can make it have the ability of scheduling integration.

Development history of modularization

Simulation period

Due to the occasional success of JavaScript and its initial poor design, it lacked many of the features of mature languages when it became a mainstream script on the client side. Programmers who need to use modularity to solve namespace problems need to use objects to solve them:

var module1 = {
  foo: 'bar'.baz: function () {
    console.log(this.foo); }};Copy the code

A namespace, module1, is implemented reluctantly, and properties and methods in that namespace can also be invoked. But this approach has a fatal drawback: it’s not secure. Any developer or user can modify it, which is prone to bugs and unknown situations.

Data access problems can be solved using IIFE:

var module2 = (function () {
  var foo = 'bar';
  var baz = function (value) {
    foo = value;
  };
  return{ baz, }; }) ();Copy the code

In this mode, you can block access and unlimited modification of internal properties. That is, developers can only read and write data and call methods through the interface exposed by the module, and you can only use what the module exposes.

This is the basis for future modularity, even though it is not supported by the native language and is simply a simulation written by developers “on the spur of the moment”. This approach is also cumbersome for circular references and handling references between modules.

CommonJS & AMD => UMD

CommonJS is a modular standard proposed by the community in 2009 and later adopted by Node.js with minor modifications as its modular standard. CommonJS and Node have a == relationship, not ===!

CommonJS treats a file as a module, and the code in the file runs in a separate scope without polluting the global space. Modules can be referenced and loaded multiple times. CommonJS module loading is a synchronous operation. After the first loading, it will be cached. Exports the module.exports property exports copies of values, not references, meaning that after a value is exported, any subsequent changes to it do not affect the output value. Modules are loaded in the order they are exported.

// module.js
/ / export
module1.exports = {
  fn,
}
/ / or
exports.fn = fn

// index.js
/ / import
const module3 = require('./module.js')
module3.fn()
Copy the code

CommonJS is generally applied to the server and files are stored on disk without the need for additional network asynchronous loading. Therefore, the CommonJS specification loading module is synchronous and can be performed only after the loading is completed. This is not particularly applicable in a browser environment, where clients typically make a large number of asynchronous network requests to obtain resources. To this end, AMD came into being.

As mentioned earlier, the AMD standard loads modules asynchronously and is perfectly suited for browsers. At the heart of the AMD module implementation is the use of an IIFE function that wraps the module definition, prevents contamination of global variables, and allows the loader library to control when the module is loaded. The wrapper module functions are parameters to global define, which are defined by the AMD loader library implementation. One of the better known AMD loaders is the require.js library, and interested readers can read the source code.

Generic module definition

Kids make choices. Adults all make choices. The UMD module specification allows both AMD and CommonJS specifications to be used in the environment as an integration solution. The idea is to use the immediate execution function to determine the required parameter class based on the environment, essentially defining a flag to determine the environment in which to execute the specification.

ES native module

JavaScript is finally a native modular solution in ES6. Native browser support means that preprocessors and extra loaders are no longer needed, which means that module loaders are slowly being replaced by native module systems.

The ES6 module exists as a single block of JavaScript code:

<script type="module">
  /* something */
</script>
Copy the code

Such script tags are executed sequentially as

The ES module outputs a reference to a value, that is, subsequent changes to the value will also affect the module:

// data.js
export let data = 'data';
export const handleChangeData = () = > {
  data = 'xxx';
};

// index.js
import { data, handleChangeData } from './data.js';
console.log(data); // data
handleChangeData();
console.log(data); // xxx
Copy the code

The ES module is designed to be as static as possible to ensure that dependencies between modules are determined at compile time and that input/output variables for each module are determined. One of the advantages of statics is that it is fast. Through static analysis, module dependency graphs can be analyzed more quickly. If the imported modules are not being used, you can use tree shaking to reduce code volume and improve performance.

What is dynamic static: The expression require(path) can be used to import CommonJS.

There are some limitations to being static:

  • Dependencies can only be introduced at the top of the file
  • The exported variable types are strictly limited
  • Variables are not allowed to be overwritten bindings, and imported module names can only be string constants, that is, dependencies cannot be dynamically bound