Created
January 7, 2013 18:54
-
-
Save anonymous/4477439 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// machine for a loader instance | |
enyo.machine = { | |
sheet: function(inPath) { | |
var type = "text/css"; | |
var rel = "stylesheet"; | |
var isLess = (inPath.slice(-5) == ".less"); | |
if (isLess) { | |
if (window.less) { | |
// If client-side less is loaded, insert the less stylesheet | |
type = "text/less"; | |
rel = "stylesheet/less"; | |
} else { | |
// Otherwise, we expect a css file of the same name to exist | |
inPath = inPath.slice(0, inPath.length-4) + "css"; | |
} | |
} | |
var link; | |
if (enyo.runtimeLoading || isLess) { | |
link = document.createElement('link'); | |
link.href = inPath; | |
link.media = "screen"; | |
link.rel = rel; | |
link.type = type; | |
document.getElementsByTagName('head')[0].appendChild(link); | |
} else { | |
document.write('<link href="' + inPath + '" media="screen" rel="' + rel + '" type="' + type + '" />'); | |
} | |
if (isLess && window.less) { | |
less.sheets.push(link); | |
if (!enyo.loader.finishCallbacks.lessRefresh) { | |
enyo.loader.finishCallbacks.lessRefresh = function() { | |
less.refresh(true); | |
}; | |
} | |
} | |
}, | |
script: function(inSrc, onLoad, onError) { | |
if (!enyo.runtimeLoading) { | |
document.write('<scri' + 'pt src="' + inSrc + '"' + (onLoad ? ' onload="' + onLoad + '"' : '') + (onError ? ' onerror="' + onError + '"' : '') + '></scri' + 'pt>'); | |
} else { | |
var script = document.createElement('script'); | |
script.src = inSrc; | |
script.onload = onLoad; | |
script.onerror = onError; | |
document.getElementsByTagName('head')[0].appendChild(script); | |
} | |
}, | |
inject: function(inCode) { | |
document.write('<script type="text/javascript">' + inCode + "</script>"); | |
} | |
}; | |
// create a dependency processor using our script machine | |
enyo.loader = new enyo.loaderFactory(enyo.machine); | |
// dependency API uses enyo loader | |
enyo.depends = function() { | |
var ldr = enyo.loader; | |
if (!ldr.packageFolder) { | |
var tag = enyo.locateScript("package.js"); | |
if (tag && tag.path) { | |
ldr.aliasPackage(tag.path); | |
ldr.packageFolder = tag.path + "/"; | |
//console.log("detected PACKAGEFOLDER [" + ldr.packageFolder + "]"); | |
} | |
} | |
ldr.load.apply(ldr, arguments); | |
}; | |
// Runtime loader | |
// Usage: enyo.load(depends, [onLoadCallback]) | |
// where - depends is string or array of string paths to package.js, script, or css to load | |
// - doneCallback is fired after file or package loading has completed | |
// Only one file/package is loaded at a time; additional calls are queued and loading deferred | |
(function() { | |
var enyo = window.enyo; | |
var runtimeLoadQueue = []; | |
enyo.load = function(depends, onLoadCallback) { | |
runtimeLoadQueue.push(arguments); | |
if (!enyo.runtimeLoading) { | |
enyo.runtimeLoading = true; | |
runtimeLoad(); | |
} | |
}; | |
function runtimeLoad(onLoad) { | |
if (onLoad) { | |
onLoad(); // Run user callback function | |
} | |
if (runtimeLoadQueue.length) { | |
var args = runtimeLoadQueue.shift(); | |
var depends = args[0]; | |
var dependsArg = enyo.isArray(depends) ? depends : [depends]; | |
var onLoadCallback = args[1]; | |
enyo.loader.finishCallbacks.runtimeLoader = function(inBlock) { | |
// Once loader is done loading a package, we chain a call to runtimeLoad(), | |
// which will call the onLoadCallback from the original load call, passing | |
// a reference to the depends argument from the original call for tracking, | |
// followed by kicking off any additionally queued load() calls | |
runtimeLoad(function() { | |
if (onLoadCallback) { | |
onLoadCallback(inBlock); | |
} | |
}); | |
}; | |
enyo.loader.packageFolder = "./"; | |
// Kick off next queued call to loader | |
enyo.depends.apply(this, dependsArg); | |
} else { | |
enyo.runtimeLoading = false; | |
enyo.loader.packageFolder = ""; | |
} | |
} | |
})(); | |
// predefined path aliases | |
enyo.path.addPaths({ | |
enyo: enyo.args.root, | |
lib: "$enyo/../lib" | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function() { | |
enyo = window.enyo || {}; | |
enyo.pathResolverFactory = function() { | |
this.paths = {}; | |
}; | |
enyo.pathResolverFactory.prototype = { | |
addPath: function(inName, inPath) { | |
return this.paths[inName] = inPath; | |
}, | |
addPaths: function(inPaths) { | |
if (inPaths) { | |
for (var n in inPaths) { | |
this.addPath(n, inPaths[n]); | |
} | |
} | |
}, | |
includeTrailingSlash: function(inPath) { | |
return (inPath && inPath.slice(-1) !== "/") ? inPath + "/" : inPath; | |
}, | |
// match $name | |
rewritePattern: /\$([^\/\\]*)(\/)?/g, | |
// replace macros of the form $pathname with the mapped value of paths.pathname | |
rewrite: function (inPath) { | |
var working, its = this.includeTrailingSlash, paths = this.paths; | |
var fn = function(macro, name) { | |
working = true; | |
return its(paths[name]) || ''; | |
}; | |
var result = inPath; | |
do { | |
working = false; | |
result = result.replace(this.rewritePattern, fn); | |
} while (working); | |
return result; | |
} | |
}; | |
enyo.path = new enyo.pathResolverFactory(); | |
enyo.loaderFactory = function(inMachine, inPathResolver) { | |
this.machine = inMachine; | |
// package information | |
this.packages = []; | |
// module information | |
this.modules = []; | |
// stylesheet paths | |
this.sheets = []; | |
// (protected) internal dependency stack | |
this.stack = []; | |
this.pathResolver = inPathResolver || enyo.path; | |
this.packageName = ""; | |
this.packageFolder = ""; | |
this.finishCallbacks = {}; | |
}; | |
enyo.loaderFactory.prototype = { | |
verbose: false, | |
loadScript: function(inScript, success, failure) { | |
this.machine.script(inScript, success, failure); | |
}, | |
loadSheet: function(inSheet) { | |
this.machine.sheet(inSheet); | |
}, | |
loadPackage: function(inPackage) { | |
this.machine.script(inPackage); | |
}, | |
report: function() { | |
}, | |
// | |
load: function(/*<inDependency0, inDependency1 ...>*/) { | |
// begin processing dependencies | |
this.more({ | |
index: 0, | |
depends: arguments || [] | |
}); | |
}, | |
more: function(inBlock) { | |
// a 'block' is a dependency list with a bookmark | |
// the bookmark (index) allows us to interrupt | |
// processing and then continue asynchronously. | |
if (inBlock) { | |
// returns true if this block has asynchronous requirements | |
// in that case, we unwind the stack. The asynchronous loader | |
// must provide the continuation (by calling 'more' again). | |
if (this.continueBlock(inBlock)) { | |
return; | |
} | |
} | |
// A package is now complete. Pop the block that was interrupted for that package (if any). | |
var block = this.stack.pop(); | |
if (block) { | |
// block.packageName is the name of the package that interrupted us | |
//this.report("finished package", block.packageName); | |
if (this.verbose) { | |
console.groupEnd("* finish package (" + (block.packageName || "anon") + ")"); | |
} | |
// cache the folder for the currently processing package | |
this.packageFolder = block.folder; | |
// no current package | |
this.packageName = ""; | |
// process this new block | |
this.more(block); | |
} else { | |
this.finish(inBlock); | |
} | |
}, | |
finish: function(inBlock) { | |
this.packageFolder = ""; | |
if (this.verbose) { | |
console.log("-------------- fini"); | |
} | |
for (var i in this.finishCallbacks) { | |
if (this.finishCallbacks[i]) { | |
this.finishCallbacks[i](inBlock); | |
this.finishCallbacks[i] = null; | |
} | |
} | |
}, | |
continueBlock: function(inBlock) { | |
while (inBlock.index < inBlock.depends.length) { | |
var d = inBlock.depends[inBlock.index++]; | |
if (d) { | |
if (typeof d == "string") { | |
if (this.require(d, inBlock)) { | |
// return true to indicate we need to interrupt | |
// processing until asynchronous file load completes | |
// the load process itself must provide the | |
// continuation | |
return true; | |
} | |
} else { | |
this.pathResolver.addPaths(d); | |
} | |
} | |
} | |
}, | |
require: function(inPath, inBlock) { | |
// process aliases | |
var path = this.pathResolver.rewrite(inPath); | |
// get path root | |
var prefix = this.getPathPrefix(inPath); | |
// assemble path | |
path = prefix + path; | |
// process path | |
if ((path.slice(-4) == ".css") || (path.slice(-5) == ".less")) { | |
if (this.verbose) { | |
console.log("+ stylesheet: [" + prefix + "][" + inPath + "]"); | |
} | |
this.requireStylesheet(path); | |
} else if (path.slice(-3) == ".js" && path.slice(-10) != "package.js") { | |
if (this.verbose) { | |
console.log("+ module: [" + prefix + "][" + inPath + "]"); | |
} | |
return this.requireScript(inPath, path, inBlock); | |
} else { | |
// package | |
this.requirePackage(path, inBlock); | |
// return true to indicate a package was located and | |
// we need to interrupt further processing until it's completed | |
return true; | |
} | |
}, | |
getPathPrefix: function(inPath) { | |
var delim = inPath.slice(0, 1); | |
if ((delim != "/") && (delim != "\\") && (delim != "$") && !/^https?:/i.test(inPath)) { | |
return this.packageFolder; | |
} | |
return ""; | |
}, | |
requireStylesheet: function(inPath) { | |
// stylesheet | |
this.sheets.push(inPath); | |
this.loadSheet(inPath); | |
}, | |
requireScript: function(inRawPath, inPath, inBlock) { | |
// script file | |
this.modules.push({ | |
packageName: this.packageName, | |
rawPath: inRawPath, | |
path: inPath | |
}); | |
if(enyo.runtimeLoading) { | |
var _this = this; | |
var success = function() { | |
_this.more(inBlock); | |
}; | |
var failure = function() { | |
inBlock.failed = inBlock.failed || []; | |
// index has to be decremented because it's incremented after reference in continueBlock | |
inBlock.failed.push(inBlock.index-1); | |
_this.more(inBlock); | |
} | |
this.loadScript(inPath, success, failure); | |
} else { | |
this.loadScript(inPath); | |
} | |
return enyo.runtimeLoading; | |
}, | |
decodePackagePath: function(inPath) { | |
// A package path can be encoded in two ways: | |
// | |
// 1. [folder] | |
// 2. [folder]/[*package.js] | |
// | |
// Note: manifest file name must end in "package.js" | |
// | |
var alias = '', target = '', folder = '', manifest = 'package.js'; | |
// convert back slashes to forward slashes, remove double slashes, split on slash | |
var parts = inPath.replace(/\\/g, "/").replace(/\/\//g, "/").replace(/:\//, "://").split("/"); | |
var i, p; | |
if (parts.length) { | |
// if inPath has a trailing slash, parts has an empty string which we pop off and ignore | |
var name = parts.pop() || parts.pop() || ""; | |
// test if name includes the manifest tag | |
if (name.slice(-manifest.length) !== manifest) { | |
// if not a manifest name, it's part of the folder path | |
parts.push(name); | |
} else { | |
// otherwise this is the manifest name | |
manifest = name; | |
} | |
// | |
folder = parts.join("/"); | |
folder = (folder ? folder + "/" : ""); | |
manifest = folder + manifest; | |
// | |
// build friendly aliasing: | |
// | |
for (i=parts.length-1; i >= 0; i--) { | |
if (parts[i] == "source") { | |
parts.splice(i, 1); | |
break; | |
} | |
} | |
target = parts.join("/"); | |
// | |
// portable aliasing: | |
// | |
// packages that are rooted at a folder named "enyo" or "lib" do not | |
// include that root path in their alias | |
// | |
// remove */lib or */enyo prefix | |
// | |
// e.g. foo/bar/baz/lib/zot -> zot package | |
// | |
for (i=parts.length-1; (p=parts[i]); i--) { | |
if (p == "lib" || p == "enyo") { | |
parts = parts.slice(i+1); | |
break; | |
} | |
} | |
// remove ".." and "." | |
for (i=parts.length-1; (p=parts[i]); i--) { | |
if (p == ".." || p == ".") { | |
parts.splice(i, 1); | |
} | |
} | |
// | |
alias = parts.join("-"); | |
} | |
return { | |
alias: alias, | |
target: target, | |
folder: folder, | |
manifest: manifest | |
}; | |
}, | |
aliasPackage: function(inPath) { | |
var parts = this.decodePackagePath(inPath); | |
// cache manifest path | |
this.manifest = parts.manifest; | |
// cache package info for named packages | |
if (parts.alias) { | |
// debug only | |
/* | |
var old = this.pathResolver.paths[parts.name]; | |
if (old && old != parts.folder) { | |
this.verbose && console.warn("mapping alias [" + parts.name + "] to [" + parts.folder + "] replacing [" + old + "]"); | |
} | |
this.verbose && console.log("mapping alias [" + parts.name + "] to [" + parts.folder + "]"); | |
*/ | |
// | |
// create a path alias for this package | |
this.pathResolver.addPath(parts.alias, parts.target); | |
// | |
// cache current name | |
this.packageName = parts.alias; | |
// cache package information | |
this.packages.push({ | |
name: parts.alias, | |
folder: parts.folder | |
}); | |
} | |
// cache current folder | |
this.packageFolder = parts.folder; | |
}, | |
requirePackage: function(inPath, inBlock) { | |
// cache the interrupted packageFolder | |
inBlock.folder = this.packageFolder; | |
this.aliasPackage(inPath); | |
// cache the name of the package 'inBlock' is loading now | |
inBlock.packageName = this.packageName; | |
// push inBlock on the continuation stack | |
this.stack.push(inBlock); | |
// console/user reporting | |
this.report("loading package", this.packageName); | |
if (this.verbose) { | |
console.group("* start package [" + this.packageName + "]"); | |
} | |
// load the actual package. the package MUST call a continuation function | |
// or the process will halt. | |
this.loadPackage(this.manifest); | |
} | |
}; | |
})(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
enyo.kind({ | |
name: "LoaderTest", | |
kind: enyo.TestSuite, | |
testSingleLoad: function() { | |
enyo.load("tests/loader1.js", enyo.bind(this, | |
function() { | |
if (window.LOADER_TEST === "loader1") { | |
this.finish(); | |
} | |
else { | |
this.finish("callback called before load complete"); | |
} | |
} | |
)); | |
}, | |
testMultipleLoad: function() { | |
enyo.load(["tests/loader2a.js", "tests/loader2b.js"], | |
enyo.bind(this, function() { | |
if (window.LOADER_TEST === "loader2b") { | |
this.finish(); | |
} | |
else { | |
this.finish("callback called before load complete"); | |
} | |
} | |
)); | |
}, | |
testMultipleLoadWith404: function() { | |
enyo.load(["tests/loader2b.js", "tests/loader2a.js", "tests/anotherfilethatdoesnotexist.js"], | |
enyo.bind(this, function(block) { | |
if (window.LOADER_TEST === "loader2a" && block.failed.length === 1 && block.failed[0] === 2) { | |
this.finish(); | |
} | |
else { | |
this.finish("callback called before load complete"); | |
} | |
} | |
)); | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment