The evolution of modularity

File partition

The initial file division method is rough, the young development students may not feel. Take a look at the following small example.

  • index.html
<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Modular evolution stage 1</title>
</head>
<body>
  <script src="module-a.js"></script>
  <script src="module-b.js"></script>
  <script>
    // Name conflict
    method1()
    // Module members can be modified
    name = 'foo'
  </script>
</body>
</html>
Copy the code
  • module-a.js
// module A Related status data and function functions

var name = 'module-a'

function method1 () {
  console.log(name + '#method1')}function method2 () {
  console.log(name + '#method2')}Copy the code
  • module-b.js
// module b related state data and function functions

var name = 'module-b'

function method1 () {
  console.log(name + '#method1')}function method2 () {
  console.log(name + '#method2')}Copy the code

The idea is to place each function and its associated state data in a separate file, with the convention that each file is a separate module, and to use a module is to introduce the module into the page and call its members (variables/functions) directly.

Disadvantages are as follows:

  • All modules work directly globally, there is no private space, and all members can be accessed or modified outside the module

  • Once there are too many modules, it is easy to cause naming conflicts. In the example, the name variable will be overwritten, and it is difficult to locate the cause of the problem

  • In addition, the dependency between modules cannot be managed, so it needs to be introduced in order

Namespace mode

Look at the following example

  • index.html
<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Modular evolution stage 2</title>
</head>
<body>
  <script src="module-a.js"></script>
  <script src="module-b.js"></script>
  <script>
    moduleA.method1()
    moduleB.method1()
    // Module members can be modified
    moduleA.name = 'foo'
  </script>
</body>
</html>
Copy the code
  • module-a.js
// module A Related status data and function functions
var moduleA = {
  name: 'module-a'.method1: function () {
    console.log(this.name + '#method1')},method2: function () {
    console.log(this.name + '#method2')}}Copy the code
  • module-b.js
// module b related state data and function functions

var moduleB = {
  name: 'module-b'.method1: function () {
    console.log(this.name + '#method1')},method2: function () {
    console.log(this.name + '#method2')}}Copy the code

Each module exposes only one global object, to which all module members are mounted, by “wrapping” each module into a global object, similar to adding a “namespace” feel to the members within the module. The possibility of naming conflicts is reduced through “namespaces”, but again there is no private space, all module members can be accessed or modified outside the module, and dependencies between modules cannot be managed.

IIFE(Immediately-Invoked Function Expression)

Here’s an example

  • index.html
<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Modular evolution stage 3</title>
</head>
<body>
  <h1>Modular Evolution (Stage 3)</h1>
  <h2>Use IIFE: Immediate-Invoked Function Expression to provide private space for the module</h2>
  <script src="module-a.js"></script>
  <script src="module-b.js"></script>
  <script>
    moduleA.method1()
    moduleB.method1()
    // Private members of the module cannot be accessed
    console.log(moduleA.name) // => undefined
  </script>
</body>
</html>
Copy the code
  • module-a.js
// module A Related status data and function functions; (function () {
  var name = 'module-a'
  
  function method1 () {
    console.log(name + '#method1')}function method2 () {
    console.log(name + '#method2')}window.moduleA = {
    method1: method1,
    method2: method2
  }
})()

Copy the code
  • module-b.js
// module b related state data and function functions; (function () {
  var name = 'module-b'
  
  function method1 () {
    console.log(name + '#method1')}function method2 () {
    console.log(name + '#method2')}window.moduleB = {
    method1: method1,
    method2: method2
  }
})()

Copy the code

This is done by placing each module member in a private scope provided by a function. For members that need to be exposed to the outside world, they are implemented by hanging on global objects. With the concept of private members, private members can only be accessed within a module member through a closure.

IIFE adds dependencies

The following code

  • index.html
<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Modular evolution stage 4</title>
</head>
<body>
  <script src="https://unpkg.com/jquery"></script>
  <script src="module-a.js"></script>
  <script src="module-b.js"></script>
  <script>
    moduleA.method1()
    moduleB.method1()
  </script>
</body>
</html>

Copy the code
  • module-a.js
// module A Related status data and function functions; (function ($) {
  var name = 'module-a'
  
  function method1 () {
    console.log(name + '#method1'The $()'body').animate({ margin: '200px'})}function method2 () {
    console.log(name + '#method2')}window.moduleA = {
    method1: method1,
    method2: method2
  }
})(jQuery)

Copy the code
  • module-b.js
