Skip to content

Instantly share code, notes, and snippets.

Created January 7, 2013 18:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save anonymous/4477439 to your computer and use it in GitHub Desktop.
Save anonymous/4477439 to your computer and use it in GitHub Desktop.
// 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"
});
(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);
}
};
})();
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