Understanding of CommonJS, AMD and CMD modular programming

1,commonJS

principle

The fundamental reason why the browser is not compatible with CommonJS is the lack of four nodes JS environment variables.

  • module
  • exports
  • require
  • global

As long as these four variables can be provided, the browser can load the CommonJS module.

Here is a simple example.

var module = {
  exports: {}
};

(function(module, exports) {
  exports.multiply = function (n) { return n * 1000 };
}(module, module.exports))

var f = module.exports.multiply;
f(5) // 5000 

The above code provides two external variables module and exports to an immediate execution function, and the module is placed in the immediate execution function. The output value of the module is placed in module Exports, which realizes the loading of modules.

Implementation of Browserify

Knowing the principle, you can make tools. Browserify It is currently the most commonly used tool for common JS format conversion.

Take a look at an example, main JS module loading foo JS module.

// foo.js
module.exports = function(x) {
  console.log(x);
};

// main.js
var foo = require("./foo");
foo("Hi");

Use the following command to set main JS to the format available to the browser.

$ browserify main.js > compiled.js

What exactly did Browserify do? Install it browser-unpack , you can see clearly.

$ npm install browser-unpack -g

Then, the previously generated compile JS unpacking.

$ browser-unpack < compiled.js

[
  {
    "id":1,
    "source":"module.exports = function(x) {\n  console.log(x);\n};",
    "deps":{}
  },
  {
    "id":2,
    "source":"var foo = require(\"./foo\");\nfoo(\"Hi\");",
    "deps":{"./foo":1},
    "entry":true
  }
]

It can be seen that browerify puts all modules into an array. The id attribute is the number of the module, the source attribute is the source code of the module, and the deps attribute is the dependency of the module.

Because main JS loaded foo JS, so the deps attribute is specified/ Foo corresponds to module 1. When executing, when the browser encounters the require('./foo') statement, it will automatically execute the source attribute of module 1 and the executed module The value of the exports property is output.

Tiny Browser Require

Although Browserify is very powerful, it cannot be operated in the browser, which is sometimes very inconvenient.

My basis mocha The internal implementation of a pure browser CommonJS module loader tiny-browser-require  . You don't need the command line at all, just put it into the browser directly, and all the code is only more than 30 lines.

Its logic is very simple, that is to read the module into the array, and the loading path is the module id.

function require(p){
  var path = require.resolve(p);
  var mod = require.modules[path];
  if (!mod) throw new Error('failed to require "' + p + '"');
  if (!mod.exports) {
    mod.exports = {};
    mod.call(mod.exports, mod, mod.exports, require.relative(path));
  }
  return mod.exports;
}

require.modules = {};

require.resolve = function (path){
  var orig = path;
  var reg = path + '.js';
  var index = path + '/index.js';
  return require.modules[reg] && reg
    || require.modules[index] && index
    || orig;
};

require.register = function (path, fn){
  require.modules[path] = fn;
};

require.relative = function (parent) {
  return function(p){
    if ('.' != p.charAt(0)) return require(p);
    var path = parent.split('/');
    var segs = p.split('/');
    path.pop();

    for (var i = 0; i < segs.length; i++) {
      var seg = segs[i];
      if ('..' == seg) path.pop();
      else if ('.' != seg) path.push(seg);
    }

    return require(path.join('/'));
  };
};

When using, first put the above code into the page. Then, put the module in the following immediate execution function, and you can call it.

<script src="require.js" />

<script>
require.register("moduleId", function(module, exports, require){
  // Module code goes here
});
var result = require("moduleId");
</script>

Or use the previous main JS load foo JS as an example.

require.register("./foo.js", function(module, exports, require){
  module.exports = function(x) {
    console.log(x);
  };
});

var foo = require("./foo.js");
foo("Hi");

Note that this library only simulates three variables: require, module and exports. If the module also uses global or other Node specific variables (such as process), it can be provided by executing the function immediately.

2,AMD

After nodeJS based on commonjs specification came out, the concept of server-side module has been formed. Naturally, we want client-side module. And it is best that the two can be compatible. A module does not need to be modified and can run in both server and browser. However, due to a major limitation, the commonjs specification is not suitable for browser environment. Or the above code. If it runs in the browser, there will be a big problem. Can you see it?

var math = require('math');
math.add(2, 3);

The second line math Add (2, 3), run after the first line of require('math '), so you must wait for math JS loading completed. In other words, if the loading time is long, the whole application will stop there and wait. You will notice that , require , is synchronized.

This is not a problem for the server side, because all modules are stored in the local hard disk and can be loaded synchronously. The waiting time is the reading time of the hard disk. However, for the browser, this is a big problem, because the modules are placed on the server side, and the waiting time depends on the speed of the network. It may take a long time, and the browser is in the "fake death" state.