// module b related state data and function functions; (function () {
  var name = 'module-b'
  
  function method1 () {
    console.log(name + '#method1')}function method2 () {
    console.log(name + '#method2')}window.moduleB = {
    method1: method1,
    method2: method2
  }
})()

Copy the code

The obvious advantage of this phase over the previous phase is that the dependencies can be seen intuitively.

Modular solutions

CommonJS

background

CommonJS is a project whose goal is to create modular conventions for JavaScript outside of web browsers. The main reason for the creation of this project was the lack of a unit of JavaScript script modules in a generally acceptable form that could be reused in a different environment from that provided by regular Web browsers running JavaScript scripts.

The project was launched in January 2009 by Mozilla engineer Kevin Dangoor and was originally called ServerJS. In August 2009, the project was renamed CommonJS to demonstrate the wide application of its API. Regulations are created and endorsed in an open process, and a regulation is not considered final until it has been implemented by multiple implementations. CommonJS is not affiliated with TC39, the Ecma International working group working on ECMAScript, but some TC39 members are involved in the project.

In May 2013, Isaac Z. Schlueter, author of the Node.js package manager NPM, announced that Node.js had deprecated CommonJS and that core Node.js developers should avoid using it.

CommonJS has been deprecated, but there is still a lot of CommonJS code in the works. Let’s take a look at CommonJS.

use

Module export

There are two ways to export in CommonJS

  • module.exports
  • exports

Take a look at the _require function below, where I’ve also introduced the concept of package imports for ease of understanding.

