AMD加载器实现笔记(三)

  上一篇文章中我们为config添加了baseUrl和packages的支持,那么这篇文章中将会看到对shim与paths的支持。

  要添加shim与paths,第一要务当然是了解他们的语义与用法。先来看shim,shim翻译成中文是“垫片”的意思。在AMD中主要用途是把不支持AMD的某些变量包装AMD模块。shim是一个哈希对象,key为包装后的模块Id,value是关于这个包装模块的一些配置,主要配置项如下:

  • deps:定义模块需要的依赖项的moduleId数组
  • exports:模块输出值
  • init:如果它的返回值不是undefined,则返回值作为‘some/thind’模块的返回值,否则以exports作为模块的返回值。

  举个例子:

  

  这个配置的目的是想将window.some.thing这个全局变量包装成id为‘some/thing’的模块。模块的依赖项Id为‘a‘、‘b‘,输出值为some.thing但因为定义了init函数,所以最后模块的输出值变成了some.thing + ‘another‘。

  因为shim是要将全局变量包装成模块,所以直接用shim中的配置项来定义模块即可。上例中的配置最终将转化为:

1 define(‘some/thing‘, [‘a‘, ‘b‘], function(a, b) {
2    var initResult = shimItem.init(a, b);
3    return initResult === undefined ? shimItem.exports : initResult;
4 });

  但前面我的加载器中,define只支持匿名模块,现在我们让它来支持显示定义模块Id:

global.define = function(id, deps, callback) {
        //加上moduleId的支持
        if (typeof id !== "string" && arguments.length === 2) {
            callback = deps;
            deps = id;
            id = "";
        }
        var id = id || getCurrentScript();
        if (modules[id]) {
            console.error(‘multiple define module: ‘ + id);
        }

        require(deps, callback, id);
    };

  完成后我们在require.config函数中加入对shim的支持。

//shim 要放在最后处理
        if (config.shim) {
            for (var p in config.shim) {
                var item = config.shim[p];
                define(p, item.deps, function() {
                    var exports;
                    if (item.init) {
                        exports = item.init.apply(item, arguments);
                    }

                    return exports ? exports : item.exports;
                });
            }
        }

  于此同时,require中也要改一下,现在已经不能一股脑的将dep都转化为绝对路径了。

 1 // dep为非绝对路径形式,而modules的key仍然需要绝对路径
 2         deps = deps.map(function(dep) {
 3             if (modules[dep]) { //jquery
 4                 return dep;
 5             } else if (dep in global.require.parsedConfig.paths) {
 6                 return dep;
 7             }
 8             var rel = "";
 9             if (/^Bodhi/.test(id)) {
10                 rel = global.require.parsedConfig.baseUrl;
11             } else {
12                 var parts = parent.split(‘/‘);
13                 parts.pop();
14                 rel = parts.join(‘/‘);
15             }
16             return getModuleUrl(dep, rel);
17         });

 到此,shim已经完美支持了。

  下面轮到了paths,paths也是一个对象,格式为{模块Id:模块所在路径}。当其他模块引用该模块时,该模块的加载地址使用paths中所配置的地址,这个地址可以是绝对的,也可以是相对路径(相对于baseUrl)。首先要在require.config函数中解析path路径