Therefore, modules on the browser side cannot be loaded synchronously, but can only be loaded asynchronous ly. This is the background of AMD specification.

CommonJS is mainly formulated for the performance of JS in the back end. It is not suitable for the front end. With the emergence of AMD (asynchronous module definition), it mainly formulates specifications for the performance of JS in the front end.

AMD It is the abbreviation of "Asynchronous Module Definition", which means "Asynchronous Module Definition". It loads modules asynchronously, and the loading of modules does not affect the operation of subsequent statements. All statements that depend on this module are defined in a callback function. The callback function will not run until the loading is completed.

AMD also uses the require() statement to load modules, but unlike CommonJS, it requires two parameters:

require([module], callback);

The first parameter [module] is an array whose members are the modules to be loaded; The second parameter callback is the callback function after loading successfully. If the previous code is rewritten into AMD form, it is as follows:

require(['math'], function (math) {
    math.add(2, 3);
});

math.add() is not synchronized with math module loading, and the browser will not fake death. So obviously, AMD is more suitable for browser environment. At present, there are mainly two Javascript libraries that implement amd specification: require.js and curl.js.

(1) Why use require js?

In the earliest days, all Javascript code was written in one file, which was enough to load. Later, there were more and more codes. One file was not enough. It had to be divided into multiple files and loaded in turn. The following web page code, I believe many people have seen.

<script src="1.js"></script>
<script src="2.js"></script>
<script src="3.js"></script>
<script src="4.js"></script>
<script src="5.js"></script>
<script src="6.js"></script>

This code loads multiple js files in turn.

This way of writing has great disadvantages. First of all, when loading, the browser will stop web page rendering. The more files are loaded, the longer the web page will lose response; Secondly, due to the dependency between JS files, the loading order must be strictly guaranteed (for example, 1.js in the above example should be in front of 2.js). The modules with the greatest dependency must be loaded at the end. When the dependency is very complex, the coding and maintenance of the code will become difficult.

require.js was born to solve these two problems:

  • Realize the asynchronous loading of js files to avoid the loss of response of web pages;
  • Manage the dependencies between modules to facilitate code writing and maintenance.

(2)require.js loading

Use require The first step of JS is to go to the official website first download Latest version.

After downloading, suppose you put it under the js subdirectory, you can load it.

<script src="js/require.js"></script>

One might think that loading this file may also cause the web page to lose response. There are two solutions. One is to load it at the bottom of the web page, and the other is to write it as follows:

<script src="js/require.js" defer async="true" ></script>

The async attribute indicates that the file needs to be loaded asynchronously to prevent the web page from losing response. IE does not support this attribute, only supports defer, so write defer as well.

Load require js, the next step is to load our own code. Suppose our own code file is main js, also under the js directory. Then, just write it as follows:

<script src="js/require.js" data-main="js/main"></script>

The data main attribute is used to specify the main module of the web program. In the above example, it is main under the js directory js, this file will be required first js load. Due to require js the default file suffix is js, so you can put main js is abbreviated as main.

(3) Writing method of main module

Main in the previous section JS, I call it "main module", which means the entry code of the whole web page. It's a bit like the main() function of C language. All the code starts from here.

Let's see how to write main js.

If our code does not depend on any other module, we can write javascript code directly.

// main.js
alert("Loading succeeded!");

But in this case, there is no need to use require JS. The real common situation is that the main module depends on other modules. At this time, the require() function defined by AMD specification should be used.

// main.js
require(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){
    // some code here
});

The require() function takes two arguments. The first parameter is an array, which indicates the modules it depends on. The above example is ['modulea ',' moduleb ',' modulec '], that is, the main module depends on these three modules; The second parameter is a callback function, which will be called when the previously specified modules are loaded successfully. The loaded modules are passed into the function in the form of parameters, so they can be used inside the callback function.

require() asynchronously loads moduleA, moduleB and moduleC, and the browser will not lose response; The callback function specified by it will run only after the previous modules are loaded successfully, which solves the problem of dependency.

Next, let's look at a practical example.

It is assumed that the main module relies on three modules: jquery, underscore and backbone JS can be written as follows:

require(['jquery', 'underscore', 'backbone'], function ($, _, Backbone){
    // some code here
});

require.js will load jQuery, underscore and backbone first, and then run the callback function. The code of the main module is written in the callback function.

(4) Module loading

In the last example in the previous section, the dependent modules of the main module are ['jquery ',' underscore ',' backbone ']. By default, require JS assumes that these three modules are related to main JS are in the same directory, and the file names are jQuery js,underscore.js and backbone JS, and then automatically load.

