1.定義什麼是模塊,如何聲明模塊:id、deps、factory、exports ----define=function(id,deps,factory){return exports}css
2.管理模塊依賴,解決自動加載。本質實際上是個加載器loader:id配上路徑規則,factory能夠提取依賴模塊,遞歸遍歷後自動加載js文件html
核心的代碼是util-path.js和module.jsnode
發現seajs已經修訂到3.0.0部分實現有改動,有些地方隨手寫的,也沒求證過估計誤人子弟很多。忙着review下,後面再補了三、0版的module.js註釋貽笑大方jquery
seajs.use 執行的入口方法git
define 模塊定義的初始化方法github
seajs.use("a")->Module.get("a") m.load();執行fetch後加載define調用Module.save跟新deps m.onload(); m.exec(); 子模塊: deps->Module.get() fetch/load 通知回調-> 主模板onload
模塊狀態: fetch--Module.fetch 拉取模板 saved--Module.saved 拉取模板成功後就save模塊(define會提取deps) loading--m.load() 檢測依賴模塊是否所有加載 loaded--m.onload() 已加載所有模塊後觸發 executing/executed-m.exec() seajs.use->mod.onload->mod.callback()->mod.exec(); use是seajs模塊入口函數 定義模塊:
Module.use(seajs.use)
Module.define(global.define)web
靜態方法瀏覽器
Module.use(使用模塊load後exec)、Module.preload(獲取data.preload參數後執行Module.use)、Module.get(拿到模塊,若無則建立)緩存
模塊use的執行過程服務器
load 開始加載目標主模塊
get 建立主模塊
fetch 發送request請求加載模塊,加載成功後執行define方法會抽取依賴deps
save 加載成功後會執行define方法,解析依賴的deps。且保持Module.save
load 每次模塊加載成功後onRequest回調主模塊load判斷是否remain==0,調用主模塊onload(新版改用Module.prototype.pass)。每次子模板加載成功後,都要通知依賴本身的主模板。更新狀態以便判斷是否進入onload
onload 所有加載完觸發onload,回調callback(seajs.use(id,callback))。只有主模板會有onload執行
exec 執行模塊factory(即define(fn)的fn),且將require關鍵字映射exec上,依次執行全部依賴模塊
/* 建立seajs對象,標識版本號。建立data數據對象,data掛在seajs爲外部訪問 */ var seajs = global.seajs = { // The current version of Sea.js being used version: "@VERSION" } var data = seajs.data = {} /* seajs本身的事件觸發,on綁定事件,off移除,emit觸發 */ /** * util-events.js - The minimal events support */ var events = data.events = {} // Bind event seajs.on = function(name, callback) { var list = events[name] || (events[name] = []) list.push(callback) return seajs } // Remove event. If `callback` is undefined, remove all callbacks for the // event. If `event` and `callback` are both undefined, remove all callbacks // for all events seajs.off = function(name, callback) { // Remove *all* events if (!(name || callback)) { events = data.events = {} return seajs } var list = events[name] if (list) { if (callback) { for (var i = list.length - 1; i >= 0; i--) { if (list[i] === callback) { list.splice(i, 1) } } } else { delete events[name] } } return seajs } // Emit event, firing all bound callbacks. Callbacks are passed the same // arguments as `emit` is, apart from the event name var emit = seajs.emit = function(name, data) { var list = events[name], fn if (list) { // Copy callback lists to prevent modification list = list.slice() // Execute event callbacks while ((fn = list.shift())) { fn(data) } } return seajs } /* 類型判斷和計數函數 */ /** * util-lang.js - The minimal language enhancement */ function isType(type) { return function(obj) { return Object.prototype.toString.call(obj) === "[object " + type + "]" } } var isObject = isType("Object") var isString = isType("String") var isArray = Array.isArray || isType("Array") var isFunction = isType("Function") var _cid = 0 function cid() { return _cid++ } /* 解析路徑的處理方法,包括: dirname 獲取路徑的目錄 realpath 過濾目錄中的./ ../ normalize 補全文件名.js後綴 parseAlias 解析配置別名 parsePaths 解析配置中的path parseVars 解析配置中{} parseMap 加載路徑根據配置匹配規則映射 addBase 經過id,路由規則生成完整絕對路徑 id2Uri 根據id獲取uri,完整的絕對路徑 loaderDir seajs文件路徑的獲取:根據id=seajsnode查找seajs的dom,獲取src後匹配目錄 */ /** * util-path.js - The utilities for operating path such as id, uri */ var DIRNAME_RE = /[^?#]*\// var DOT_RE = /\/\.\//g var DOUBLE_DOT_RE = /\/[^/]+\/\.\.\// // Extract the directory portion of a path // dirname("a/b/c.js?t=123#xx/zz") ==> "a/b/" // ref: http://jsperf.com/regex-vs-split/2 function dirname(path) { return path.match(DIRNAME_RE)[0] } // Canonicalize a path // realpath("http://test.com/a//./b/../c") ==> "http://test.com/a/c" function realpath(path) { // /a/b/./c/./d ==> /a/b/c/d path = path.replace(DOT_RE, "/") // a/b/c/../../d ==> a/b/../d ==> a/d while (path.match(DOUBLE_DOT_RE)) { path = path.replace(DOUBLE_DOT_RE, "/") } return path } // Normalize an id // normalize("path/to/a") ==> "path/to/a.js" // NOTICE: substring is faster than negative slice and RegExp function normalize(path) { var last = path.length - 1 // If the uri ends with `#`, just return it without '#' if (path.charAt(last) === "#") { return path.substring(0, last) } return (path.substring(last - 2) === ".js" || path.indexOf("?") > 0 || path.substring(last - 3) === ".css") ? path : path + ".js" } var PATHS_RE = /^([^/:]+)(\/.+)$/ var VARS_RE = /{([^{]+)}/g function parseAlias(id) { var alias = data.alias return alias && isString(alias[id]) ? alias[id] : id } function parsePaths(id) { var paths = data.paths var m if (paths && (m = id.match(PATHS_RE)) && isString(paths[m[1]])) { id = paths[m[1]] + m[2] } return id } function parseVars(id) { var vars = data.vars if (vars && id.indexOf("{") > -1) { id = id.replace(VARS_RE, function(m, key) { return isString(vars[key]) ? vars[key] : m }) } return id } function parseMap(uri) { var map = data.map var ret = uri if (map) { for (var i = 0, len = map.length; i < len; i++) { var rule = map[i] ret = isFunction(rule) ? (rule(uri) || uri) : uri.replace(rule[0], rule[1]) // Only apply the first matched rule if (ret !== uri) break } } return ret } var ABSOLUTE_RE = /^\/\/.|:\// var ROOT_DIR_RE = /^.*?\/\/.*?\// function addBase(id, refUri) { var ret var first = id.charAt(0) // Absolute if (ABSOLUTE_RE.test(id)) { ret = id } // Relative else if (first === ".") { ret = realpath((refUri ? dirname(refUri) : data.cwd) + id) } // Root else if (first === "/") { var m = data.cwd.match(ROOT_DIR_RE) ret = m ? m[0] + id.substring(1) : id } // Top-level else { ret = data.base + id } return ret } function id2Uri(id, refUri) { if (!id) return "" id = parseAlias(id) id = parsePaths(id) id = parseVars(id) id = normalize(id) var uri = addBase(id, refUri) uri = parseMap(uri) return uri } var doc = document var loc = location var cwd = dirname(loc.href) var scripts = doc.getElementsByTagName("script") // Recommend to add `seajsnode` id for the `sea.js` script element var loaderScript = doc.getElementById("seajsnode") || scripts[scripts.length - 1] // When `sea.js` is inline, set loaderDir to current working directory var loaderDir = dirname(getScriptAbsoluteSrc(loaderScript) || cwd) function getScriptAbsoluteSrc(node) { return node.hasAttribute ? // non-IE6/7 node.src : // see http://msdn.microsoft.com/en-us/library/ms536429(VS.85).aspx node.getAttribute("src", 4) } /* request 動態加載css或者script到head 建立dom原生link、script標籤 addOnload 定義瀏覽器兼容的異步請求回調 getCurrentScript 獲取當前資源加載狀態爲進行中interactive的資源 */ /** * util-request.js - The utilities for requesting script and style files * ref: tests/research/load-js-css/test.html */ var head = doc.getElementsByTagName("head")[0] || doc.documentElement var baseElement = head.getElementsByTagName("base")[0] var IS_CSS_RE = /\.css(?:\?|$)/i var READY_STATE_RE = /^(?:loaded|complete|undefined)$/ var currentlyAddingScript var interactiveScript // `onload` event is supported in WebKit < 535.23 and Firefox < 9.0 // ref: // - https://bugs.webkit.org/show_activity.cgi?id=38995 // - https://bugzilla.mozilla.org/show_bug.cgi?id=185236 // - https://developer.mozilla.org/en/HTML/Element/link#Stylesheet_load_events var isOldWebKit = (navigator.userAgent .replace(/.*AppleWebKit\/(\d+)\..*/, "$1")) * 1 < 536 function request(url, callback, charset) { var isCSS = IS_CSS_RE.test(url) var node = doc.createElement(isCSS ? "link" : "script") if (charset) { var cs = isFunction(charset) ? charset(url) : charset if (cs) { node.charset = cs } } addOnload(node, callback, isCSS) if (isCSS) { node.rel = "stylesheet" node.href = url } else { node.async = true node.src = url } // For some cache cases in IE 6-8, the script executes IMMEDIATELY after // the end of the insert execution, so use `currentlyAddingScript` to // hold current node, for deriving url in `define` call currentlyAddingScript = node // ref: #185 & http://dev.jquery.com/ticket/2709 baseElement ? head.insertBefore(node, baseElement) : head.appendChild(node) currentlyAddingScript = null } function addOnload(node, callback, isCSS) { var missingOnload = isCSS && (isOldWebKit || !("onload" in node)) // for Old WebKit and Old Firefox if (missingOnload) { setTimeout(function() { pollCss(node, callback) }, 1) // Begin after node insertion return } node.onload = node.onerror = node.onreadystatechange = function() { if (READY_STATE_RE.test(node.readyState)) { // Ensure only run once and handle memory leak in IE node.onload = node.onerror = node.onreadystatechange = null // Remove the script to reduce memory leak if (!isCSS && !data.debug) { head.removeChild(node) } // Dereference the node node = null callback() } } } function pollCss(node, callback) { var sheet = node.sheet var isLoaded // for WebKit < 536 if (isOldWebKit) { if (sheet) { isLoaded = true } } // for Firefox < 9.0 else if (sheet) { try { if (sheet.cssRules) { isLoaded = true } } catch (ex) { // The value of `ex.name` is changed from "NS_ERROR_DOM_SECURITY_ERR" // to "SecurityError" since Firefox 13.0. But Firefox is less than 9.0 // in here, So it is ok to just rely on "NS_ERROR_DOM_SECURITY_ERR" if (ex.name === "NS_ERROR_DOM_SECURITY_ERR") { isLoaded = true } } } setTimeout(function() { if (isLoaded) { // Place callback here to give time for style rendering callback() } else { pollCss(node, callback) } }, 20) } function getCurrentScript() { if (currentlyAddingScript) { return currentlyAddingScript } // For IE6-9 browsers, the script onload event may not fire right // after the the script is evaluated. Kris Zyp found that it // could query the script nodes and the one that is in "interactive" // mode indicates the current script // ref: http://goo.gl/JHfFW if (interactiveScript && interactiveScript.readyState === "interactive") { return interactiveScript } var scripts = head.getElementsByTagName("script") for (var i = scripts.length - 1; i >= 0; i--) { var script = scripts[i] if (script.readyState === "interactive") { interactiveScript = script return interactiveScript } } } /** * module.js - The core of module loader */ //系統全局對象,用來保存全局數據和狀態 var cachedMods = seajs.cache = {} var anonymousMeta var fetchingList = {} var fetchedList = {} var callbackList = {} var STATUS = Module.STATUS = { // 1 - The `module.uri` is being fetched FETCHING: 1, // 2 - The meta data has been saved to cachedMods SAVED: 2, // 3 - The `module.dependencies` are being loaded LOADING: 3, // 4 - The module are ready to execute LOADED: 4, // 5 - The module is being executed EXECUTING: 5, // 6 - The `module.exports` is available EXECUTED: 6 } //模塊類 id、deps、exports 構造函數 function Module(uri, deps) { this.uri = uri this.dependencies = deps || [] this.exports = null this.status = 0//模塊狀態 // The number of unloaded dependencies this._remain = 0 // Who depend on me this._waitings = {} } //Module靜態方法,Module.use使用模塊,Module.preload調用data.preload後回調Module.use // Load preload modules before all other modules Module.preload = function(callback) { var preloadMods = data.preload var len = preloadMods.length if (len) { Module.use(preloadMods, function() { // Remove the loaded preload modules preloadMods.splice(0, len) // Allow preload modules to add new preload modules Module.preload(callback) }, data.cwd + "_preload_" + cid()) } else { callback() } } //入口模塊,ids--依賴模塊,uri模塊id:獲取uri模塊 // Use function is equal to load a anonymous module Module.use = function (ids, callback, uri) { var mod = Module.get(uri, isArray(ids) ? ids : [ids]) mod.callback = function() { var exports = [] var uris = mod.resolve() for (var i = 0, len = uris.length; i < len; i++) { exports[i] = cachedMods[uris[i]].exec() } if (callback) { callback.apply(global, exports) } delete mod.callback } mod.load() } //獲取uri模塊,若cachedMods中無則建立個新模塊 Module.get = function(uri, deps) { return cachedMods[uri] || (cachedMods[uri] = new Module(uri, deps)) } // seajs.use 入口方法 // 執行過程:A依賴B,B依賴C,seajs.use(A): /* fetch 獲取A模塊的依賴模塊(B),B拉取成功後回調A.load(每次依賴的文件onRquest,要跟新本身的狀態) load 遍歷A模塊依賴的全部模塊狀態(B/C),直到remain==0,觸發onload onload 當A模塊全部依賴都已加載,觸發A.callback exec seajs.use 將執行模塊的callback設置爲exec,調用A.exec,且A模塊中的require映射了exec方法,鏈式的執行下去。。 */ seajs.use = function(ids, callback) { Module.preload(function() { Module.use(ids, callback, data.cwd + "_use_" + cid()) }) return seajs } //將依賴的模塊經過fetch方法註冊,最終執行load請求js,onRequest回調onload // Load module.dependencies and fire onload when all done Module.prototype.load = function() { var mod = this // If the module is being loaded, just wait it onload call if (mod.status >= STATUS.LOADING) { return } mod.status = STATUS.LOADING // Emit `load` event for plugins such as combo plugin var uris = mod.resolve()//獲取依賴uris emit("load", uris) var len = mod._remain = uris.length var m // Initialize modules and register waitings 拿到依賴模塊註冊依賴 for (var i = 0; i < len; i++) { m = Module.get(uris[i])//獲取依賴module if (m.status < STATUS.LOADED) {//模塊未加載 // Maybe duplicate m._waitings[mod.uri] = (m._waitings[mod.uri] || 0) + 1//記錄下有幾個模塊依賴本身 } else { mod._remain--//加載後-- } } //若全部依賴都加載後,執行onload if (mod._remain === 0) { mod.onload() return } // Begin parallel loading var requestCache = {} for (i = 0; i < len; i++) { m = cachedMods[uris[i]] if (m.status < STATUS.FETCHING) { m.fetch(requestCache) } else if (m.status === STATUS.SAVED) { m.load() } } // Send all requests at last to avoid cache bug in IE6-9. Issues#808 for (var requestUri in requestCache) { if (requestCache.hasOwnProperty(requestUri)) { requestCache[requestUri]() } } } //將模塊的全部依賴的uri都註冊到此模塊的requestCache列表中 // Fetch a module Module.prototype.fetch = function(requestCache) { var mod = this var uri = mod.uri mod.status = STATUS.FETCHING // Emit `fetch` event for plugins such as combo plugin var emitData = { uri: uri } emit("fetch", emitData) var requestUri = emitData.requestUri || uri // Empty uri or a non-CMD module if (!requestUri || fetchedList[requestUri]) { mod.load() return } if (fetchingList[requestUri]) { callbackList[requestUri].push(mod) return } fetchingList[requestUri] = true callbackList[requestUri] = [mod] // Emit `request` event for plugins such as text plugin emit("request", emitData = { uri: uri, requestUri: requestUri, onRequest: onRequest, charset: data.charset }) if (!emitData.requested) { requestCache ? requestCache[emitData.requestUri] = sendRequest : sendRequest() } function sendRequest() { request(emitData.requestUri, emitData.onRequest, emitData.charset) } function onRequest() { delete fetchingList[requestUri] fetchedList[requestUri] = true // Save meta data of anonymous module if (anonymousMeta) { save(uri, anonymousMeta) anonymousMeta = null } // Call callbacks var m, mods = callbackList[requestUri] delete callbackList[requestUri] while ((m = mods.shift())) m.load() } } //若模塊的依賴加載所有加載觸發onload回調mod.callback,同時通知全部依賴他的模塊 // Call this method when module is loaded Module.prototype.onload = function() { var mod = this mod.status = STATUS.LOADED if (mod.callback) { mod.callback() } // Notify waiting modules to fire onload var waitings = mod._waitings var uri, m for (uri in waitings) { if (waitings.hasOwnProperty(uri)) { m = cachedMods[uri] m._remain -= waitings[uri] if (m._remain === 0) { m.onload() } } } // Reduce memory taken delete mod._waitings delete mod._remain } //解析模塊依賴的uris 獲取絕對uris // Resolve module.dependencies Module.prototype.resolve = function() { var mod = this var ids = mod.dependencies var uris = [] for (var i = 0, len = ids.length; i < len; i++) { uris[i] = resolve(ids[i], mod.uri) } return uris } //觸發resolve事件,返回id2Uri // Helpers function resolve(id, refUri) { // Emit `resolve` event for plugins such as text plugin var emitData = { id: id, refUri: refUri } emit("resolve", emitData) return emitData.uri || id2Uri(emitData.id, refUri) } //加載完全部依賴的模塊後,執行自身模塊,同時經過關鍵字require將依賴的模塊順序執行 // Execute a module Module.prototype.exec = function () { var mod = this // When module is executed, DO NOT execute it again. When module // is being executed, just return `module.exports` too, for avoiding // circularly calling if (mod.status >= STATUS.EXECUTING) { return mod.exports//模塊已執行 } mod.status = STATUS.EXECUTING // Create require var uri = mod.uri //執行require的id function require(id) { return cachedMods[require.resolve(id)].exec() } //得到id模塊的絕對路徑 require.resolve = function(id) { return resolve(id, uri) } //異步請求uri require.async = function(ids, callback) { Module.use(ids, callback, uri + "_async_" + cid()) return require } // Exec factory var factory = mod.factory var exports = isFunction(factory) ? factory(require, mod.exports = {}, mod) : factory if (exports === undefined) { exports = mod.exports } // Emit `error` event if (exports === null && !IS_CSS_RE.test(uri)) { emit("error", mod) } // Reduce memory leak delete mod.factory mod.exports = exports mod.status = STATUS.EXECUTED // Emit `exec` event emit("exec", mod) return exports } //對外的接口 define(function(){...})用於定義模塊,初始化模塊的基礎屬性(並不執行模塊factory方法) Module.define.cmd = {} global.define = Module.define // Define a module Module.define = function (id, deps, factory) { var argsLen = arguments.length // define(factory) if (argsLen === 1) { factory = id id = undefined } else if (argsLen === 2) { factory = deps // define(deps, factory) if (isArray(id)) { deps = id id = undefined } // define(id, factory) else { deps = undefined } } // Parse dependencies according to the module factory code if (!isArray(deps) && isFunction(factory)) { deps = parseDependencies(factory.toString()) } var meta = { id: id, uri: resolve(id), deps: deps, factory: factory } // Try to derive uri in IE6-9 for anonymous modules if (!meta.uri && doc.attachEvent) { var script = getCurrentScript() if (script) { meta.uri = script.src } // NOTE: If the id-deriving methods above is failed, then falls back // to use onload event to get the uri } // Emit `define` event, used in nocache plugin, seajs node version etc emit("define", meta) meta.uri ? save(meta.uri, meta) : // Save information for "saving" work in the script onload event anonymousMeta = meta } function save(uri, meta) { var mod = Module.get(uri) // Do NOT override already saved modules if (mod.status < STATUS.SAVED) { mod.id = meta.id || uri mod.dependencies = meta.deps || [] mod.factory = meta.factory mod.status = STATUS.SAVED } } // For Developers 調試接口 seajs.Module = Module data.fetchedList = fetchedList data.cid = cid //獲取模塊id seajs.resolve = id2Uri //獲取模塊exports接口 seajs.require = function(id) { return (cachedMods[resolve(id)] || {}).exports }
附commonjs、seajs、requirejs比較
function Module(uri, deps) { this.uri = uri this.dependencies = deps || [] this.status = 0 this._entry = [] } //獲取模板的全路徑 // Resolve id to uri Module.resolve = function(id, refUri) { // Emit `resolve` event for plugins such as text plugin var emitData = { id: id, refUri: refUri } emit("resolve", emitData) return emitData.uri || seajs.resolve(emitData.id, refUri) } //定義一個模塊mod // Define a module Module.define = function (id, deps, factory) { var argsLen = arguments.length // define(factory) if (argsLen === 1) { factory = id id = undefined } else if (argsLen === 2) { factory = deps // define(deps, factory) if (isArray(id)) { deps = id id = undefined } // define(id, factory) else { deps = undefined } } // Parse dependencies according to the module factory code if (!isArray(deps) && isFunction(factory)) { deps = parseDependencies(factory.toString()) } var meta = { id: id, uri: Module.resolve(id), deps: deps, factory: factory } // Try to derive uri in IE6-9 for anonymous modules if (!meta.uri && doc.attachEvent) { var script = getCurrentScript() if (script) { meta.uri = script.src } // NOTE: If the id-deriving methods above is failed, then falls back // to use onload event to get the uri } // Emit `define` event, used in nocache plugin, seajs node version etc emit("define", meta) meta.uri ? Module.save(meta.uri, meta) : // Save information for "saving" work in the script onload event anonymousMeta = meta } //生成mod,賦值一些基本屬性 // Save meta data to cachedMods Module.save = function(uri, meta) { var mod = Module.get(uri) // Do NOT override already saved modules if (mod.status < STATUS.SAVED) { mod.id = meta.id || uri mod.dependencies = meta.deps || [] mod.factory = meta.factory mod.status = STATUS.SAVED emit("save", mod) } } //建立mod,緩存到cachedMods // Get an existed module or create a new one Module.get = function(uri, deps) { return cachedMods[uri] || (cachedMods[uri] = new Module(uri, deps)) } //load執行一個匿名module // Use function is equal to load a anonymous module Module.use = function (ids, callback, uri) { var mod = Module.get(uri, isArray(ids) ? ids : [ids]) mod._entry.push(mod) mod.history = {} mod.remain = 1 mod.callback = function() { var exports = [] var uris = mod.resolve() for (var i = 0, len = uris.length; i < len; i++) { exports[i] = cachedMods[uris[i]].exec() } if (callback) { callback.apply(global, exports) } delete mod.callback delete mod.history delete mod.remain delete mod._entry } mod.load() } /************************************************************* 對外只暴露 Module.use(seajs.use) Module.define(global.define) ************************************************************/ //得到mod的全部dep的uri // Resolve module.dependencies Module.prototype.resolve = function() { var mod = this var ids = mod.dependencies var uris = [] for (var i = 0, len = ids.length; i < len; i++) { uris[i] = Module.resolve(ids[i], mod.uri) } return uris } //遞歸遍歷自身依賴的子模板,判斷所有加載完畢後執行onload // Load module.dependencies and fire onload when all done Module.prototype.load = function() { var mod = this // If the module is being loaded, just wait it onload call if (mod.status >= STATUS.LOADING) { return } mod.status = STATUS.LOADING // Emit `load` event for plugins such as combo plugin var uris = mod.resolve() emit("load", uris) //將依賴uri遍歷,判斷是否所有加載 // Pass entry to it's dependencies mod.pass(uris) //若是所有加載,執行onload // If module has entries not be passed, call onload if (mod._entry.length) { mod.onload() return } // Begin parallel loading var requestCache = {} var m //對依賴的模板執行拉取,拉取後執行load遞歸子依賴 for (var i = 0, len = uris.length; i < len; i++) { m = cachedMods[uris[i]] if (m.status < STATUS.FETCHING) { m.fetch(requestCache) } //若模板已經拉取過,跳過拉取執行load else if (m.status === STATUS.SAVED) { m.load() } } // Send all requests at last to avoid cache bug in IE6-9. Issues#808 for (var requestUri in requestCache) { if (requestCache.hasOwnProperty(requestUri)) { requestCache[requestUri]() } } } //將依賴uri遍歷,判斷是否所有加載 Module.prototype.pass = function(uris) { var mod = this uris = uris || mod.resolve() var len = uris.length //use時mod._entry.push(mod) for (var i = 0; i < mod._entry.length; i++) { var entry = mod._entry[i] var count = 0 for (var j = 0; j < len; j++) { var m = Module.get(uris[j]) // If the module is unload and unused in the entry, pass entry to it if (m.status < STATUS.LOADED && !entry.history.hasOwnProperty(m.uri)) { entry.history[m.uri] = true count++ m._entry.push(entry) if(m.status === STATUS.LOADING) { m.pass() } } } // If has passed the entry to it's dependencies, modify the entry's count and del it in the module if (count > 0) { entry.remain += count - 1 mod._entry.shift() i-- } } } //拉取模板,成功拉取後save並通知依賴本身的模板更新load // Fetch a module Module.prototype.fetch = function(requestCache) { var mod = this var uri = mod.uri mod.status = STATUS.FETCHING // Emit `fetch` event for plugins such as combo plugin var emitData = { uri: uri } emit("fetch", emitData) var requestUri = emitData.requestUri || uri // Empty uri or a non-CMD module if (!requestUri || fetchedList.hasOwnProperty(requestUri)) { mod.load() return } if (fetchingList.hasOwnProperty(requestUri)) { callbackList[requestUri].push(mod) return } fetchingList[requestUri] = true callbackList[requestUri] = [mod] // Emit `request` event for plugins such as text plugin emit("request", emitData = { uri: uri, requestUri: requestUri, onRequest: onRequest, charset: data.charset }) if (!emitData.requested) { requestCache ? requestCache[emitData.requestUri] = sendRequest : sendRequest() } function sendRequest() { seajs.request(emitData.requestUri, emitData.onRequest, emitData.charset) } function onRequest(error) { delete fetchingList[requestUri] fetchedList[requestUri] = true // Save meta data of anonymous module if (anonymousMeta) { Module.save(uri, anonymousMeta) anonymousMeta = null } // Call callbacks var m, mods = callbackList[requestUri] delete callbackList[requestUri] while ((m = mods.shift())) { // When 404 occurs, the params error will be true if(error === true) { m.error() } else { m.load() } } } } //所有加載完觸發onload,回調callback(seajs.use(id,callback)) // Call this method when module is loaded Module.prototype.onload = function() { var mod = this mod.status = STATUS.LOADED for (var i = 0, len = mod._entry.length; i < len; i++) { var entry = mod._entry[i] if (--entry.remain === 0) { entry.callback() } } delete mod._entry } //seajs.use->mod.onload->mod.callback()->mod.exec(); // Execute a module Module.prototype.exec = function () { var mod = this // When module is executed, DO NOT execute it again. When module // is being executed, just return `module.exports` too, for avoiding // circularly calling if (mod.status >= STATUS.EXECUTING) { return mod.exports } mod.status = STATUS.EXECUTING if (mod._entry && !mod._entry.length) { delete mod._entry } //non-cmd module has no property factory and exports if (!mod.hasOwnProperty('factory')) { mod.non = true return } // Create require var uri = mod.uri function require(id) { var m = Module.get(require.resolve(id)) if (m.status == STATUS.ERROR) { throw new Error('module was broken: ' + m.uri); } return m.exec() } require.resolve = function(id) { return Module.resolve(id, uri) } require.async = function(ids, callback) { Module.use(ids, callback, uri + "_async_" + cid()) return require } // Exec factory var factory = mod.factory var exports = isFunction(factory) ? factory(require, mod.exports = {}, mod) : factory if (exports === undefined) { exports = mod.exports } // Reduce memory leak delete mod.factory mod.exports = exports mod.status = STATUS.EXECUTED // Emit `exec` event emit("exec", mod) return exports }