1 this.parsedConfig.paths = {};
2         if (config.paths) {
3             for (var p in config.paths) {
4                 this.parsedConfig.paths[p] = /^http(s)?/.test(config.paths[p]) ? config.paths[p] : getRoute(burl, config.paths[p]);
5             }
6         }

  然后当依赖模块Id在paths中有配置时,那就需要从paths中拿到加载地址,所以需要修改loadJs中代码

 1 function loadJS(url) {
 2         var script = document.createElement(‘script‘);
 3         script.type = "text/javascript";
 4         //判断模块是否在paths中定义了路径
 5         script.src = (url in global.require.parsedConfig.paths ? global.require.parsedConfig.paths[url] : url) + ‘.js‘;
 6         script.onload = function() {
 7             var module = modules[url];
 8             if (module && isReady(module) && loadings.indexOf(url) > -1) {
 9                 callFactory(module);
10             }
11             checkDeps();
12         };
13         var head = document.getElementsByTagName(‘head‘)[0];
14         head.appendChild(script);
15     };

  同时如果dep在paths中有配置,也不能将dep转化为绝对路径(require函数)

  这里面需要注意的是,模块的Id必须与路径所在文件中定义的模块的id保持一致。以jquery为例:

 1 require.config({
 2     baseUrl: "./",
 3     packages: [{
 4         name: "more",
 5         location: "./more"
 6     }, {
 7         name: "mass",
 8         location: "../"
 9     }, {
10         name: "wab",
11         location: "../../../"
12     }],
13     paths: {
14         ‘jquery‘: "../../Bodhi/src/roots/jquery"
15     }
16   });

  当在paths中配置了jquery模块时,那么路径对应的文件一定要定义jquery模块,如jquery源码中用define定义jquery模块:

