At present, the selection of a typical front-end project technical framework mainly includes the following three aspects:
- JS modular framework. (Require/Sea/ES6 Module/NEJ)
- Front-end template framework. (React/Vue/Regular)
- State management framework. The Flux/Redux series will cover the above three aspects and try to build a simple wheel yourself.
This article introduces JS modularity. JS modularization is an inevitable measure taken by engineering with the development of front-end technology and the explosive growth of front-end code. Current modular ideas are divided into CommonJS, AMD and CMD. About the difference between the three, we are basically how much to understand, and a lot of information, here will not repeat.
The core idea of modularization:
- Break up. The JS code is divided into multiple reusable JS code files (modules) according to the functional logic.
- Load. How to load the module for execution and output.
- Injection. The ability to inject the output of one JS module into another.
- Dependency management. There are many front-end engineering modules, which need to manage the dependency between modules.
Based on the core ideas above, it can be seen that there are two key problems in designing a modular tool framework: one is how to execute one module and inject the resulting output into another module; Another is that in large projects, the dependency relationship between modules is very complex, how to make modules in the correct order of dependency injection, this is dependency management.
Here is a concrete example of implementing a simple browser-based AMD modularity framework (similar to NEJ) that exposes a define function, injects dependencies into callback functions, and returns module output. The implementation is shown in the code below.
define([
'/lib/util.js'.// Absolute path
'./modal/modal.js'.// Relative path
'./modal/modal.html'.// Text file].function(Util, Modal, tpl) {
/*
* 模块逻辑
*/
return Module;
})Copy the code
1. How is the module loaded and executed
Forget about how a module’s dependencies are handled. Assuming a module’s dependencies are already injected, how do you load, execute, and output the module? In the browser side, we can use the browser’s script tag to realize the introduction and execution of JS module files, for text module files can be directly realized by ajax request. The specific steps are as follows:
- The first step is to get the absolute path to the module file. To load a file in your browser, you first need to get the full network absolute address of the corresponding module file. Since the href attribute of the A tag always returns an absolute path, that is, it has the ability to convert a relative path to an absolute path, we can use this feature to obtain the absolute network path of the module. It should be pointed out that for dependent module files that use relative path, it is also necessary to recursively obtain the network absolute address of the current module, and then concatenate it with the relative path to form a complete absolute address. The code is as follows:
var a = document.createElement('a');
a.id = '_defineAbsoluteUrl_';
a.style.display = 'none';
document.body.appendChild(a);
function getModuleAbsoluteUrl(path) {
a.href = path;
return a.href;
}
function parseAbsoluteUrl(url, parentDir) {
var relativePrefix = '. ',
parentPrefix = '.. ',
result;
if (parentDir && url.indexOf(relativePrefix) === 0) {
// Relative path starting with './'
return getModuleAbsoluteUrl(parentDir.replace(/ [^ \] * $/.' ') + url);
}
if (parentDir && url.indexOf(parentPrefix) === 0) {
/ / to '.. /' relative path
return getModuleAbsoluteUrl(parentDir.replace(* $/ / [\].' ').replace($/ / [\].' ').replace(/ [^ \] * $/.' ') + url);
}
return getModuleAbsoluteUrl(url);
}Copy the code
- Second, load and execute the module file.
For JS files, use the script tag. The code is as follows:
var head = document.getElementsByTagName('head') [0] | |document.body;
function loadJsModule(url) {
var script = document.createElement('script');
script.charset = 'utf-8';
script.type = 'text/javascript';
script.onload = script.onreadystatechange = function() {
if (!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete') {
/* * Apply (window, args); /* * apply(window, args); * /
script.onload = script.onreadystatechange = null; }}; }Copy the code
For text files, use Ajax directly. The code is as follows:
var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP'),
textContent = ' ';
xhr.onreadystatechange = function(){
var DONE = 4, OK = 200;
if(xhr.readyState === DONE){
if(xhr.status === OK){
textContent = xhr.responseText; // Return a text file
} else{
console.log("Error: "+ xhr.status); // Load failed
}
}
}
xhr.open('GET', url, true);// The url is the absolute path to the text file
xhr.send(null);Copy the code
2. Module dependency management
The loading process of a module is shown below.
- State management As you can see from the above, there are several possible states for a module to load.
- Load state, including unloaded state, loading state and loaded state.
- Loading a dependency state.
- The module callback is finished. Therefore, a status flag needs to be added to each loaded module to identify the current status of the module.
-
Dependency analysis After the module is loaded, we need to parse out the absolute path (PATH), dependency module (DEPS) and callback function (callback) of each module, and put them in module information as well. The data model for the module object management logic is shown below.
{ path: 'http://asdas/asda/a.js'.deps: [{}, {}, {}].callback: function(){},status: 'pending' }Copy the code
-
A dependency loop module is likely to have cyclic dependencies. That is, modules A and B depend on each other. Dependencies are classified into strong and weak dependencies. Strong dependencies are those that are used when a module callback is executed; Otherwise, it is weak dependence. For strong dependencies, there are deadlocks that cannot be resolved. Weak dependencies, however, can be replaced by injecting an empty module reference to allow a module to execute first and then replace the dependent module after it has executed. Examples of strong and weak dependencies are as follows:
// Examples of strong dependencies
/ / A module
define(['b.js'].function(B) {
// The dependency module needs to be used directly when the callback is executed
B.demo = 1;
// Other logic
});
/ / B module
define(['a.js'].function(A) {
// The dependency module needs to be used directly when the callback is executed
A.demo = 1;
// Other logic
});Copy the code
// Examples of weak dependencies
/ / A module
define(['b.js'].function(B) {
// The dependent module is not executed directly when the callback is executed
function test() {
B.demo = 1;
}
return {testFunc: test}
});
/ / B module
define(['a.js'].function(A) {
// The dependent module is not executed directly when the callback is executed
function test() {
A.demo = 1;
}
return {testFunc: test}
});Copy the code
3. Exposuredefinemethods
For the define function, you need to iterate through all of the unprocessed JS scripts (both inline and inline), and then perform the loading of the module. For inline and for define in an external script, you do this separately. There are two main reasons:
- Introverted scripts do not require loading operations.
- The callback output of a module defined in an introverted script cannot be relied on by other modules.
var handledScriptList = [];
window.define = function(deps, callback) {
var scripts = document.getElementsByTagName('script'),
defineReg = /s*define\s*\(\[.*\]\s*\,\s*function\s*\(.*\)\s*\{/,
script;
for (var i = scripts.length - 1; i >= 0; i--) {
script = list[i];
if (handledScriptList.indexOf(script.src) < 0) {
handledScriptList.push(script.src);
if (script.innerHTML.search(defineReg) >= 0) {
// Internalize scripts to check module dependencies directly.
} else {
// The first thing to do is listen for the script to load}}}};Copy the code
This is a description of the core issues involved in implementing a modular tool. Complete code point me.