├── seedcombo ├── hello6.js ├── hello5.js ├── testmod │ ├── hello1.js │ └── hello2.js ├── hello3.js ├── hello4.js ├── usehello.js ├── hello.js ├── build.js ├── index.html ├── hello-combo.js └── seedcombo.js ├── README.md ├── LICENSE └── dist ├── seed.min.js └── seed.js /seedcombo/hello6.js: -------------------------------------------------------------------------------- 1 | define({ foo : 'bar' }); -------------------------------------------------------------------------------- /seedcombo/hello5.js: -------------------------------------------------------------------------------- 1 | define(function(){ 2 | console.log( 'hello5' ); 3 | }); -------------------------------------------------------------------------------- /seedcombo/testmod/hello1.js: -------------------------------------------------------------------------------- 1 | define(function(){ 2 | console.log( 'hello1' ); 3 | }); -------------------------------------------------------------------------------- /seedcombo/hello3.js: -------------------------------------------------------------------------------- 1 | define(['hello4'], function(){ 2 | console.log( 'hello3' ); 3 | }); -------------------------------------------------------------------------------- /seedcombo/hello4.js: -------------------------------------------------------------------------------- 1 | define(['hello5', 'hello6'],function(){ 2 | console.log( 'hello4' ); 3 | }); -------------------------------------------------------------------------------- /seedcombo/usehello.js: -------------------------------------------------------------------------------- 1 | seed.use( 'hello', function(){ 2 | console.log( 'use hello done' ); 3 | }); -------------------------------------------------------------------------------- /seedcombo/testmod/hello2.js: -------------------------------------------------------------------------------- 1 | define(['../hello5'],function(){ 2 | console.log( 'hello2' ); 3 | }); -------------------------------------------------------------------------------- /seedcombo/hello.js: -------------------------------------------------------------------------------- 1 | define( 'hello', ['testmod/hello1', 'testmod/hello2', 'hello3'], function(){ 2 | console.log( 'hello' ); 3 | }); -------------------------------------------------------------------------------- /seedcombo/build.js: -------------------------------------------------------------------------------- 1 | require( './seedcombo' ).seedCombo({ 2 | baseUrl : './', 3 | modules : [{ 4 | input : ['usehello'], 5 | output : 'hello-combo.js' 6 | }] 7 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AMD module loader 2 | seed 是一个简单纯粹的 JavaScript 模块加载器,基于 AMD 的模块化规范。 3 | *** 4 | ### [快速入手 seed](https://github.com/chenmnkken/seed/wiki/%E5%BF%AB%E9%80%9F%E5%85%A5%E6%89%8B-seed) 5 | *** 6 | ### 开源协议 7 | seed 遵循 MIT 协议。 8 | *** 9 | 10 | -------------------------------------------------------------------------------- /seedcombo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | test 6 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /seedcombo/hello-combo.js: -------------------------------------------------------------------------------- 1 | define('hello5',function(){ 2 | console.log( 'hello5' ); 3 | }); 4 | define('hello6',{ foo : 'bar' }); 5 | define('hello4',['hello5','hello6'],function(){ 6 | console.log( 'hello4' ); 7 | }); 8 | define('hello1',function(){ 9 | console.log( 'hello1' ); 10 | }); 11 | define('hello2',['hello5'],function(){ 12 | console.log( 'hello2' ); 13 | }); 14 | define('hello3',['hello4'], function(){ 15 | console.log( 'hello3' ); 16 | }); 17 | define( 'hello', ['hello1','hello2','hello3'], function(){ 18 | console.log( 'hello' ); 19 | }); 20 | seed.use( 'hello', function(){ 21 | console.log( 'use hello done' ); 22 | }); 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Yiguo Chan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /dist/seed.min.js: -------------------------------------------------------------------------------- 1 | /* seed.js v1.1.4 | MIT Licenses | 2014-09-21 */ 2 | (function(u,D){u.seed=function(){var G=/^(file\:.+\:\/|[\w\-]+\:\/\/)/,F=/([^\/?]+?)(\.(?:js|css))?(\?.*)?$/,I=/loaded|complete|undefined/,x={},v={},w=document.head||document.getElementsByTagName("head")[0]||document.documentElement,H="seed_mod_"+ +new Date+(Math.random()+"").slice(-8),E=!!document.addEventListener,y={baseUrl:null,charset:{},alias:{}},k={error:function(b){throw Error(b);},merge:function(b,a){if(a)for(var c in a)y[b][c]=a[c]},init:function(){var b,a,c;document.currentScript?b=document.currentScript: 3 | (a=document.getElementsByTagName("script"),b=a[a.length-1]);a=b.getAttribute("data-main");c=b.getAttribute("data-baseurl");b=(b=b.hasAttribute?b.src:b.getAttribute("src",4))||u.location.href;y.baseUrl=c?k.mergePath(c,u.location.href):b.slice(0,b.lastIndexOf("/")+1);a&&(a=a.split(","),r.use(a))},getCurrentScript:function(){var b,a,c;try{k.makeerror()}catch(e){a=e.stack}if(a)return a=a.split(/[@ ]/g).pop(),a="("===a[0]?a.slice(1,-1):a.replace(/\s/,""),a.replace(/(:\d+)?:\d+$/i,"").match(F)[1];a=w.getElementsByTagName("script"); 4 | for(c=a.length-1;0<=c&&(b=a[c],b.className!==H||"interactive"!==b.readyState);c--);return b.src.match(F)[1]},mergePath:function(b,a){var c="/"===b.charAt(0),e="http"===a.slice(0,4),f="",d=0,g,h,k;g=a.match(G)[1];a=a.slice(g.length);e&&(f=a.slice(0,a.indexOf("/")+1),a=c?"":a.slice(f.length));e=a.split("/");e.pop();h=b.split("/");h.pop();c&&h.shift();for(c=h.length;d define('hello', ['hello1']) 58 | // use('hello', function(){ => use('hello') 59 | literal = literal.replace(rRightEnd, ')'); 60 | 61 | // 然后用eval去执行处理过的define和use获取到依赖模块的标识 62 | arr = eval( literal ); 63 | 64 | if( arr && arr.length ){ 65 | // 为依赖模块解析真实的模块路径 66 | arr.forEach(function( item, i ){ 67 | arr[i] = path.resolve( baseUrl, item ); 68 | }); 69 | 70 | deps = deps.concat( arr ); 71 | } 72 | }); 73 | }); 74 | 75 | if( deps.length ){ 76 | cache.ids = deps.concat( cache.ids ); 77 | 78 | // 递归调用直到所有的依赖模块都添加完 79 | parseDeps( key, deps, encoding ); 80 | } 81 | }, 82 | 83 | formatDeps = function( _, define, deps ){ 84 | var arr = deps.split( ',' ), 85 | len = arr.length, 86 | i = 0, 87 | item, index; 88 | 89 | for( ; i < len; i++ ){ 90 | item = arr[i]; 91 | item = item.replace( /['"]/g, '' ).trim(); 92 | index = item.lastIndexOf( '/' ); 93 | arr[i] = ~index ? item.slice( index + 1 ) : item; 94 | } 95 | 96 | return define + "'" + arr.join("','") + "'"; 97 | }, 98 | 99 | // 合并内容 100 | comboContent = function( key, baseUrl, encoding, format ){ 101 | var cache = depsCache[ key ], 102 | unique = cache.unique, 103 | ids = cache.ids; 104 | 105 | ids.forEach(function( id ){ 106 | var modName = id.match( rModId )[1], 107 | modUrl = path.resolve( __dirname, id ), 108 | content; 109 | 110 | if( !~modUrl.indexOf('.js') ){ 111 | modUrl += '.js' 112 | } 113 | 114 | content = fs.readFileSync( modUrl, encoding ); 115 | 116 | // 非use()的情况下防止重复合并 117 | if( !~content.indexOf('use(') ){ 118 | if( unique[modUrl] ){ 119 | return; 120 | } 121 | 122 | unique[ modUrl ] = true; 123 | } 124 | 125 | // utf-8 编码格式的文件可能会有 BOM 头,需要去掉 126 | if( encoding === 'UTF-8' && content.charCodeAt(0) === 0xFEFF ){ 127 | content = content.slice( 1 ); 128 | } 129 | 130 | // 格式化 131 | if( typeof format === 'function' ){ 132 | content = format( content ); 133 | } 134 | 135 | // 为匿名模块添加模块名 136 | if( !rExistId.test(content) ){ 137 | content = content.replace( rDefine, "define('" + modName + "'," ); 138 | } 139 | 140 | // 格式化依赖模块列表 ['../hello5'] => ['hello5'] 141 | content = content.replace( rDeps, formatDeps ); 142 | 143 | // 合并 144 | cache.contents += content + '\n'; 145 | console.log( 'Combo the [' + modName + '] success.' ); 146 | }); 147 | }, 148 | 149 | // 写入文件 150 | writeFile = function( key, mod, uglifyUrl ){ 151 | var output = mod.output, 152 | contents = depsCache[ key ].contents, 153 | uglify, jsp, pro, ast; 154 | 155 | // 压缩文件 156 | if( uglifyUrl ){ 157 | uglify = require( uglifyUrl ); 158 | jsp = uglify.parser; 159 | pro = uglify.uglify; 160 | 161 | ast = jsp.parse( contents ); 162 | ast = pro.ast_mangle( ast ); 163 | ast = pro.ast_squeeze( ast ); 164 | contents = pro.gen_code( ast ); 165 | } 166 | 167 | // 合并好文本内容后的回调 168 | if( typeof complete === 'function' ){ 169 | contents = complete( contents ); 170 | } 171 | 172 | // 写入文件 173 | try{ 174 | fs.writeFileSync( output, contents, mod.encoding ); 175 | } 176 | catch( error ){ 177 | console.log( 'Output file ' + error ); 178 | return; 179 | } 180 | 181 | console.log( '\n' ); 182 | console.log( '============================================================' ); 183 | console.log( 'Output the [' + output + '] success.' ); 184 | console.log( '============================================================' ); 185 | console.log( '\n' ); 186 | delete depsCache[ key ]; 187 | }, 188 | 189 | seedCombo = function( options ){ 190 | var modules = options.modules, 191 | baseUrl = path.resolve(); 192 | 193 | modules.forEach(function( mod ){ 194 | var encoding = mod.encoding = ( mod.encoding || 'UTF-8' ).toUpperCase(), 195 | randomKey = ( +new Date() + '' ) + ( Math.random() + '' ).slice( -8 ); 196 | 197 | depsCache[ randomKey ] = { 198 | ids : [], 199 | unique : {}, 200 | contents : '' 201 | }; 202 | 203 | mod.input.forEach(function( id ){ 204 | var modUrl = path.resolve( baseUrl, id ); 205 | 206 | depsCache[ randomKey ].ids.push( modUrl ); 207 | parseDeps( randomKey, [modUrl], encoding ); 208 | }); 209 | 210 | comboContent( randomKey, baseUrl, encoding, mod.format ); 211 | writeFile( randomKey, mod, options.uglifyUrl ); 212 | }); 213 | }; 214 | 215 | exports.seedCombo = seedCombo; -------------------------------------------------------------------------------- /dist/seed.js: -------------------------------------------------------------------------------- 1 | /* 2 | * seed v1.1.4 3 | * AMD module loader 4 | * 5 | * Copyright (c) 2013-2014 Yiguo Chan 6 | * Released under the MIT Licenses 7 | * 8 | * Gighub : https://github.com/chenmnkken/seed.git 9 | * Mail : chenmnkken@gmail.com 10 | * Date : 2014-09-21 11 | */ 12 | (function( window, undefined ){ 13 | 14 | 'use strict'; 15 | 16 | var seed = function(){ 17 | var rProtocol = /^(file\:.+\:\/|[\w\-]+\:\/\/)/, 18 | rModId = /([^\/?]+?)(\.(?:js|css))?(\?.*)?$/, 19 | rReadyState = /loaded|complete|undefined/, 20 | 21 | moduleCache = {}, // 模块加载时的队列数据存储对象 22 | modifyCache = {}, // modify的临时数据存储对象 23 | 24 | head = document.head || 25 | document.getElementsByTagName( 'head' )[0] || 26 | document.documentElement, 27 | 28 | modClassName = 'seed_mod_' + ( +new Date() ) + ( Math.random() + '' ).slice( -8 ), 29 | isScriptOnload = !!document.addEventListener, 30 | 31 | // 模块加载器的配置对象 32 | moduleOptions = { 33 | baseUrl : null, 34 | charset : {}, // 模块对应的charset存储对象 35 | alias : {} 36 | }; 37 | 38 | var easyModule = { 39 | 40 | error : function( msg ){ 41 | throw new Error( msg ); 42 | }, 43 | 44 | // 用于合并模块加载器配置的工具函数 45 | merge : function( key, options ){ 46 | if( options ){ 47 | var name; 48 | 49 | for( name in options ){ 50 | moduleOptions[ key ][ name ] = options[ name ]; 51 | } 52 | } 53 | }, 54 | 55 | // 初始化模块加载器时获取baseUrl(既是当前js文件加载的url) 56 | init : function(){ 57 | var i = 0, 58 | script, scripts, initMod, initBaseUrl, url; 59 | 60 | // firefox支持currentScript属性 61 | if( document.currentScript ){ 62 | script = document.currentScript; 63 | } 64 | else{ 65 | // 正常情况下,在页面加载时,当前js文件的script标签始终是最后一个 66 | scripts = document.getElementsByTagName( 'script' ); 67 | script = scripts[ scripts.length - 1 ]; 68 | } 69 | 70 | initMod = script.getAttribute( 'data-main' ); 71 | initBaseUrl = script.getAttribute( 'data-baseurl' ); 72 | url = script.hasAttribute ? script.src : script.getAttribute( 'src', 4 ); 73 | 74 | // 如果seed是通过script标签inline添加到页面中其baseUrl就是当前页面的路径 75 | url = url || window.location.href; 76 | 77 | moduleOptions.baseUrl = initBaseUrl ? 78 | easyModule.mergePath( initBaseUrl, window.location.href ) : 79 | url.slice( 0, url.lastIndexOf('/') + 1 ); 80 | 81 | // 初始化时加载data-main中的模块 82 | if( initMod ){ 83 | initMod = initMod.split( ',' ); 84 | seedExports.use( initMod ); 85 | } 86 | 87 | scripts = script = null; 88 | }, 89 | 90 | // 获取当前运行脚本的文件的名称 91 | // 用于获取匿名模块的模块名 92 | getCurrentScript : function(){ 93 | var script, scripts, i, stack; 94 | 95 | // 标准浏览器(IE10、Chrome、Opera、Safari、Firefox)通过强制捕获错误(e.stack)来确定为当前运行的脚本 96 | // http://www.cnblogs.com/rubylouvre/archive/2013/01/23/2872618.html 97 | try{ 98 | // 运行一个不存在的方法强制制造错误 99 | easyModule.makeerror(); 100 | } 101 | // 捕获错误 102 | // safari的错误对象只有line,sourceId,sourceURL 103 | catch( e ){ 104 | stack = e.stack; 105 | } 106 | 107 | if( stack ){ 108 | // 取得最后一行,最后一个空格或@之后的部分 109 | stack = stack.split( /[@ ]/g ).pop(); 110 | // 去掉换行符 111 | stack = stack[0] === '(' ? stack.slice( 1, -1 ) : stack.replace( /\s/, '' ); 112 | //去掉行号与或许存在的出错字符起始位置 113 | return stack.replace( /(:\d+)?:\d+$/i, '' ).match( rModId )[1]; 114 | } 115 | 116 | // IE6-8通过遍历script标签,判断其readyState为interactive来确定为当前运行的脚本 117 | scripts = head.getElementsByTagName( 'script' ); 118 | i = scripts.length - 1; 119 | 120 | for( ; i >= 0; i-- ){ 121 | script = scripts[i]; 122 | if( script.className === modClassName && script.readyState === 'interactive' ){ 123 | break; 124 | } 125 | } 126 | 127 | return script.src.match( rModId )[1]; 128 | }, 129 | 130 | // 将模块标识(相对路径)和基础路径合并成新的真正的模块路径(不含模块的文件名) 131 | mergePath : function( id, url ){ 132 | var isRootDir = id.charAt(0) === '/', 133 | isHttp = url.slice( 0, 4 ) === 'http', 134 | domain = '', 135 | i = 0, 136 | protocol, isHttp, urlDir, idDir, dirPath, len, dir; 137 | 138 | protocol = url.match( rProtocol )[1]; 139 | url = url.slice( protocol.length ); 140 | 141 | // HTTP协议的路径含有域名 142 | if( isHttp ){ 143 | domain = url.slice( 0, url.indexOf('/') + 1 ); 144 | url = isRootDir ? '' : url.slice( domain.length ); 145 | } 146 | 147 | // 组装基础路径的目录数组 148 | urlDir = url.split( '/' ); 149 | urlDir.pop(); 150 | 151 | // 组装模块标识的目录数组 152 | idDir = id.split( '/' ); 153 | idDir.pop(); 154 | 155 | if( isRootDir ){ 156 | idDir.shift(); 157 | } 158 | 159 | len = idDir.length; 160 | 161 | for( ; i < len; i++ ){ 162 | dir = idDir[i]; 163 | // 模块标识的目录数组中含有../则基础路径的目录数组删除最后一个目录 164 | // 否则直接将模块标识的目录数组的元素添加到基础路径的目录数组中 165 | if( dir === '..' ){ 166 | urlDir.pop(); 167 | } 168 | else if( dir !== '.' ){ 169 | urlDir.push( dir ); 170 | } 171 | } 172 | 173 | // 基础路径的目录数组转换成目录字符串 174 | dirPath = urlDir.join( '/' ); 175 | // 无目录的情况不用加斜杠 176 | dirPath = dirPath === '' ? '' : dirPath + '/'; 177 | return protocol + domain + dirPath; 178 | }, 179 | 180 | /* 181 | * 解析模块标识,返回模块名和模块路径 182 | * @parmm { String } 模块标识 183 | * @param { String } 基础路径baseUrl 184 | * @return { Array } [ 模块名, 模块路径 ] 185 | * ===================================================================== 186 | * 解析规则: 187 | * baseUrl = http://easyjs.org/js/ 188 | * http://example.com/test.js => [ test, http://example.com/test.js ] 189 | * style.css => [ test, http://easyjs.org/js/style.css ] 190 | * ajax/xhr => [ xhr, http://easyjs.org/js/ajax/xhr.js ] 191 | * ../core => [ core, http://easyjs.org/core.js ] 192 | * test.js => [ test, http://easyjs.org/js/test.js ] 193 | * test => [ test, http://easyjs.org/js/test.js ] 194 | * test.js?v20121202 => [ test, http://easyjs.org/js/test.js?v20121202 ] 195 | * ===================================================================== 196 | */ 197 | parseModId : function( id, url ){ 198 | var isAbsoluteId = rProtocol.test( id ), 199 | result = id.match( rModId ), 200 | modName = result[1], 201 | suffix = result[2] || '.js', 202 | search = result[3] || '', 203 | baseUrl, modUrl; 204 | 205 | // 模块标识为绝对路径时,标识就是基础路径 206 | if( isAbsoluteId ){ 207 | url = id; 208 | id = ''; 209 | } 210 | 211 | baseUrl = easyModule.mergePath( id, url ); 212 | modUrl = baseUrl + modName + suffix + search; 213 | return [ modName, modUrl ]; 214 | }, 215 | 216 | /* 217 | * 将依赖模块列表的外部接口(exports)合并成arguments 218 | * @param { Array } 依赖模块列表 219 | * @return { Array } 返回参数数组 220 | */ 221 | getExports : function( deps ){ 222 | if( deps ){ 223 | var len = deps.length, 224 | module = seedExports.module, 225 | arr = [], 226 | i = 0, 227 | j = 0, 228 | dep; 229 | 230 | for( ; i < len; i++ ){ 231 | arr[ j++ ] = module[ deps[i] ].exports; 232 | } 233 | 234 | return arr; 235 | } 236 | 237 | return []; 238 | }, 239 | 240 | /* 241 | * 测试该模块的依赖模块是否都已加载并执行完 242 | * @param { Object } 模块对象 243 | * @return { Boolean } 依赖模块是否都加载并执行完 244 | */ 245 | isLoaded : function( mod ){ 246 | var deps = mod.deps, 247 | len = deps.length, 248 | module = seedExports.module, 249 | i = 0, depMod; 250 | 251 | for( ; i < len; i++ ){ 252 | depMod = module[ deps[i] ]; 253 | if( depMod.status !== 4 ){ 254 | return false; 255 | } 256 | } 257 | 258 | return true; 259 | }, 260 | 261 | factoryHandle : function( name, mod, factory, data ){ 262 | // 模块解析完毕,所有的依赖模块也都加载完,但还未输出exports 263 | mod.status = 3; 264 | 265 | var args = easyModule.getExports( mod.deps ), 266 | exports = typeof factory === 'function' ? factory.apply( null, args ) : factory; 267 | 268 | if( exports !== undefined ){ 269 | // 如果有绑定modify方法,将在正式返回exports前进行修改 270 | if( modifyCache[name] ){ 271 | exports = modifyCache[ name ]( exports ); 272 | // 修改后即删除modify方法 273 | delete modifyCache[ name ]; 274 | } 275 | // 存储exports到当前模块的缓存中 276 | mod.exports = exports; 277 | } 278 | 279 | // 当前模块加载并执行完毕,exports已可用 280 | mod.status = 4; 281 | 282 | if( data ){ 283 | data.length--; 284 | } 285 | }, 286 | 287 | /* 288 | * 触发被依赖模块的factory 289 | * @param { Object } 模块的缓存对象 290 | */ 291 | fireFactory : function( useKey ){ 292 | var data = moduleCache[ useKey ], 293 | factorys = data.factorys, 294 | result = factorys[0], 295 | args, exports, name, toDepMod; 296 | 297 | if( !result ){ 298 | return; 299 | } 300 | 301 | name = result.name; 302 | toDepMod = seedExports.module[ name ]; 303 | 304 | if( easyModule.isLoaded(toDepMod) ){ 305 | factorys.shift(); 306 | easyModule.factoryHandle( name, toDepMod, result.factory, data ); 307 | 308 | if( factorys.length ){ 309 | easyModule.fireFactory( useKey ); 310 | } 311 | } 312 | }, 313 | 314 | /* 315 | * 模块加载完触发的回调函数 316 | * @param{ Object } 模块对象 317 | */ 318 | complete : function( mod ){ 319 | var module = seedExports.module, 320 | useKey = mod.useKey, 321 | keyLen = useKey.length, 322 | k = 0, 323 | namesCache, args, cacheLen, cacheMod, name, data, key, i, j; 324 | 325 | for( ; k < useKey.length; k++ ){ 326 | key = useKey[k]; 327 | data = moduleCache[ key ]; 328 | useKey.splice( k--, 1 ); 329 | 330 | if( !data ){ 331 | continue; 332 | } 333 | 334 | // 队列没加载完将继续加载 335 | if( data.urls.length ){ 336 | easyModule.load( key ); 337 | } 338 | else if( !data.length ){ 339 | namesCache = data.namesCache; 340 | cacheLen = namesCache.length; 341 | args = []; 342 | i = 0; 343 | j = 0; 344 | 345 | // 合并模块的exports为arguments 346 | for( ; i < cacheLen; i++ ){ 347 | name = namesCache[i]; 348 | cacheMod = module[ name ]; 349 | 350 | if( cacheMod.status !== 4 ){ 351 | return; 352 | } 353 | args[ j++ ] = cacheMod.exports; 354 | } 355 | 356 | // 执行use的回调 357 | if( data.callback ){ 358 | data.callback.apply( null, args ); 359 | } 360 | 361 | // 删除队列数据 362 | delete moduleCache[ key ]; 363 | } 364 | } 365 | }, 366 | 367 | /* 368 | * 创建script/link元素来动态加载JS/CSS资源 369 | * @param{ String } 模块的URL 370 | * @param{ String } 模块名 371 | * @param{ String } 用来访问存储在moduleCache中的数据的属性名 372 | * @return { HTMLElement } 用于添加到head中来进行模块加载的元素 373 | */ 374 | create : function( url, name, useKey ){ 375 | var charset = moduleOptions.charset[ name ], 376 | mod = seedExports.module[ name ], 377 | script, link; 378 | 379 | mod.status = 1; 380 | 381 | // CSS模块的处理 382 | if( ~url.indexOf('.css') ){ 383 | link = document.createElement( 'link' ); 384 | link.rel = 'stylesheet'; 385 | link.href = url; 386 | 387 | if( charset ){ 388 | link.charset = charset; 389 | } 390 | 391 | link.onload = link.onerror = function(){ 392 | link = link.onload = link.onerror = null; 393 | mod.status = 4; 394 | moduleCache[ useKey ].length--; 395 | easyModule.fireFactory( useKey ); 396 | easyModule.complete( mod ); 397 | }; 398 | 399 | return link; 400 | } 401 | 402 | // JS模块的处理 403 | script = document.createElement( 'script' ); 404 | script.className = modClassName; 405 | script.async = true; 406 | 407 | if( charset ){ 408 | script.charset = charset; 409 | } 410 | 411 | if( isScriptOnload ){ 412 | script.onerror = function(){ 413 | script.onerror = script.onload = null; 414 | head.removeChild( script ); 415 | script = null; 416 | 417 | easyModule.error( '[' + name + '] module failed to load, the url is ' + url + '.' ); 418 | }; 419 | } 420 | 421 | script[ isScriptOnload ? 'onload' : 'onreadystatechange' ] = function(){ 422 | if( isScriptOnload || rReadyState.test(script.readyState) ){ 423 | script[ isScriptOnload ? 'onload' : 'onreadystatechange' ] = null; 424 | head.removeChild( script ); 425 | script = null; 426 | // 加载成功 427 | easyModule.complete( mod ); 428 | } 429 | }; 430 | 431 | script.src = url; 432 | return script; 433 | }, 434 | 435 | /* 436 | * 加载模块 437 | * @param { String } 用来访问存储在moduleCache中的数据的属性名 438 | */ 439 | load : function( useKey ){ 440 | var data = moduleCache[ useKey ], 441 | module = seedExports.module, 442 | names = data.names.shift(), 443 | urls = data.urls.shift(), 444 | len = urls.length, 445 | i = 0, 446 | mod, script; 447 | 448 | for( ; i < len; i++ ){ 449 | mod = module[ names[i] ]; 450 | 451 | if( mod.useKey === undefined ){ 452 | mod.useKey = []; 453 | } 454 | 455 | mod.useKey.push( useKey ); 456 | 457 | if( module[names[i]].status === undefined ){ 458 | script = easyModule.create( urls[i], names[i], useKey ); 459 | head.insertBefore( script, head.firstChild ); 460 | } 461 | else{ 462 | data.length--; 463 | } 464 | } 465 | } 466 | }; 467 | 468 | var seedExports = { 469 | 470 | version : '1.1.2', 471 | 472 | module : {}, 473 | 474 | use : function( ids, fn ){ 475 | ids = typeof ids === 'string' ? [ ids ] : ids; 476 | var alias = moduleOptions.alias, 477 | module = seedExports.module, 478 | len = ids.length, 479 | isLoaded = false, 480 | namesCache = [], 481 | modNames = [], 482 | modUrls = [], 483 | j = 0, 484 | mod, modName, result, useKey, args, name, i, id; 485 | 486 | for( i = 0; i < len; i++ ){ 487 | id = ids[i]; 488 | 489 | // 获取解析后的模块名和url 490 | result = easyModule.parseModId( alias[id] || id, moduleOptions.baseUrl ); 491 | modName = alias[ id ] ? id : result[0]; 492 | mod = module[ modName ]; 493 | 494 | if( !mod ){ 495 | mod = module[ modName ] = {}; 496 | isLoaded = false; 497 | } 498 | else if( mod.status === 4 ){ 499 | isLoaded = true; 500 | } 501 | 502 | // 将模块名和模块路径添加到队列中 503 | modNames[ modNames.length++ ] = modName; 504 | modUrls[ modUrls.length++ ] = mod.url = result[1]; 505 | } 506 | 507 | // 生成队列的随机属性名 508 | useKey = modNames.join( '_' ) + '_' + ( +new Date() ) + ( Math.random() + '' ).slice( -8 ); 509 | // 复制模块名,在输出exports时会用到 510 | namesCache = namesCache.concat( modNames ); 511 | 512 | // 在模块都合并的情况下直接执行callback 513 | if( isLoaded ){ 514 | len = namesCache.length; 515 | args = []; 516 | 517 | // 合并模块的exports为arguments 518 | for( i = 0; i < len; i++ ){ 519 | name = namesCache[i]; 520 | mod = module[ name ]; 521 | 522 | if( mod.status !== 4 ){ 523 | easyModule.error( '[' + name + '] module failed to use.' ); 524 | } 525 | 526 | args[ j++ ] = mod.exports; 527 | } 528 | 529 | // 执行use的回调 530 | if( fn ){ 531 | fn.apply( null, args ); 532 | } 533 | 534 | return; 535 | } 536 | 537 | // 添加队列 538 | moduleCache[ useKey ] = { 539 | length : namesCache.length, 540 | namesCache : namesCache, 541 | names : [ modNames ], 542 | urls : [ modUrls ], 543 | callback : fn, 544 | factorys : [], 545 | deps : {} 546 | }; 547 | 548 | // 开始加载 549 | easyModule.load( useKey ); 550 | }, 551 | 552 | /* 553 | * 给模块添加modify方法以便在正式返回exports前进行修改 554 | * @param { String } 模块名 555 | * @param { Function } 修改exports的函数,该函数至少要有一个返回值 556 | */ 557 | modify : function( name, fn ){ 558 | modifyCache[ name ] = fn; 559 | }, 560 | 561 | /* 562 | * 修改模块加载器的配置 563 | * @param { Object } 564 | */ 565 | config : function( options ){ 566 | var baseUrl, isHttp; 567 | 568 | if( options.baseUrl ){ 569 | baseUrl = options.baseUrl; 570 | isHttp = baseUrl.slice( 0, 4 ) === 'http'; 571 | 572 | if( isHttp ){ 573 | moduleOptions.baseUrl = baseUrl; 574 | } 575 | // 相对路径的baseUlr是基于HTML页面所在的路径(无论是http地址还是file地址) 576 | else{ 577 | moduleOptions.baseUrl = easyModule.mergePath( baseUrl, window.location.href ); 578 | } 579 | } 580 | 581 | easyModule.merge( 'charset', options.charset ); 582 | easyModule.merge( 'alias', options.alias ); 583 | } 584 | 585 | }; 586 | 587 | /* 588 | * 定义模块的全局方法(AMD规范) 589 | * @param { String } 模块名 590 | * @param { Array } 依赖模块列表,单个可以用字符串形式传参,多个用数组形式传参 591 | * @param { Function } 模块的内容 592 | * factory的参数对应依赖模块的外部接口(exports) 593 | */ 594 | window.define = function( name, deps, factory ){ 595 | if( typeof name !== 'string' ){ 596 | if( typeof name === 'function' ){ 597 | factory = name; 598 | } 599 | else{ 600 | factory = deps; 601 | deps = name; 602 | } 603 | name = easyModule.getCurrentScript(); 604 | } 605 | else if( deps !== undefined && factory === undefined ){ 606 | factory = deps; 607 | deps = null; 608 | } 609 | 610 | var alias = moduleOptions.alias, 611 | module = seedExports.module, 612 | mod = module[ name ], 613 | isRepeat = false, 614 | isLoaded = true, 615 | names = [], 616 | urls = [], 617 | insertIndex = 0, 618 | pullIndex = 0, 619 | useKey, data, modUrl, factorys, baseUrl, depMod, depName, result, exports, args, depsData, repeatDepsData, i, repeatName, dep; 620 | 621 | // 在模块都合并的情况下直接执行factory 622 | if( !mod ){ 623 | mod = module[ name ] = {}; 624 | 625 | if( deps ){ 626 | mod.deps = deps; 627 | } 628 | 629 | easyModule.factoryHandle( name, mod, factory ); 630 | return; 631 | } 632 | 633 | useKey = mod.useKey[0]; 634 | data = moduleCache[ useKey ]; 635 | modUrl = mod.url; 636 | 637 | // 开始解析模块内容 638 | mod.status = 2; 639 | mod.deps = []; 640 | 641 | // 如果有依赖模块,先加载依赖模块 642 | if( deps && deps.length ){ 643 | // 依赖模块的baseUrl是当前模块的baseUrl 644 | baseUrl = modUrl.slice( 0, modUrl.lastIndexOf('/') + 1 ); 645 | factorys = data.factorys; 646 | depsData = data.deps[ name ] = {}; 647 | 648 | // 遍历依赖模块列表,如果该依赖模块没加载过, 649 | // 则将该依赖模块名和模块路径添加到当前模块加载队列的数据去进行加载 650 | for( i = 0; i < deps.length; i++ ){ 651 | dep = deps[i]; 652 | result = easyModule.parseModId( alias[dep] || dep, baseUrl ); 653 | depName = alias[ dep ] ? dep : result[0]; 654 | depMod = module[ depName ]; 655 | mod.deps.push( depName ); 656 | depsData[ depName ] = true; 657 | 658 | if( depMod ){ 659 | if( depMod.status !== 4 ){ 660 | // 获取第一个重复依赖的模块名,会在稍后进行factorys的顺序调整 661 | if( !isRepeat ){ 662 | isRepeat = true; 663 | repeatName = depName; 664 | } 665 | isLoaded = false; 666 | } 667 | deps.splice( i--, 1 ); 668 | continue; 669 | } 670 | else{ 671 | depMod = module[ depName ] = {}; 672 | } 673 | 674 | isLoaded = false; 675 | data.length++; 676 | names[ names.length++ ] = depName; 677 | urls[ urls.length++ ] = depMod.url = result[1]; 678 | } 679 | 680 | // 只要当前模块有一个依赖模块没加载完就将当前模块的factory添加到factorys中 681 | if( !isLoaded ){ 682 | factorys.unshift({ 683 | name : name, 684 | factory : factory 685 | }); 686 | 687 | // 有重复依赖时将调整factorys的顺序 688 | if( repeatName ){ 689 | repeatDepsData = data.deps[ repeatName ]; 690 | for( i = factorys.length - 1; i >= 0; i-- ){ 691 | result = factorys[i].name; 692 | if( result === repeatName ){ 693 | pullIndex = i; 694 | if( !repeatDepsData ){ 695 | break; 696 | } 697 | } 698 | 699 | if( repeatDepsData && repeatDepsData[result] ){ 700 | insertIndex = i; 701 | break; 702 | } 703 | } 704 | 705 | // 将重复模块的factory插入到该模块最后一个依赖模块的factory后 706 | factorys.splice( insertIndex + 1, 0, factorys.splice(pullIndex, 1)[0] ); 707 | // 将当前模块的factory插入到重复模块的factory后 708 | factorys.splice( insertIndex + 1, 0, factorys.shift() ); 709 | } 710 | } 711 | 712 | if( names.length ){ 713 | data.names.unshift( names ); 714 | data.urls.unshift( urls ); 715 | } 716 | } 717 | 718 | // 该模块无依赖模块就直接执行其factory 719 | if( isLoaded ){ 720 | easyModule.factoryHandle( name, mod, factory, data ); 721 | } 722 | 723 | easyModule.fireFactory( useKey ); 724 | 725 | // 无依赖列表将删除依赖列表的数组 726 | if( !mod.deps.length ){ 727 | delete mod.deps; 728 | } 729 | }; 730 | 731 | window.define.amd = { 732 | jQuery : true 733 | }; 734 | 735 | // 初始化模块加载器 736 | easyModule.init(); 737 | 738 | return seedExports; 739 | }; 740 | 741 | window.seed = seed(); 742 | 743 | })( window ); --------------------------------------------------------------------------------