1 if ( typeof define === "function" && define.amd ) {
2     define( "jquery", [], function() {
3         return jQuery;
4     });
5 }

  

  本文内容结束,目前为止loader加载器的源码为:

  1 (function(global){
  2     global = global || window;
  3     modules = {};
  4     loadings = [];
  5     loadedJs = [];
  6     //module: id, state, factory, result, deps;
  7     global.require = function(deps, callback, parent){
  8         var id = parent || "Bodhi" + Date.now();
  9         var cn = 0, dn = deps.length;
 10         var args = [];
 11
 12          // dep为非绝对路径形式,而modules的key仍然需要绝对路径
 13         deps = deps.map(function(dep) {
 14             if (modules[dep]) { //jquery
 15                 return dep;
 16             } else if (dep in global.require.parsedConfig.paths) {
 17                 return dep;
 18             }
 19             var rel = "";
 20             if (/^Bodhi/.test(id)) {
 21                 rel = global.require.parsedConfig.baseUrl;
 22             } else {
 23                 var parts = parent.split(‘/‘);
 24                 parts.pop();
 25                 rel = parts.join(‘/‘);
 26             }
 27             return getModuleUrl(dep, rel);
 28         });
 29
 30         var module = {
 31             id: id,
 32             deps: deps,
 33             factory: callback,
 34             state: 1,
 35             result: null
 36         };
 37         modules[id] = module;
 38
 39         deps.forEach(function(dep) {
 40             if (modules[dep] && modules[dep].state === 2) {
 41                 cn++
 42                 args.push(modules[dep].result);
 43             } else if (!(modules[dep] && modules[dep].state === 1) && loadedJs.indexOf(dep) === -1) {
 44                 loadJS(dep);
 45                 loadedJs.push(dep);
 46             }
 47         });
 48         if (cn === dn) {
 49             callFactory(module);
 50         } else {
 51             loadings.push(id);
 52             checkDeps();
 53         }
 54     };
 55
 56     global.require.config = function(config) {
 57         this.parsedConfig = {};
 58         if (config.baseUrl) {
 59             var currentUrl = getCurrentScript();
 60             var parts = currentUrl.split(‘/‘);
 61             parts.pop();
 62             var currentDir = parts.join(‘/‘);
 63             this.parsedConfig.baseUrl = getRoute(currentDir, config.baseUrl);
 64         }
 65         var burl = this.parsedConfig.baseUrl;
 66         // 得到baseUrl后,location相对baseUrl定位
 67         this.parsedConfig.packages = [];
 68         if (config.packages) {
 69             for (var i = 0, len = config.packages.length; i < len; i++) {
 70                 var pck = config.packages[i];
 71                 var cp = {
 72                     name: pck.name,
 73                     location: getRoute(burl, pck.location)
 74                 }
 75                 this.parsedConfig.packages.push(cp);
 76             }
 77         }
 78
 79
 80         this.parsedConfig.paths = {};
 81         if (config.paths) {
 82             for (var p in config.paths) {
 83                 this.parsedConfig.paths[p] = /^http(s)?/.test(config.paths[p]) ? config.paths[p] : getRoute(burl, config.paths[p]);
 84             }
 85         }
 86         //shim 要放在最后处理
 87         if (config.shim) {
 88             for (var p in config.shim) {
 89                 var item = config.shim[p];
 90                 define(p, item.deps, function() {
 91                     var exports;
 92                     if (item.init) {
 93                         exports = item.init.apply(item, arguments);
 94                     }
 95
 96                     return exports ? exports : item.exports;
 97                 });
 98             }
 99         }
100
101         console.log(this.parsedConfig);
102     }
103
104     global.define = function(id, deps, callback) {
105         //加上moduleId的支持
106         if (typeof id !== "string" && arguments.length === 2) {
107             callback = deps;
108             deps = id;
109             id = "";
110         }
111         var id = id || getCurrentScript();
112         if (modules[id]) {
113             console.error(‘multiple define module: ‘ + id);
114         }
115
116         require(deps, callback, id);
117     };
118
119     global.define.amd = {};//AMD规范
120
121     function getRoute(base, target) {
122         var bts = base.replace(/\/$/, "").split(‘/‘);  //base dir
123         var tts = target.split(‘/‘); //target parts
124         while (isDefined(tts[0])) {
125             if (tts[0] === ‘.‘) {
126                 return bts.join(‘/‘) + ‘/‘ + tts.slice(1).join(‘/‘);
127             } else if (tts[0] === ‘..‘) {
128                 bts.pop();
129                 tts.shift();
130             } else {
131                 return bts.join(‘/‘) + ‘/‘ + tts.join(‘/‘);
132             }
133         }
134     };
135
136     function isDefined(v) {
137         return v !== null && v !== undefined;
138     }
139
140     function getModuleUrl(moduleId, relative) {
141         function getPackage(nm) {
142             for (var i = 0, len = require.parsedConfig.packages.length; i < len; i++) {
143                 var pck = require.parsedConfig.packages[i];
144                 if (nm === pck.name) {
145                     return pck;
146                 }
147             }
148             return false;
149         }
150         var mts = moduleId.split(‘/‘);
151         var pck = getPackage(mts[0]);
152         if (pck) {
153             mts.shift();
154             return getRoute(pck.location, mts.join(‘/‘));
155         } else if (mts[0] === ‘.‘ || mts[0] === ‘..‘) {
156             return getRoute(relative, moduleId);
157         } else {
158             return getRoute(require.parsedConfig.baseUrl, moduleId);
159         }
160     }
161
162     function loadJS(url) {
163         var script = document.createElement(‘script‘);
164         script.type = "text/javascript";
165         //判断模块是否在paths中定义了路径
166         script.src = (url in global.require.parsedConfig.paths ? global.require.parsedConfig.paths[url] : url) + ‘.js‘;
167         script.onload = function() {
168             var module = modules[url];
169             if (module && isReady(module) && loadings.indexOf(url) > -1) {
170                 callFactory(module);
171             }
172             checkDeps();
173         };
174         var head = document.getElementsByTagName(‘head‘)[0];
175         head.appendChild(script);
176     };
177
178     function checkDeps() {
179         for (var p in modules) {
180             var module = modules[p];
181             if (isReady(module) && loadings.indexOf(module.id) > -1) {
182                 callFactory(module);
183                 checkDeps(); // 如果成功,在执行一次,防止有些模块就差这次模块没有成功
184             }
185         }
186     };
187
188     function isReady(m) {
189         var deps = m.deps;
190         var allReady = deps.every(function(dep) {
191             return modules[dep] && isReady(modules[dep]) && modules[dep].state === 2;
192         })
193         if (deps.length === 0 || allReady) {
194             return true;
195         }
196     };
197
198     function callFactory(m) {
199         var args = [];
200         for (var i = 0, len = m.deps.length; i < len; i++) {
201             args.push(modules[m.deps[i]].result);
202         }
203         m.result = m.factory.apply(window, args);
204         m.state = 2;
205
206         var idx = loadings.indexOf(m.id);
207         if (idx > -1) {
208             loadings.splice(idx, 1);
209         }
210     };
211
212     function getCurrentScript(base) {
213         // 参考 https://github.com/samyk/jiagra/blob/master/jiagra.js
214         var stack;
215         try {
216             a.b.c(); //强制报错,以便捕获e.stack
217         } catch (e) { //safari的错误对象只有line,sourceId,sourceURL
218             stack = e.stack;
219             if (!stack && window.opera) {
220                 //opera 9没有e.stack,但有e.Backtrace,但不能直接取得,需要对e对象转字符串进行抽取
221                 stack = (String(e).match(/of linked script \S+/g) || []).join(" ");
222             }
223         }
224         if (stack) {
225             /**e.stack最后一行在所有支持的浏览器大致如下:
226              *chrome23:
227              * at http://113.93.50.63/data.js:4:1
228              *firefox17:
229              *@http://113.93.50.63/query.js:4
230              *opera12:http://www.oldapps.com/opera.php?system=Windows_XP
231              *@http://113.93.50.63/data.js:4
232              *IE10:
233              *  at Global code (http://113.93.50.63/data.js:4:1)
234              *  //firefox4+ 可以用document.currentScript
235              */
236             stack = stack.split(/[@ ]/g).pop(); //取得最后一行,最后一个空格或@之后的部分
237             stack = stack[0] === "(" ? stack.slice(1, -1) : stack.replace(/\s/, ""); //去掉换行符
238             return stack.replace(/(:\d+)?:\d+$/i, "").replace(/\.js$/, ""); //去掉行号与或许存在的出错字符起始位置
239         }
240         var nodes = (base ? document : head).getElementsByTagName("script"); //只在head标签中寻找
241         for (var i = nodes.length, node; node = nodes[--i]; ) {
242             if ((base || node.className === moduleClass) && node.readyState === "interactive") {
243                 return node.className = node.src;
244             }
245         }
246     };
247 })(window)

  

  测试demo:

 1 window.something = "Bodhi";
 2   require.config({
 3     baseUrl: "./",
 4     packages: [{
 5         name: "more",
 6         location: "./more"
 7     }, {
 8         name: "mass",
 9         location: "../"
10     }, {
11         name: "wab",
12         location: "../../../"
13     }],
14     shim: {
15         "something": {
16             "deps": [‘jquery‘],
17             exports: ‘something‘,
18             init: function(jq, ol) {
19                 console.log(jq);
20                 return something + " in shim";
21             }
22         }
23     },
24     paths: {
25         ‘jquery‘: "../../Bodhi/src/roots/jquery"
26     }
27   });
28   require([
29   ‘bbb‘,
30   ‘aaa.bbb.ccc‘,
31   ‘ccc‘,
32   ‘ddd‘,
33   ‘fff‘,
34   ‘something‘
35   ], function(aaabbbccc){
36     console.log(‘simple loader‘);
37     console.log(arguments);
38   });
时间: 2024-08-09 22:01:15

