自研模块加载器(四) 模块资源定位-异步加载

资源定位-动态加载

通过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成功实现了。

 

posted @ 2020-01-29 19:58  进击的小牛牛  阅读(242)  评论(0编辑  收藏  举报