Recently work needs, to achieve a specific environment of the module loading scheme, the implementation of some technical details are puzzled, then reference some of the project API design conventions and implementation, record the memo.
This article does not discuss why modularity, and modularity-related specifications, directly consider a number of technical implementation principles.
1. Simple implementation of modularity
At first I thought that if my code had only one file, would those lines be implemented?
Main.js
var modules = {}var define = function(id,factory){ moudles[id] = factory}var require = function(id){ return modules[id]}define("moduleA",{text:"I am text"})var moduleA = require("moduleA");console.log(moduleA)
Main.html
<script src="main.js"></script>
2. Split Multiple Files
Later business requirements grew, and one of my code files gradually swelled to nearly 2w multiline. This time every change file to find a function to find a long time Ah, my editor also began to crash, and spread to the service side, but also have to wait ...
So I split the file into 3:
1.module.js
var modules = {}var define = function(id,factory){ moudles[id] = factory}var require = function(id){ return modules[id]}
2.modulea.js
define("moduleA",{text:"I am text"})
3.main.js
var A = require("moduleA")console.log(A)
So I'm going to write this in HTML.
<script src="module.js"><script><script src="moduleA.js"><script><script src="main.js"><script>
Later learned that I can use the component Tools gulp
concat
and watch
modules, can listen to file changes, automatic generation of large files, so that in the development of the module can be split into multiple files, while running is in a file. You can find out more about the relevant information.
3. Loading files by module
The above mentioned the use of component tools to implement packaging into a file, there is a drawback, code if there are errors, the number of error lines can not correspond to the corresponding file module line number, debug difficult.
At this time it seems that only the component tools are not dependent, and we implement loading other modules in the code. It seems to be quite simple.
We need to know that the script tag can be dynamically created and loaded with JS.
var loadScript = function(src){ var script = document.createElement("script") script.src = src document.head.appendChild(script)}
So we can load it in main.js.
loadScript("module.js")loadScript("moduleA.js")
This allows you to introduce only one main file to the page, and then introduce the other module files in the main file.
Know more about loadScript
This code loading method, we will know that the parallel loading is non-sequential execution, it is possible to Modulea code execution when the module has not been executed, this will be an error variable define is not defined
.
4. control file Load Order
Script will have some state during loading, support for setting up callback functions such as onload
onreadysteadychange
This, we can load another module to control the file loading order when a module is loaded.
The JSONP technology we use is probably one such principle.
var loadScript = function(src,callback){ var script = document.createElement("script") script.src = src script.onload = callback document.head.appendChild(script)}loadScript("module.js",function(){ loadScript("moduleA.js",function(){ var A = require("moduleA") console.log(A) })})
The disadvantage is that the code to write layers of callback, the loading order of the module needs to write code to manage the person themselves.
5.XHR Loading code
The script tag can be set to SRC to load remote code, and the code can be written directly inside the tag.
<script> define("A","i am A")</script>
So we can load the remote code text through the Xhr object, and then insert it dynamically, for example innerHTML even, xhr have synchronous loading method, let our serial loading code, avoid write the heavy callback. Of course, synchronous XHR request performance is low
XHR has a mishap is affected by the browser-like policy, not convenient cross-domain.
6. Implementing the Advanced API
With some of the basics above, we can encapsulate some of the advanced APIs.
In general, we only need such a, the implementation of the define(id,deps,factory)
module definition and loading is basically enough.
define("moduleC",["moduleA","moduleB"],function(moduleA,moduleB){ console.log(moduleA,moduleB)})
Such a define did such a thing
- Associating IDs with factory
- With the Loadscript scheme, to recursively load the deps, to ensure that the module is dependent on the module itself depends on the module is loaded complete.
- After the collection is completed, the relevant modules are passed to factory in Deps order.
7. Automatic collection of dependencies
We feel that each time to write a bunch of dependencies, and then also to ensure that the deps order and the factory of the variable order of the same, one by one, there are some egg pain, we will want to remove deps, change to write in the factory to rely on.
Modulec.js
define("moduleC",function(require){ var moduleA = require("moduleA") var moduleB = require("moduleB")})
This time need to use a magic feature of JS, function of the ToString method can get the source code of the functions. In this way, we can analyze which modules are require by some means. can look here https://github.com/seajs/seajs/issues/478
Of course, in order to be able to analyze the require of which modules, we have to require to do some agreement, is to hope that require have some specific flags, so that we can through the code text static analysis of require items.
For example, no, see https://github.com/seajs/seajs/issues/259 in detail.
var req = requirereq("moduleA")
Then, it cannot be compressed with a generic compression tool, because the compression tool compresses the require variable.
8. Defining anonymous Modules
Sometimes we think that the filename is already representative of the module name, we don't even want to define the module name.
Modulec.js
define(function(){ var moduleA = require("moduleA") var moduleB = require("moduleB") return { A : moduleA, B : moduleB };})
I was shocked when I saw this API usage, because when I realized define, I would have the ID and factory associated with it. Later calm down, think ID must be some, just have a way not through the function parameter pass.
Sure enough, there is a document with an object called Currentscript that can get the object of the currently executing script, So modulec.js at the time of execution, define can be document.currentscript to get src for Modulec.js script object, and then can extract the ID.
Here are some details about browser compatibility:
- Document.currentscript is supported only by modern browsers.
- IE6-10 will have some black magic, take advantage of the browser single-threaded execution of the characteristics of the page to get all the script tag, to determine its readystate
interactive
, the script is Document.currentscript
- Using Error.stack, get the file call stack to analyze to get currentscript
It is convenient to write anonymous modules, but it will bring some trouble.
For example, it cannot be packaged directly into a file because it depends on the file name of the module, which is well understood.
define(function(){ return 100})define(function(){ return 200})
9. Loading this resource
Very nostalgic for the work of the spore in the code structure and modular scheme, development does not need to rely on build tools, templates directly write HTML files, without packaging AMD. Wait a minute.. , template directly write HTML file is how to do, try to see the source code, the basic can not understand, Spore source code is too difficult to read. Later grabbed the bag to know that the original is back-end mates, the specific directory name returned under the HTML file will be automatically wrapped on define, black magic.
Of course there are other ways, in general, is to use XHR, load the corresponding text, and then use the Eval setting execution context for global, to wrap the define.
10. Reference:
- Http://requirejs.org/docs/why.html
- https://github.com/seajs/seajs/issues/259
- Http://www.cnblogs.com/rubylouvre/archive/2013/01/23/2872618.html
Some details about the implementation of the front-end JS Module loader