Many new JavaScript architecture scenarios and standards have been proposed by COMMONJS to provide unified guidance for front-end development. AMD specification is one of the more famous, the full name is asynchronous module definition, that is, the asynchronous module loading mechanism, the complete description of the module definition, dependency, reference relationship and loading mechanism. The AMD specification authors have personally implemented the AMD-compliant Requirejs. This article will detail AMD and Requirejs
AMD Specifications
The AMD (asynchronous module definition) translates to an asynchronous module definition. Asynchronously emphasizes that when loading modules and other modules on which modules depend, asynchronous loading is used to avoid block loading blocking the rendering progress of the Web page.
AMD, as a specification, simply defines its syntax API without caring about its implementation. The AMD specification is simple enough to have only one API, the Define function
define ([Module-name], [array-of-dependencies?], [Module-factory-or-object]);
Module-name: module identification, can be omitted
Array-of-dependencies: The dependent module can be omitted
Module-factory-or-object: The implementation of a module, or a JavaScript object
The Define function has an asynchronous nature. When the Define function executes, the dependent modules listed in the second parameter are called asynchronously, when all the modules are loaded, and if the third parameter is a callback function, then the system module is available, and the module that relies on itself is notified that it is already available.
Load
The first step in using Require.js is to go to the official website to download the latest version first. After downloading, assume that it is placed under the JS subdirectory, you can load the
<script src= "Js/require.js" ></script>
The script tag in HTML blocks the rendering of the Web page during loading and execution, so it is generally required to place the script tag at the bottom of the body element as much as possible to speed up the page display, and one way is to load the JS file asynchronously by loading it. This prevents the JS file from blocking HTML rendering
<script src= "Js/require.js" defer async></script>
Entry file
Require.js checks the Data-main property when loading, and once the Requirejs itself loads, the main.js that the Data-main attribute points to is loaded again asynchronously. This main.js is the gateway to all the logic of the current page, ideally, the entire page only needs this script tag, and the other files that are dependent on the Requirejs load
<script data-main= "Scripts/main" src= "Js/require.js" ></script>
[note] The script set in Main.js is loaded asynchronously. So if you configure other JS loading in the page, there is no guarantee that they depend on the JS has been loaded successfully
<script data-main= "Scripts/main" src= "js/require.js" ></script><script src= "Js/other.js" ></ Script>
"Internal mechanism"
Within Requirejs, each module dependency is loaded as a script tag using Head.appendchild (). Requirejs waits for all dependencies to be loaded, calculates the correct call order for module-defined functions, and then calls them sequentially
Module
The module differs from the traditional script file, and it defines a scope well to avoid global namespace contamination. It can explicitly list its dependencies and inject those dependencies in the form of a function (the function that defines the module), without referencing global variables. The Requirejs module is an extension of the module pattern, with the benefit of not having to refer to other modules globally
Requirejs's module syntax allows it to load multiple modules as quickly as possible, although the order of loading is variable, but the order of dependencies is ultimately correct. And because you don't need to create global variables, you can even load different versions of the same module simultaneously on the same page.
A file should only have 1 modules defined. Multiple modules can be packaged with built-in optimization tools for their organization
If our code does not depend on any other modules, then you can write the JavaScript code directly
Main.js
Console.log (1);
But in this case, there is no need to use the require.js. The real common scenario is that the main module relies on other modules, and the Require () function defined by the AMD specification will be used
Main.jsrequire ([' Modulea '], function (a) {Console.log (a);}); /modulea.jsdefine (function () { return 1;})
This throws a question, why does the main module use the Require () function, and the module Modulea use the Define () function? Because the module defined by define () can be called, and require () is not available. The main module main.js is a portal file that needs to call other modules without being called by another module, so use require () or define (). and Modulea needs to be called, so you can only use define ()
If you change the Define () method in Modulea.js to the Require () method, the undefined is returned.
Main.jsrequire ([' Modulea '], function (a) {Console.log (a);}); /modulea.jsrequire (function () { return 1;})
"Simple value pair"
In the above module Modulea, the callback function returns a number. In fact, a module can have many forms, such as a simple value pair
Define ({ color: "Black", Size: "Unisize"});
The results returned are as follows:
"Function-defined"
If a module does not have any dependencies, but requires a function to do the setup work, define the function in define () and pass it to define ()
Define (function () { //do setup work here return { color: "Black", Size: "Unisize" }});
The results returned are as follows:
"There is a functional definition of dependency"
If the module has a dependency: the first parameter is an array of dependent names, and the second argument is a function that is called to define the module after all dependencies of the module have been loaded, so the module should return an object that defines the module. The dependency is injected into the function as a parameter, and the argument list corresponds to the dependent name list one by one
Modulea.jsdefine ([' Moduleb '], function (b) { var num = ten; return B.add (num); }); Moduleb.jsdefine ({ add:function (n) { return n+1; }});
"Naming module"
Define () can contain a module name as the first parameter
Modulea.jsdefine ("Modulea", [' Moduleb '], function (b) { var num = ten; return B.add (num); });
These are often generated by the optimization tool. You can also explicitly specify the module name yourself, but this makes the module less portable-that is, if you move the file to a different directory, you have to rename it. It is generally better to avoid hard coding of the modules, but to give the optimizer tools to generate them. The optimization tool needs to generate the module name to make multiple modules into a package, speeding up to the browser loading speed
Path configuration
The base element in HTML is used to specify the underlying url,requirejs of all relative URL addresses in the document BaseURL is similar to this base element, since Requirejs always dynamically requests the dependent JS file, So inevitably involves a JS file path resolution problem, requirejs default to adopt a BaseURL + ModuleID resolution, REQUIREJS to its processing follows the following rules:
1, in the absence of the use of Data-main and config, baseurl default to the current page directory
2, in the case of Data-main, main.js in front of the part is BaseURL, such as the above js/
3, in the case of a config, the BaseURL is configured with Config as the quasi-
The above three ways, priority from low to high arrangement
Requirejs loads all the code with an address relative to BaseURL. The top-level script tag contains a special attribute Data-main,require.js uses to start the script loading process, while BaseURL is typically set to a directory that is consistent with that property
<script data-main= "Js/main.js" src= "Scripts/require.js" ></script>
In the example in the module section, the code looks like this
Main.jsrequire ([' Modulea '], function (a) {Console.log (a);}); /modulea.jsdefine (function () { return 1;})
The portal file Main.js relies on Modulea, written directly as [' Modulea '], by default, Require.js assumes Modulea is in the same directory as Main.js, ' js/modulea.js ', The file name is Modulea.js and then automatically loaded
Using the Require.config () method, we can customize the load behavior of the module. Require.config () is written on the head of the main module (main.js). A parameter is an object that specifies the load path for each module by the paths property of the object.
Create a new test folder under the Demo folder and create a new Modulea.js file under the test folder with the following content
Modulea.jsdefine (function () { return 2;})
And in the original JS folder, there is still a modulea.js file, the content is as follows
Modulea.jsdefine (function () { return 1;});
When the config configuration is main.js under the JS folder
Main.jsrequire.config ({ baseUrl: ' Test '}) require ([' Modulea '], function (a) { console.log (a);});
The result is 2, which indicates that the ' test/modulea.js ' file is recognized
When the main.js under the JS folder is not configured with Config
Main.jsrequire ([' Modulea '], function (a) { console.log (a);});
The result is 1, which indicates that the ' js/modulea.js ' file is recognized
Requirejs default assumes that all dependent resources are JS scripts, so you do not need to add the ". js" suffix to the module ID, Requirejs will automatically append the suffix when parsing the module ID to path.
If the path of a module is deep, or the file name is particularly long, such as ' js/lib/modulea.min.js ', you can use the Paths property in the Config configuration object
Main.jsrequire.config ({ paths:{ ' Modulea ': ' Lib/modulea.min ' }}) require ([' Modulea '], function (a) { Console.log (a);}); /modulea-min.jsdefine (function () { return 3;})
Result is 3
Note that the paths ' Modulea ' setting here is ' lib/modulea.min ', not ' js/lib/modulea.min ', because the file parsing in Requirejs is a "BASEURL + paths" The parsing process
The index.html entry file is set to ' Js/main ', so BaseURL is ' JS '. So ' baseUrl + paths ' = ' js/lib/modulea.min '
<script src= "Require.js" data-main= "Js/main" defer async></script>
If BaseURL is set in the Config configuration object, this will be the case
Main.jsrequire.config ({ baseUrl: ' Js/lib ', paths:{ ' Modulea ': ' Modulea.min ' }}) require ([' Modulea '], function (a) { console.log (a);});
The result is also that 3,baseurl is ' js/lib ' and Paths is ' modulea.min '. So ' baseUrl + paths ' = ' js/lib/modulea.min '
If a module ID conforms to one of the following rules, its ID resolution bypasses the regular "BASEURL + paths" configuration, but instead directly loads it as a script relative to the current HTML document: 1, ending with ". js", 2, including the URL protocol, such as "http:" or " HTTPS: "
As shown below, the Require () function relies on a module path of ' Js/modulea.js '
Main.jsrequire.config ({ baseUrl: ' Js/lib ', paths:{ ' Modulea ': ' Modulea.min ' }}) require ([' js/ Modulea.js '], function (a) { console.log (a);});
And the code for the file is as follows, the path is ' js/modulea.js ' instead of ' js/lib/modulea.min ', so the end result is 1
Modulea.jsdefine (function () { return 1;});
In general, it is best to use BaseURL and "Paths" config to set the module ID. It provides additional flexibility, such as easy script renaming, relocation, and so on. At the same time, in order to avoid messy configuration, it is best not to use multi-level nested directory hierarchy to organize the code, but instead of all the scripts are placed in the BaseURL, or divided into a project library/third-party library of a flat structure, as follows
www/ index.html js/ app/ sub.js lib/ jquery.js canvas.js main.js
CommonJS
As mentioned earlier, COMMONJS is primarily used for server-side programming, such as Nodejs. With the packaging tool browserify you can format the COMMONJS to make it available on the browser side
While Requirejs supports a simple wrapper commonjs, simply wrap a layer of functions in the outer layer of the COMMONJS code to run directly on the browser side
Define (function (Require, exports, module) {});
If the module also relies on other modules, such as dependent module Modulea, the code is as follows
define ([' Modulea '],function (Require, exports, module) {});
The code for the Commonjs form of A.js and B.js is as follows
A.jsvar a = 100;MODULE.EXPORTS.A = a;//B.jsvar result = require ('./a '); Console.log (RESULT.A);
Index.html Direct Reference B.js will error, prompting require not defined
After transforming A.js and B.js, the code is as follows
A.jsdefine (function (Require, exports, module) { var a = +; MODULE.EXPORTS.A = A;}); /B.jsdefine (function (Require, exports, module) { var result = require ('./a '); Console.log (RESULT.A);});
Index.html set the portal file to ' js/b ', the result is 100
<script src= "Require.js" data-main= "js/b" defer async></script>
Lazy Loading
The following example, the entry file Main.js code is as follows
Main.jsrequire ([' A '], function (a) { console.log (' main '); Document.onclick = function () { a.test (); }});
The code that depends on the module a.js is as follows
Define (function () { console.log (' a '); return { test:function () { console.log (' a.test ');}} )
When executed on the browser side, the browser will download the A.js file even if the page is not clicked. This performance drain is not negligible.
AMD retains the three functions of require, exprots, module in Commonjs. You can not list dependencies in the dependencies array. Instead, the code uses require to introduce
The code after the rewrite is as follows
Main.jsdefine (function () { console.log (' main '); Document.onclick = function () { require ([' A '],function (a) { a.test ();});} ); /a.jsdefine (function () {
Console.log (' a '); return { test:function () { console.log (' a.test ');}} )
When executing on the browser side, if you do not click on the page, the browser will not download the A.js file, so that lazy loading
Other configurations
In Requirejs, in addition to the path configuration, there are other configurations
"Configuration Settings"
In the previous example, we configured the path in Requirejs to be configured through the Config object in the portal file main.js. In fact, Requirejs can be configured without a portal file.
1. Embed JavaScript code in the index.html file
In an HTML file, the REQUIREJS is configured immediately after the Requirejs file is loaded, which is equivalent to turning the Main.js file into an inline JavaScript file
<! DOCTYPE html>
2, the configuration as a global variable "require" before the require.js load is defined, it will be automatically applied
One problem here is that if require is defined in advance as a global variable, the Data-main portal file is set based on BaseURL.
[note] Use the form of var require = {} instead of Window.require = {}. The latter is not working properly in IE
<! DOCTYPE html>
"Shim"
The Shim property is a dependency and export configuration for those "browser global variable injection" scripts that do not use define () to declare dependencies, to set modules, or to load non-canonical modules
For example, both underscore and backbone are not written in the AMD specification. If you want to load them, you must first define their characteristics. Specifically, each module defines (1) The exports value (the output variable name), indicating the name of the external invocation of the module, and (2) the Deps array, indicating the module's dependency
With the following configuration, it is now possible to invoke the underscore API using backbone to invoke the backbone API
Require.config ({shim: {' underscore ': {exports: ' _ '}, ' backbone ': {deps: [' underscore ', ' jquery '], exports: ' Backbone '}});
The plug-in for jquery can be defined as follows, and the plugin's API can now be invoked via JQuery.fn.scroll
Shim: {' Jquery.scroll ': {deps: [' jquery '], exports: ' JQuery.fn.scroll '}}
Plug - insRequire.js also offers a range of plugins for specific functions
"Dom Ready"
The Requirejs loading module is very fast and it is possible that the script has been loaded before the page Dom is ready. The work that needs to interact with the DOM should wait for Dom ready. Modern browsers are informed by domcontentloaded events.
However, not all browsers support domcontentloaded. The Domready module implements a cross-browser approach to determining when the DOM is ready
Main.jsrequire ([' domready! '], function () { console.log (' Ready ');});
"Text"
Text plug-ins can be used to load such as. html,. css, etc., through the plug-in to achieve a complete component (structure + logic + style) of the component development
Require (["Some/module", "text!some/module.html", "Text!some/module.css"], function (module, HTML, CSS) { });
Detailed AMD specification and implementation of REQUIREJS in engineering