Use require Config () method, we can customize the loading behavior of the module. require.config() is written in the header of the main module (main.js). Parameter is an object whose paths attribute specifies the loading path of each module.

require.config({
    paths: {
        "jquery": "jquery.min",
        "underscore": "underscore.min",
        "backbone": "backbone.min"
    }
});

The above code gives the file names of the three modules, and the default path is main js is in the same directory (js subdirectory). If these modules are in other directories, such as js/lib directory, there are two ways to write them. One is to specify paths one by one.

require.config({
    paths: {
        "jquery": "lib/jquery.min",
        "underscore": "lib/underscore.min",
        "backbone": "lib/backbone.min"
    }
});

The other is to directly change the base directory (baseUrl).

require.config({
    baseUrl: "js/lib",
    paths: {
        "jquery": "jquery.min",
        "underscore": "underscore.min",
        "backbone": "backbone.min"
    }
});

If a module is on another host, you can also directly specify its web address, such as:

require.config({
    paths: {
        "jquery": "https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min"
    }
});

require.js requires that each module is a separate js file. In this way, if multiple modules are loaded, multiple HTTP requests will be issued, which will affect the loading speed of the web page. Therefore, require js provides a Optimization tools , after the modules are deployed, you can use this tool to merge multiple modules into one file to reduce the number of HTTP requests.

(5) AMD module writing

require.js loaded module adopts amd specification. In other words, the module must be written according to AMD regulations.

Specifically, the module must be defined with a specific define() function. If a module does not depend on other modules, it can be defined directly in the define () function.

Suppose there is a math JS file, which defines a math module. So, math JS should be written like this:

// math.js
define(function (){
    var add = function (x,y){
        return x+y;
    };
    return {
        add: add
    };
});

The loading method is as follows:

// main.js
require(['math'], function (math){
    alert(math.add(1,1));
});

If this module also depends on other modules, the first parameter of the define() function must be an array indicating the dependency of the module.

define(['myLib'], function(myLib){
    function foo(){
        myLib.doSomething();
    }
    return {
        foo : foo
    };
});

When the require() function loads the above module, it will first load mylib JS file.

(6) Load nonstandard modules

In theory, require JS must be a module defined by the define() function according to AMD specification. But in fact, although some popular function libraries (such as jQuery) comply with AMD specification, more libraries do not. So, require Can JS load non-standard modules?

The answer is yes.

Such modules need to use require. Before loading with require() Config () method to define some of their characteristics.

For example, the two libraries, underscore and backbone, are not written in AMD specification. If you want to load them, you must first define their characteristics.

require.config({
    shim: {
        'underscore':{
            exports: '_'
        },
        'backbone': {
            deps: ['underscore', 'jquery'],
            exports: 'Backbone'
        }
    }
});

require.config() accepts a configuration object. In addition to the paths attribute mentioned above, this object also has a shim attribute, which is specially used to configure incompatible modules. Specifically, each module should be defined

  • exports value (output variable name), indicating the name when the module is called externally;
  • deps array, indicating the dependency of the module.

For example, the plug-in of jQuery can be defined as follows:

shim: {
    'jquery.scroll': {
        deps: ['jquery'],
            exports: 'jQuery.fn.scroll'
    }
}

(7)require.js plug-in

require.js also provides a series of plug-in unit , realize some specific functions.

The domready plug-in allows the callback function to run after the page DOM structure is loaded.

require(['domready!'], function (doc){
    // called once the DOM is ready
});

For text and image plug-ins, require is allowed JS loads text and picture files.

define([
    'text!review.txt',
    'image!cat.jpg'
],
function(review,cat){
    console.log(review);
    document.body.appendChild(cat);
});

Similar plug-ins include json and mdown, which are used to load json files and markdown files.

The Chinese version of AMD WIKI can be viewed if you don't understand it when doing it: AMD WIKI Chinese version

3,CMD

The famous Yubo wrote seajs, which follows the CMD specification proposed by him. It is quite similar to AMD, but it feels more convenient to use. The most important thing is the Chinese version, which has everything: seajs official doc

define(function(require,exports,module){...});

Used seajs, this is not strange, right.

We talked about AMD and RequireJS realized AMD. CMD looks like AMD. Does RequireJS look like SeaJS?

Although CMD is quite similar to AMD, the difference is quite obvious. Both official and unofficial have explained and understood it. I think it's very good:

Official description of similarities and differences between SeaJS and RequireJS

The biggest similarities and differences between SeaJS and RequireJS (this is also very good)

Keywords: Javascript

Added by Talguy on Thu, 10 Mar 2022 09:43:36 +0200