AMD加载器实现笔记(三)的相关文章

AMD加载器实现笔记(二)

AMD加载器实现笔记(一)中,我们实现了一个简易的模块加载器.但到目前为止这个加载器还并不能称为AMD加载器,原因很简单,我们还不支持AMD规范中的config配置.这篇文章中我们来添加对config的中baseUrl和packages的支持.API设计如下: 1 require.config({ 2 baseUrl: "./", 3 packages: [{ 4 name: "more", 5 location: "./more" 6 }, {

AMD加载器实现笔记(一)

之前研究过AMD,也写过一篇关于AMD的文章<以代码爱好者角度来看AMD与CMD>.代码我是有看过的,基本的原理也都明白,但实际动手去实现却是没有的.因为今年计划的dojo教程<静静的dojo>中,有一章节来专门讲解AMD,不免要把对AMD的研究回炉一下.时隔多日,再回头探索AMD实现原理时,竟抓耳挠腮,苦苦思索不得要领.作为开发人员,深感惭愧.故有此文,记录我在实现一个AMD加载器时的思考总结. requireJS是所有AMD加载器中,最广为人知的一个.目前的版本更凝聚了几位大牛

AMD加载器实现笔记(五)

前几篇文章对AMD规范中的config属性几乎全部支持了,这一节主要是进一步完善.到目前为止我们的加载器还无法处理环形依赖的问题,这一节就是解决环形依赖. 所谓环形依赖,指的是模块A的所有依赖项的依赖中有没有依赖A模块本身的模块.如果有那就说明存在环形依赖.所以检验的方式是利用递归,检查一个模块的依赖的依赖项中有没有依赖A模块,以及依赖项的依赖项的依赖项中有没有A模块,核心代码如下: function checkCircleRef(start, target){ var m = modules[

AMD加载器实现笔记(四)

继续这一系列的内容,到目前为止除了AMD规范中config的map.config参数外,我们已经全部支持其他属性了.这一篇文章中,我们来为增加对map的支持.同样问题,想要增加map的支持首先要知道map的语义. 主要用于解决在两个不同模块集中使用一个模块的不同版本,并且保证两个模块集的交互没有冲突. 假设磁盘有如下文件: 当'some/newmodule'请求'foo'模块时,它将从foo1.2.js总得到'foo1.2'模块:当'some/oldmodule'请求'foo'模块时它将从foo

JavaScript AMD 模块加载器原理与实现

关于前端模块化,玉伯在其博文 前端模块化开发的价值 中有论述,有兴趣的同学可以去阅读一下. 1. 模块加载器 模块加载器目前比较流行的有 Requirejs 和 Seajs.前者遵循 AMD规范,后者遵循 CMD规范.前者的规范产出比较适合于浏览器异步环境的习惯,后者的规范产出对于写过 nodejs 的同学来说是比较爽的.关于两者的比较,有兴趣的同学请参看玉伯在知乎的回答 AMD和CMD的区别有哪些.本文希望能按照 AMD 规范来简单实现自己的一个模块加载器,以此来搞清楚模块加载器的工作原理.

Android知识体系梳理笔记三:动态代理模式---插件加载机制学习笔记

静态代理模式 静态代理模式就是我们常说的代理设计模式,我们采用一个代理类调用原有的方法,且对产生的结果进行控制:举个例子:我们现在在玩一款网络游戏,需要打怪升级:太累就找个代理吧,一觉醒来就会发现我们已经当上CEO,迎娶白富美,天下第一了! 本来我们只能打怪,打怪-,但经过代理类增强,我们不仅可以打怪,还可以升级拿装备.就这样子了! 上代码: * 同一功能接口 public interface PlayNetGame { String beatMonster(); } 1 2 3 4 1 2 3

程序的加载和执行(三)——《x86汇编语言:从实模式到保护模式》读书笔记23

程序的加载和执行(三)--读书笔记23 接着上次的内容说. 关于过程load_relocate_program的讲解还没有完,还差创建栈段描述符和重定位符号表. 分配栈空间与创建栈段描述符 462 ;建立程序堆栈段描述符 463 mov ecx,[edi+0x0c] ;4KB的倍率 464 mov ebx,0x000fffff 465 sub ebx,ecx ;得到段界限 466 mov eax,4096 467 mul dword [edi+0x0c] 468 mov ecx,eax ;准备为

java中三个类别加载器的关系以及各自加载的类的范围

Java在需要使用类别的时候,才会将类别加载,Java的类别载入是由类别载入器(Class loader)来达到的,预设上,在程序启动之后,主要会有三个类别加载器:Bootstrap Loader.ExtClassLoader与AppClassLoader. Bootstrap Loader是由C++撰写而成,预设上它负责搜寻JRE所在目录的classes或lib目录下的.jar档案中(例如rt.jar)是否有指定的类别并加载(实际上是由系统参数sun.boot.class.path指定):预设

自研模块加载器(三) module模块构造器设计-模块数据初始化

依赖加载策略 模块数据初始化 status状态生命周期 代码展示 demo包括4个文件, index.html , a.js , b.js , startUp.js index.html <!DOCTYPE html> <html> <head> <title>自研模块加载器</title> </head> <body> <script src="./startUp.js"></scr