自研模块加载器(四) 模块资源定位-异步加载
资源定位-动态加载
通过resolve方法进行异步解析,完整解析如下图所示:
根据上篇文章startUp.js代码,我们继续完善本章动态加载资源的代码。
(function(global) { var startUp = global.startUp = { version: '1.0.1' } var data = {}; // 获取当前模块加载器配置信息 var cache = {}; // 缓存 //模块的生命周期 var status = { FETCHED: 1, SAVED: 2, LOADING: 3, LOADED: 4, EXECUTING: 5, EXECUTED: 6 } // 静态工具类,判断是否为数组 var isArray = function(obj) { return toString.call(obj) === "[object Array]"; } //检测别名 function parseAlias(id) { var alias = data.alias; // data为配置项 return alias && toString(alias[id]) ? alias[id] : id; } // 不能以"/" ":"开头 结尾必须是一个"/" 后面跟随任意一个字符 var PATHS_RE = /^([^\/:]+)(\/.+)$/; // 检测是否存在短路径 function parsePaths(id) { var paths = data.paths; if(paths && (m = id.match(PATHS_RE)) && isString(paths[m[1]])) { id = paths[m[1]] + m[2] } return id; } function normalize(path) { var last = path.length - 1; var lastC = path.charAt(last); return (lastC === '/' || path.substring(last - 2) === '.js') ? path : path + '.js'; } // 添加根目录 function addBase(id ,uri) { var result; // 相对路径处理 if(id.charAt(0) === '.') { result = realPath((uri ? uri.match(/[^?]*\//)[0] : data.cwd) + id); } else { result = data.cwd + id; } return result; } var DOT_RE = /\/.\//g; // /a/b/.c/./d => /a/b/c/d var DOUBBle_DOT_RE = /\/[^/]+\/\.\.\//; // /a/b/c../../d => a/d // 规范路径 function realPath(path) { console.log(path) path = path.replace(DOT_RE, '/'); while(path.match(DOUBBle_DOT_RE)) { path = path.replace(DOUBBle_DOT_RE, '/') } return path; } // 入口函数 startUp.use = function(list, callback){ // 检测有没有预加载模块 Module.preload(function() { Module.use(list, callback, data.cwd + "_use" +cid()) // 虚拟根目录 }) } // 生成绝对路径 child / parent startUp.resolve = function(child, parent) { if(!child) return ""; child = parseAlias(child); // 检测是否有别名 child = parsePaths(child); // 检测是否有路径别名,依赖模块中引入包的模块路径 require("app/c") child = normalize(child); // 检测是否有后缀 return addBase(child, parent); // 添加根目录 } startUp.request = function(url, callback) { var node = document.createElement('script'); node.src = url; document.body.appendChild(node); node.onload = function() { // 加载完成后清空页面上动态增加的标签 node.onload = null; document.body.removeChild(node); callback(); } } // 构造函数 function Module(uri, deps) { this.uri = uri; this.deps = deps || []; // ['a.js', 'b.js'] 依赖项 this.exports = null; this.status = 0; this._waitings = {}; this._remain = 0; // 依赖性数量 } // 分析主干(左子树,右子树)上的依赖 Module.prototype.load = function (){ var m = this; // 主干上的实例对象 m.status = status.LOADING; // 设置构造函数的状态为 LOADING => 3 正在加载模块项 var uris = m.resolve(); // 获取主干上依赖 var len = m._remain = uris.length; // 加载主干上的依赖 var seed; for(var i = 0; i<len; i++) { seed = Module.get(uris[i]); //创建缓存信息 if(seed.status < status.LOADED) { // LOADED == 4 准备加载执行当前模块 seed._waitings[m.uri] = seed._waitings[m.uri] || 1 } else { seed._remain--; } } // 依赖加载完 m._remain == 0 if(m._remain == 0) { // 获取模块的接口对象 // m.onload() } // 准备执行根目录下的依赖列表中的模块 var requestCache = {}; for(var i = 0; i < len; i++) { seed = Module.get(uris[i]); // 获取cache上的缓存 if(seed.status < status.FETCHED) { seed.fetch(requestCache) } } for(uri in requestCache) { requestCache[uri](); } } // 拉取依赖列表中的模块 a.js ,b.js Module.prototype.fetch = function(requestCache) { var m = this; m.status = status.FETCHED; var uri = m.uri; // a.js绝对路径和b.js的绝对路径 requestCache[uri] = sendRequest; // 动态创建script 标签 function sendRequest() { startUp.request(uri, onRequest); // 动态加载script } function onRequest() { // 事件函数 // if(anonymousMeta){ // 模块的数据更新 // m.save(uri, anonymousMeta) // } m.load(); //递归 模块加载策略 deps } } // 资源定位,获取资源的地址 Module.prototype.resolve = function(){ var mod = this; // var ids = mod.deps; // ['a.js', 'b.js'] var uris = []; for(var i = 0; i<ids.length; i++) { uris[i] = startUp.resolve(ids[i], mod.uri); //依赖项 子树/主干 } console.log(uris) return uris; } /* * 模块的静态属性和方法 */ // 模块定义 Module.define = function (factory) { } // 检测缓存对象是否有当前模块信息 Module.get = function (uri, deps) { return cache[uri] || (cache[uri] = new Module(uri, deps)); } Module.use = function (deps, callback, uri) { var m = Module.get(uri, isArray(deps) ? deps : [deps]); console.log(m) // 模块加载完成执行回调 m.callback = function() { } m.load(); } var _cid = 0; function cid() { return _cid++; } // 初始化预加载资源 data.preload = []; // 获取当前文档的URL data.cwd = document.URL.match(/[^?]*\//)[0]; Module.preload = function(callback) { var length = data.preload.length; if(!length) callback(); } global.define = Module.define; })(this)
新增c.js 用于测试c.js有没有加载成功
console.log("c.js");
修改index.html,引入c.js
<!DOCTYPE html> <html> <head> <title>自研模块加载器</title> </head> <body> <script src="./startUp.js"></script> <script> startUp.use(['./a.js', 'b.js', 'c.js'], function() { console.log('startUp...') }) </script> </body> </html>
最终element如图所示,清空了动态加载的script标签
控制台成功输出了c.js
动态加载js成功实现了。
日拱一卒,不负所期