function _require(/ *... * /) {
  const module = { exports: {}}; ((module.exports) = > {
    // The module code is here. In this case, you define a function.
    function someFunc() {}
    // exports = someFunc;
    Exports is no longer a shortcut to module.exports, which still exports an empty default object.
    // exports.a = 1;
    exports = {};
    module.exports = someFunc;
    // At this point, the module will now export someFunc instead of the default object.}) (module.module.exports);
  return module.exports;
}

Copy the code

In IIFE we pass in two arguments, module and module.exports. Exports: module. Exports: module.

From the above code we can analyze the following points:

  • whenexports.xxxwithmodule.export = xxxWhen they coexist,exports.xxxinvalid
# m.js
exports.name = 'm';
module.exports = 'module';

# test.js
const m = require('./m.js')
console.log(m); // module
Copy the code
  • When there is more than oneexports.xxxExists, their values are assigned to a variable at the same time.
# m.js
exports.name = 'marvin'
exports.age = 18

# test.js
const m = require('./m.js')
console.log(m) // {age: 'marvin, age: 18}
Copy the code
  • Direct changeexportWill not affect the export content, the lead out after changing the point except.
# m.js
exports.name = 'marvin';
exports = {age: 18}

# test.js
const m = require('./m.js')
console.log(m) // {name: 'marvin}
Copy the code

Module import

In CommonJS, importing modules requires the require keyword.

There are a lot of details here, the official has provided a pseudo code, students who need to go to check.

Briefly describe the common search order

  • If it is a core module, such asfsAnd so on, the module is directly loaded and returned
  • If the/First, go to the root of the file system to find the module
  • If is. /or../The relative path is used to find the module
    • Check whether the path is a directory and whether the directory existspacakge.jsonIf there is and there ismainNode, then want in the correspondingjsFile, otherwise load the file name set toindexGo the next step
    • Look for the exact filename. If you can’t find the exact filename,Node.jsAn attempt will be made to load the desired filename and add the extension:.js,.jsonAnd, finally,.node.
    • .jsThe document is interpreted asJavaScriptText file, while.jsonThe file is parsed asJSONText file..nodeThe file is interpreted as loadedprocess.dlopen()Compiles the plug-in module.
  • Go to thenode_modulesDirectory to look for modules
    • Note here that instead of just looking in this directory, we can print in the modulemodule.paths, that is, you can see the search order. Here is the search order of my test items.
    '/Users/marvin/Documents/Git/test-modules/node_modules',
    '/Users/marvin/Documents/Git/node_modules',
    '/Users/marvin/Documents/node_modules',
    '/Users/marvin/node_modules',
    '/Users/node_modules',
    '/node_modules'
    Copy the code
    • The lookup order in node_modules is the same as the previous step

The following code tests the order of lookup inside the module, since.node is not used, it will not be tested here.

The main test file is as follows. The file with the same name has the same content as index.js

module.exports = {name:'js'}
Copy the code

index.json

{"name":"json"}
Copy the code

main.js

module.exports = {name:'main'}
Copy the code

package.json

{
  "name": "main"."version": "1.0.0"."description": ""."main": "main.js"."scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": []."author": ""."license": "ISC"
}
Copy the code

The directory structure is as follows

. ├ ─ ─ index. Js └ ─ ─ node_modules ├ ─ ─ js │ ├ ─ ─ index. The js │ └ ─ ─ index. The json ├ ─ ─ json │ └ ─ ─ index. The json └ ─ ─ the main ├ ─ ─ index, js ├─ ├─ download.txt ├─ download.txtCopy the code

The test file content is as follows:

const js = require("js");
const json = require("json");
const main = require("main");

console.log("js", js);
console.log("json", json);
console.log("main", main);

Copy the code

It can be seen that the printed content is as follows, which is consistent with our expectation.

Module loading mechanism

Modules are cached after the first load. This means (like other caches) that every call to require(‘foo’) will return exactly the same object (if resolved to the same file).

# cache.js
const cache = {
  name: "cache".arr: [].age: 18};module.exports = cache;

# test.js
console.log("cache1", cache);
cache.name = "othercache";
cache.age = 28;
cache.arr.push(1);
console.log("cache2", cache);
const cache3 = require("./cache");
console.log("cache3", cache3);

// cache1 { name: 'cache', arr: [], age: 18 }
// cache2 { name: 'othercache', arr: [ 1 ], age: 28 }
// cache3 { name: 'othercache', arr: [ 1 ], age: 28 }

Copy the code

The module creates an address 0x0001 in the heap, and every time it requires (./cache) it will retrieve the address.

There is a bit of a catch: if you use the structure module at the same time as require, changing the base type will not affect the values in the cache module. See the following test code.

let { age, name, arr } = require("./cache");
console.log(name, age, arr);
age = 28;
console.log(age);
arr.push(1);
const cache3 = require("./cache");
console.log("cache3", cache3);

// cache 18 []
/ / 28
// cache3 { name: 'cache', arr: [ 1 ], age: 18 }
Copy the code

Tips that might be used

// Verify that the file is being run directly by testing require.main === module.
console.log(require.main === module);
To get the entry point for the current application, view require.main.filename.
console.log(require.main.filename);
// __dirname Directory name of the current module
// __filename Specifies the name of the current module
Copy the code

AMD

background

You have to wonder why there is AMD and CMD with CommonJS, and there is a loading time issue involved here. NodeJS runs on a synchronous load of hard disks and loads quickly. The browser is based on the network, if the network request time is too long, then the browser side will be in a long wait state, this situation is not user-friendly.

To solve this problem, AMD emerged.

AMD stands for Asynchronous Module Definition. Require. js is the implementer of AMD, and the following is a brief introduction to use.

require.js

Take the official demo as an example.

├ ─ ─ app │ ├ ─ ─ the main, js │ └ ─ ─ messages. Js ├ ─ ─ app. Js ├ ─ ─ index. The HTML └ ─ ─ lib ├ ─ ─ print. Js └ ─ ─ the require. JsCopy the code
  • index.htmlYou can see the code in addition to the specifiedrequire.jsIn addition to the path, an entry file is specified namelydata-main=app, this configuration will load and execute theapp.jsfile
<! DOCTYPEhtml>
<html>
    <head>
        <script data-main="app" src="lib/require.js"></script>
    </head>
    <body>
        <h1>Hello World</h1>
    </body>
</html>
Copy the code
  • app.jsBaseUrl is the default path to be searched. Generally, third-party libraries such as jQuery will be stored in this directory. Other path mappings are configured in paths.requirejs(['app/main'])Will request and execute the main.js file in the app path.
// For any third party dependencies, like jQuery, place them in the lib folder.

// Configure loading modules from the lib directory,
// except for 'app' ones, which are in a sibling
// directory.
requirejs.config({
    baseUrl: 'lib'.paths: {
        app: '.. /app'}});// Start loading the main app file. Put all of
// your application logic in there.
requirejs(['app/main']);

Copy the code
  • main.jsFile, which defines the code to execute, is loaded first in the same directorymessage.jsFile, and then load itlibIn the directoryprintFile. The following is executed after the contents of both files are loaded successfullyprint(messages.getHello());
define(function (require) {
  // Load any app-specific modules
  // with a relative require call,
  // like:
  var messages = require("./messages");
  console.log("message", message);

  // Load library/vendor modules using
  // full IDs, like:
  var print = require("print");

  print(messages.getHello());
});
Copy the code
  • message.js
define(function () {
    return {
        getHello: function () {
            return 'Hello World'; }}; });Copy the code
  • print.js
define(function () {
    return function print(msg) {
        console.log(msg);
    };
});
Copy the code

CMD

background

CMD is another JS modularization solution, which is similar to AMD, except that AMD advocates relying on front-loading and up-front execution, while CMD advocates relying on nearby and delayed execution. This specification was actually created during the promotion of Sea-js.

AMD advocates relying on front-loading and front-loading execution, while CMD advocates relying on nearby and delayed execution. I always thought that the difference between the two was whether they would dynamically request resources. Later, the actual test found that in the whole code block, as long as there was require, it would request network resources. The difference was that require.js would execute JS code after the network request was successfully executed. Sea.js executes the code in the module when it is actually called. You can compare them in this link. I have prepared the test file.

Please refer to the following link for details:

Github.com/seajs/seajs…

The main differences are as follows :(link to quote)

  1. The positioning is different. RequireJS wants to be a module loader for browsers as well as environments such as Rhino/Node. Sea.js focuses on the Web browser side, while Node extensions make it easy to run in Node environments.
  2. Different norms are followed. RequireJS follows the AMD (asynchronous module definition) specification, while Sea-.js follows the CMD (common module definition) specification. Different specifications lead to different apis. Sea-.js is closer to CommonJS Modules/1.1 and Node Modules specifications.
  3. There are differences in promotion concepts. RequireJS is trying to get third-party libraries to modify themselves to support RequireJS, and only a few communities have adopted it so far. Sea.js is not forced to adopt independent packaging to “accept all rivers into the Sea”. At present, there is a relatively mature packaging strategy.
  4. Support for development debugging varies. Sea.js pays much attention to the development and debugging of code, with nocache, DEBUG and other plugins for debugging. RequireJS has no obvious support for this.
  5. The plugin mechanism is different. RequireJS adopts the form of interface reserved in the source code, and the plug-in type is relatively single. Sea-.js adopts a common event mechanism, with richer plug-in types.

ESModule

background

ES6, on the level of language standards, implements module functions, and implements them quite simply, aiming to become a common module solution for browsers and servers. Its module functions are mainly composed of two commands: export and import. The export command is used to specify the external interface of a module, and the import command is used to input functions provided by other modules.

use

export

export var name = 'foo module'

export function hello () {
  console.log('hello')}export class Person {}

// This is equivalent to the following code

var name = 'foo module'

function hello () {
  console.log('hello')}class Person {}

export { name, hello, Person }
Copy the code
// The default export mode

export default 'default value'

/ / equivalent to the
var name = 'default value'
export default name;
Copy the code

The import

import defaultVal, {name, hello, Person}  from './foo.js'
/ / equivalent to the
import {default as defaultVal, name, hello, Person} from './foo.js'
Copy the code

Note that the contents in braces are not a deconstruction of the exported object, but a syntax rule.

Commonjs exports a copy of the module’s value, which is a copy of the reference address.

The exported content in esModule is a reference to a variable, as shown in the following example.

// foo.js
let a = 1;
let b = {bar: '3'}
let defaultVal = {defaultVal: 'defaultVal'}
setTimeout(() = > {
  a = 2;
  b = {foo: '4'}
  defaultVal = {defaultVal: 'defaultVal-change'}},1000);

export {a,b}
export default defaultVal;
Copy the code
// main.js
import defaultVal, {default as defaultVal2, a, b} from './foo.js';

console.log('defaultVal', defaultVal); // defaultVal {defaultVal: "defaultVal"}
console.log('defaultVal2', defaultVal2); // defaultVal2 {defaultVal: "defaultVal"}
console.log('a', a); // a 1
console.log('b', b); // b {bar: "3"}

setTimeout(() = > {
  console.log('defaultVal-change', defaultVal); // defaultVal-change {defaultVal: "defaultVal"}
	console.log('defaultVal2-change', defaultVal2); // defaultVal2-change {defaultVal: "defaultVal"}
	console.log('a-change', a); // a-change 2
	console.log('b-change', b); // b-change {foo: "4"}
}, 2000);
Copy the code

According to the above tests, it can be concluded that what is exported through export is the direction of variables. When the direction changes, the value will also change, but the content exported by Export default will not change.

The code address used in this article: github.com/huomarvin/t…