JavaScript modularization AMD

Recently, when writing a small game, I used the RequireJs construction project. By the way, I added RequireJs. Here are some basic and advanced usage.

AMD

AMD Async Module Definition means asynchronous module definition. It is a Javascript modular browser solution. It loads modules asynchronously and does not affect the operation of subsequent statements. All statements that depend on this module are defined in the callback function. The callback function will not run until the loading is completed.

AMD The specification defines a function define, which defines the module through the define method:

 define(id?, dependencies?, factory);

And use the require() statement to load the module:

require([module], callback);

The imported module and the callback function are not synchronized, so the browser will not pretend to die because the imported module is not loaded successfully.

RequireJS

RequireJS is a JavaScript file and module loader based on AMD specification. It is optimized for in browser use and is compatible with other JS environments (Rhino and Node). Using modular script loaders such as RequireJS can improve the speed and quality of your code.

  • Asynchronous loading: using RequireJS, the callback function will be executed after the relevant js are loaded. This process is asynchronous, so it will not block the page.
  • Load on demand: with RequireJS, you can load the corresponding js module when you need to load the js logic. Unnecessary modules will not be loaded. This avoids a large number of requests and data transmission when initializing the web page.

Basic use

According to the official documents and project examples, let's talk about the basic use of ReuqireJS:

Reuqire Download

Download the latest version of RequireJS.

Project Structure

The following is the RequireJS project structure of the official example. Minor changes have been made to the content. www is the root directory of the project, and lib stores some JS libraries that the project depends on, that is, app JS is the main entry file, and the module file written by yourself is stored in the app.

Project Code

1. index.html

Index HTML defines a script tag to introduce require JS, where the data main attribute is a user-defined attribute. This attribute specifies that after loading the reuqire JS, specify the attributes to the JS file under the path and run it. This file is the entry file. Here, app The JS suffix of JS is omitted.

<!DOCTYPE html>
<html>
    <head>
        <script data-main="app" src="lib/require.js"></script>
    </head>
    <body>
        <h1>Hello World</h1>
    </body>
</html>

If the <script/> ~ ~ ~ tag introduces require If the data main attribute is not specified in js, the path of the html file into which the js is imported is taken as the root path. If the data main attribute is specified, that is, if the entry file is specified, the path of the entry file is taken as the root path.

2. app.js

Main JS loads the main module and configures project dependencies. To change the default configuration of RequireJS, you can use require The config function passes in an optional parameter object. Here are some configurations you can use:

// 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({
  // The root path of the module load.
  baseUrl: ".",
     // Used for path mapping of some common library files or folders. The js suffix can be omitted
  paths: {
    app: "app/",
    fmt: "lib/fmt",
  },
});

// Start loading the main app file. Put all of
// your application logic in there.
requirejs(["app/main"]);

If in require If baseUrl is configured in config (), the path of baseUrl will be taken as the root path. This rule will override the effect of data main above.

3. app/

Main JS, we load a message module through the require function, which is used to print some defined strings.

define(function (require) {
  var msg = require("./message");
  msg.helloWorld();
});

Main The module used in JS is defined in message JS, he introduced an output dependency fmt.

define(["fmt"], function (fmt) {
  return {
    helloWorld: function () {
      fmt.println("hello word");
    },
  };
});

The loading methods of these two dependencies are different and will be introduced later.

4. lib/

Lib/fmt In js, I define a js module to simulate the FMT package of go and expose the interface through return. Note that the exposed object is the introduced object.

define(function () {
  var print = function (msg) {
    console.log(msg);
  };
  var println = function (msg) {
    console.log(msg + "\n");
  };

  return {
    moduleName: "fmt",
    print: print,
    println: println,
  };
});

Require Config function configuration

To change the default configuration of RequireJS, you can use require The config function passes in an optional parameter object, which can be configured with five attributes:

require.config({
    baseUrl: '.',
      paths: {
        app: "app/",
        fmt: "lib/fmt",
      },
    shim: {
        'backbone': {
            deps: ['underscore', 'jquery'],
            exports: 'Backbone'
        },
        'underscore': {
            exports: '_'
        }
    },
    config: {
        'app/main': {
            ENV: 'development'
        }
    },
     map: {
        'script/newmodule': {
            'foo': 'foo1.2'
        },
        'script/oldmodule': {
            'foo': 'foo1.0'
        }
    },
});

1. baseUrl

baseUrl as the root path of the load module. After configuring this attribute, all path definitions are based on this root path (including configuration and dependency import).

2. path

It is used for path mapping of some common library files or folders. After definition, it can be directly used in dependency import.

3. shim

Although some popular function libraries comply with AMD specification, there are still many libraries that do not. shim is designed to load these non amd standard js and solve their loading order, such as the above backbone.

4. config

config passes configuration information to a module. These configurations are often application level information, and a means is needed to pass them down to the module.

config: {
  'app/main': {
    ENV: 'development'
  }
}

You can get this information by loading special dependent module s.

define(['module'], function (module) {
  var ENV = module.config().ENV;  // development
  var msg = require("./message");
  msg.helloWorld();
});

5. map

For a given module prefix, a different module ID is used to load the module. This approach is important for some large projects. For example, after the above configuration, different modules will use different versions of foo.

When some/newmodule calls require('foo'), it will get foo1.2 JS file. When oldmodule calls require('foo'), it will get foo1.0 JS file.

Map also supports *, which means "for all module loads, use this map configuration". If there is a more detailed map configuration, it will take precedence over the * configuration.

requirejs.config({
    map: {
        '*': {
            'foo': 'foo1.2'
        },
        'some/oldmodule': {
            'foo': 'foo1.0'
        }
    }
});

Module definition

1. object

If a module contains only key value pairs without any dependencies, it can be defined directly in define.

define({
    foo: "foo",
    bar: function(){}
});

2. objects requiring pretreatment

define(function () {
    console.log("Do something...");
 
    return {
        foo: "foo",
        bar: function(){}
    }
});

3. there is only one function

Functions without any dependencies are defined as follows:

define(function () {
    return function (){};
});

When calling, directly press () to call:

require(['lib/foo'],function (foo) {
    foo();
});

4. other dependencies are required:

define(['jquery'],function($){
    return function (){};
});

Cyclic loading

Suppose we have the following two interdependent modules a and b. if we call the b method of module b.

// app/a.js
define(['app/b'],function(b){
    return function() { b() }
});

// app/b.js
define(['app/a'],function(a){
    return function() { a() }
});

It will be found that b calls a normally, but calling the b method in a will report an undefined error.

require(['app/b'],function (b) {
    b();    // b is not defined.
});

Solution:

Circular dependency is rare. For circular dependency, as long as any edge of the dependency ring is a runtime dependency, the ring is alive in theory. If all the edges are load dependent, the ring is dead.

Modify module a as follows, that is, it no longer relies on pre loading. Instead, we introduce the require dependency, then load module b through the require() method, and execute it in the callback.

// app/a.js
define(['require'],function(require){
    var b = require('b')
    return function() {
      b()
    }
});

Tags: Javascript Front-end

Posted by integravtec on Tue, 31 May 2022 22:35:01 +0530