scc
Version:
This is a simple combo & compress tool for seajs project.
938 lines (729 loc) • 20.7 kB
JavaScript
/**
* 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);