UNPKG

scc

Version:

This is a simple combo & compress tool for seajs project.

938 lines (729 loc) 20.7 kB
/** * Sea.js 2.1.0 | seajs.org/LICENSE.md */ (function(global, undefined) { // Avoid conflicting when `sea.js` is loaded multiple times if (global.seajs) { return } var seajs = global.seajs = { // The current version of Sea.js being used version: "2.1.0" } var configData = {} /** * 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") /** * util-log.js - The tiny log function */ // The safe wrapper for `console.xxx` functions // log("message") ==> console.log("message") // log("message", "warn") ==> console.warn("message") var log = seajs.log = function(msg, type) { global.console && // Do NOT print `log(msg)` in non-debug mode (type || configData.debug) && // Set the default value of type (console[type || (type = "log")]) && // Call native method of console console[type](msg) } /** * util-events.js - The minimal events support */ var eventsCache = seajs.events = {} // Bind event seajs.on = function(event, callback) { var list = eventsCache[event] || (eventsCache[event] = []) 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(event, callback) { // Remove *all* events if (!(event || callback)) { seajs.events = eventsCache = {} return seajs } var list = eventsCache[event] if (list) { if (callback) { for (var i = list.length - 1; i >= 0; i--) { if (list[i] === callback) { list.splice(i, 1) } } } else { delete eventsCache[event] } } 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(event, data) { var list = eventsCache[event], fn if (list) { // Copy callback lists to prevent modification list = list.slice() // Execute event callbacks while ((fn = list.shift())) { fn(data) } } return seajs } /** * util-path.js - The utilities for operating path such as id, uri */ var DIRNAME_RE = /[^?#]*\// var DOT_RE = /\/\.\//g var MULTIPLE_SLASH_RE = /([^:\/])\/\/+/g var DOUBLE_DOT_RE = /\/[^/]+\/\.\.\// var URI_END_RE = /\?|\.(?:css|js)$|\/$/ var HASH_END_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, "/") // "file:///a//b/c" ==> "file:///a/b/c" // "http://a//b/c" ==> "http://a/b/c" // "https://a//b/c" ==> "https://a/b/c" // "/a/b//" ==> "/a/b/" path = path.replace(MULTIPLE_SLASH_RE, "$1\/") // 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 uri // normalize("path/to/a") ==> "path/to/a.js" function normalize(uri) { // Call realpath() before adding extension, so that most of uris will // contains no `.` and will just return in realpath() call uri = realpath(uri) // Add the default `.js` extension except that the uri ends with `#` if (HASH_END_RE.test(uri)) { uri = uri.slice(0, -1) } else if (!URI_END_RE.test(uri)) { uri += ".js" } // issue #256: fix `:80` bug in IE return uri.replace(":80/", "/") } var PATHS_RE = /^([^/:]+)(\/.+)$/ var VARS_RE = /{([^{]+)}/g function parseAlias(id) { var alias = configData.alias return alias && isString(alias[id]) ? alias[id] : id } function parsePaths(id) { var paths = configData.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 = configData.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 = configData.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 RELATIVE_RE = /^\./ var ROOT_RE = /^\// function isAbsolute(id) { return ABSOLUTE_RE.test(id) } function isRelative(id) { return RELATIVE_RE.test(id) } function isRoot(id) { return ROOT_RE.test(id) } var ROOT_DIR_RE = /^.*?\/\/.*?\// var id2UriCache = {} function addBase(id, refUri) { var ret if (isAbsolute(id)) { ret = id } else if (isRelative(id)) { ret = (refUri ? dirname(refUri) : configData.cwd) + id } else if (isRoot(id)) { var m = configData.cwd.match(ROOT_DIR_RE) ret = m ? m[0] + id.substring(1) : id } // top-level id else { ret = configData.base + id } return ret } function id2Uri(id, refUri) { if (!id) return "" // Memoize id2Uri function to avoiding duplicated computations var cacheKey = id + refUri if (id2UriCache[cacheKey]) { return id2UriCache[cacheKey] } id = parseAlias(id) id = parsePaths(id) id = parseVars(id) var uri = addBase(id, refUri) uri = normalize(uri) uri = parseMap(uri) return (id2UriCache[cacheKey] = 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) } /** * 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 = undefined } 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 && !configData.debug) { head.removeChild(node) } // Dereference the node node = undefined 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 } } } /** * util-deps.js - The parser for dependencies * ref: tests/research/parse-dependencies/test.html */ var REQUIRE_RE = /"(?:\\"|[^"])*"|'(?:\\'|[^'])*'|\/\*[\S\s]*?\*\/|\/(?:\\\/|[^\/\r\n])+\/(?=[^\/])|\/\/.*|\.\s*require|(?:^|[^$])\brequire\s*\(\s*(["'])(.+?)\1\s*\)/g var SLASH_RE = /\\\\/g function parseDependencies(code) { var ret = [] code.replace(SLASH_RE, "") .replace(REQUIRE_RE, function(m, m1, m2) { if (m2) { ret.push(m2) } }) return ret } /** * module.js - The core of module loader */ var cachedModules = seajs.cache = {} var anonymousModuleData var fetchingList = {} var fetchedList = {} var callbackList = {} var STATUS = { // 1 - The module file is being fetched now FETCHING: 1, // 2 - The module data has been saved to cachedModules SAVED: 2, // 3 - The module and all its dependencies are ready to execute LOADED: 3, // 4 - The module is being executed EXECUTING: 4, // 5 - The module is executed and `module.exports` is available EXECUTED: 5 } // NOTICE: `createModule(uri)` is faster than `new Module(uri)` function createModule(uri) { return { uri: uri, dependencies: [], exports: null, status: 0, callbacks: [] } } function resolve(ids, refUri) { if (isArray(ids)) { var ret = [] for (var i = 0, len = ids.length; i < len; i++) { ret[i] = resolve(ids[i], refUri) } return ret } // Emit `resolve` event for plugins such as plugin-text var data = { id: ids, refUri: refUri } emit("resolve", data) return data.uri || id2Uri(data.id, refUri) } function use(uris, callback) { isArray(uris) || (uris = [uris]) load(uris, function() { var exports = [] for (var i = 0, len = uris.length; i < len; i++) { exports[i] = getExports(cachedModules[uris[i]]) } if (callback) { callback.apply(global, exports) } }) } function load(uris, callback) { var unloadedUris = getUnloadedUris(uris) if (unloadedUris.length === 0) { callback() return } // Emit `load` event for plugins such as plugin-combo emit("load", unloadedUris) var len = unloadedUris.length var remain = len // Register callbacks for (var i = 0; i < len; i++) { cachedModules[unloadedUris[i]].callbacks.push(done) } // Start parallel loading for (i = 0; i < len; i++) { _load(unloadedUris[i]) } function _load(uri) { var mod = cachedModules[uri] mod.status < STATUS.FETCHING ? fetch(uri, loadDeps) : mod.status === STATUS.SAVED && loadDeps() function loadDeps() { load(mod.dependencies, function() { mod.status = STATUS.LOADED // Fire loaded callbacks var fn, fns = mod.callbacks mod.callbacks = [] while ((fn = fns.shift())) fn() }) } } // Check whether all unloadedUris are loaded function done() { if (--remain === 0) { callback() } } } function fetch(uri, callback) { cachedModules[uri].status = STATUS.FETCHING // Emit `fetch` event for plugins such as plugin-combo var data = { uri: uri } emit("fetch", data) var requestUri = data.requestUri || uri if (fetchedList[requestUri]) { callback() return } if (fetchingList[requestUri]) { callbackList[requestUri].push(callback) return } fetchingList[requestUri] = true callbackList[requestUri] = [callback] // Emit `request` event for plugins such as plugin-text var charset = configData.charset emit("request", data = { uri: uri, requestUri: requestUri, callback: onRequested, charset: charset }) if (!data.requested) { request(data.requestUri, onRequested, charset) } function onRequested() { delete fetchingList[requestUri] fetchedList[requestUri] = true // Save meta data of anonymous module if (anonymousModuleData) { save(uri, anonymousModuleData) anonymousModuleData = undefined } // Call callbacks var fn, fns = callbackList[requestUri] delete callbackList[requestUri] while ((fn = fns.shift())) fn() } } function define(id, deps, factory) { var argsLen = arguments.length // define(factory) if (argsLen === 1) { factory = id id = undefined } // define(id, factory) else if (argsLen === 2) { factory = deps deps = undefined } // Parse dependencies according to the module factory code if (!isArray(deps) && isFunction(factory)) { deps = parseDependencies(factory.toString()) } var data = { id: id, uri: resolve(id), deps: deps, factory: factory } // Try to derive uri in IE6-9 for anonymous modules if (!data.uri && doc.attachEvent) { var script = getCurrentScript() if (script) { data.uri = script.src } else { log("Failed to derive: " + factory) // 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 plugin-nocache, seajs node version etc emit("define", data) data.uri ? save(data.uri, data) : // Save information for "saving" work in the script onload event anonymousModuleData = data } function save(uri, meta) { var mod = cachedModules[uri] || (cachedModules[uri] = createModule(uri)) // Do NOT override already saved modules if (mod.status < STATUS.SAVED) { mod.id = meta.id || uri mod.dependencies = resolve(meta.deps || [], uri) mod.factory = meta.factory mod.status = STATUS.SAVED } } function exec(mod) { // Return `null` when `mod` is invalid if (!mod) { return null } // 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 function resolveInThisContext(id) { return resolve(id, mod.uri) } function require(id) { return getExports(cachedModules[resolveInThisContext(id)]) } require.resolve = resolveInThisContext require.async = function(ids, callback) { use(resolveInThisContext(ids), callback) return require } var factory = mod.factory var exports = isFunction(factory) ? factory(require, mod.exports = {}, mod) : factory mod.exports = exports === undefined ? mod.exports : exports mod.status = STATUS.EXECUTED return mod.exports } // Helpers function getUnloadedUris(uris) { var ret = [] for (var i = 0, len = uris.length; i < len; i++) { var uri = uris[i] if (uri) { var mod = cachedModules[uri] || (cachedModules[uri] = createModule(uri)) if (mod.status < STATUS.LOADED) { ret.push(uri) } } } return ret } function getExports(mod) { var exports = exec(mod) if (exports === null && (!mod || !IS_CSS_RE.test(mod.uri))) { emit("error", mod) } return exports } function preload(callback) { var preloadMods = configData.preload var len = preloadMods.length if (len) { use(resolve(preloadMods), function() { // Remove the loaded preload modules preloadMods.splice(0, len) // Allow preload modules to add new preload modules preload(callback) }) } else { callback() } } // Public API seajs.use = function(ids, callback) { // Load preload modules before all other modules preload(function() { use(resolve(ids), callback) }) return seajs } global.define = define // For developers seajs.Module = { STATUS: STATUS, load: use, fetchedList: fetchedList } seajs.resolve = id2Uri seajs.require = function(id) { return (cachedModules[id2Uri(id)] || {}).exports } /** * config.js - The configuration for the loader */ // The root path to use for id2uri parsing configData.base = (function() { var ret = loaderDir // If loaderUri is `http://test.com/libs/seajs/[seajs/1.2.3/]sea.js`, the // baseUri should be `http://test.com/libs/` var m = ret.match(/^(.+?\/)(?:seajs\/)+(?:\d[^/]+\/)?$/) if (m) { ret = m[1] } return ret })() // The loader directory configData.dir = loaderDir // The current working directory configData.cwd = cwd // The charset for requesting files configData.charset = "utf-8" // Modules that are needed to load before all other modules configData.preload = (function() { var plugins = [] // Convert `seajs-xxx` to `seajs-xxx=1` // NOTE: use `seajs-xxx=1` flag in url or cookie to enable `plugin-xxx` var str = loc.search.replace(/(seajs-\w+)(&|$)/g, "$1=1$2") // Add cookie string str += " " + doc.cookie // Exclude seajs-xxx=0 str.replace(/seajs-(\w+)=1/g, function(m, name) { plugins.push(name) }) return plugin2preload(plugins) })() // configData.debug - Debug mode. The default value is false // configData.alias - An object containing shorthands of module id // configData.paths - An object containing path shorthands in module id // configData.vars - The {xxx} variables in module id // configData.map - An array containing rules to map module uri // configData.plugins - An array containing needed plugins function config(data) { // Clear id2Uri cache to avoid getting old uri when config is updated id2UriCache = {} for (var key in data) { var curr = data[key] // Convert plugins to preload config if (curr && key === "plugins") { key = "preload" curr = plugin2preload(curr) } var prev = configData[key] // Merge object config such as alias, vars if (prev && isObject(prev)) { for (var k in curr) { prev[k] = curr[k] } } else { // Concat array config such as map, preload if (isArray(prev)) { curr = prev.concat(curr) } // Make sure that `configData.base` is an absolute directory else if (key === "base") { curr = normalize(addBase(curr + "/")) } // Set config configData[key] = curr } } emit("config", data) return seajs } config.data = configData seajs.config = config function plugin2preload(arr) { var ret = [], name while ((name = arr.shift())) { ret.push(configData.dir + "plugin-" + name) } return ret } })(this);