///
///
///
declare var require: any;
declare var md: any;
if (typeof require !== "undefined") {
md = require("module").Module;
}
class AmdLoader {
public static globalVar: any = {};
public static moduleProgress: (name: string, modules: {[key: string]: Module}, status: "done" | "loading") => void;
public static moduleLoader:
(packageName: string, url: string, success: () => void, failed: (error: any) => void) => void;
public static instance: AmdLoader = new AmdLoader();
public static current: Module = null;
public root: Module = null;
public defaultUrl: string = null;
public currentStack: Module[] = [];
// public pendingModules: Module[] = [];
// public resolverStack: Module[] = [];
// only useful in node environment
public nodeModules: Module[] = [];
public modules: { [key: string]: Module } = {};
public pathMap: { [key: string]: IPackage } = {};
public enableMock: boolean;
public define: any;
private mockTypes: MockType[] = [];
private lastTimeout: any = null;
private tail: Module;
private dirty: boolean = false;
public register(
packages: string[],
modules: string[]): void {
for (const iterator of packages) {
if (!this.pathMap[iterator]) {
this.map(iterator, "/");
}
}
for (const iterator of modules) {
this.get(iterator);
}
}
public setupRoot(root: string, url: string): void {
for (const key in this.pathMap) {
if (this.pathMap.hasOwnProperty(key)) {
const moduleUrl: string = key === root ? url : `${url}/node_modules/${key}`;
this.map(key, moduleUrl);
}
}
this.defaultUrl = `${url}/node_modules/`;
}
public registerModule(name: string, moduleExports: { [key: string]: any }): void {
const m: Module = this.get(name);
m.package.url = "/";
m.exports = { __esModule: true, ... moduleExports };
m.loader = Promise.resolve();
m.resolver = Promise.resolve(m.exports);
m.isLoaded = true;
m.isResolved = true;
}
public setup(name: string): void {
const jsModule: Module = this.get(name);
// tslint:disable-next-line:ban-types
const define: Function = this.define;
jsModule.loader = Promise.resolve();
AmdLoader.current = jsModule;
if (define) {
define();
}
if (jsModule.exportVar) {
jsModule.exports = AmdLoader.globalVar[jsModule.exportVar];
}
this.push(jsModule);
jsModule.isLoaded = true;
setTimeout(() => {
this.loadDependencies(jsModule);
}, 1);
}
public loadDependencies(m: Module): void {
this.resolveModule(m).catch((e) => {
// tslint:disable-next-line:no-console
console.error(e);
});
if (m.dependencies.length) {
const all = m.dependencies.map(async (m1) => {
if (m1.isResolved) { return; }
await this.import(m1);
});
Promise.all(all).catch((e) => {
// tslint:disable-next-line:no-console
console.error(e);
}).then(() => {
m.resolve();
});
} else {
m.resolve();
}
this.queueResolveModules(1);
}
public replace(type: any, name: string, mock: boolean): void {
if (mock && !this.enableMock) {
return;
}
const peek: Module = this.currentStack.length ? this.currentStack[this.currentStack.length - 1] : undefined;
const rt: MockType = new MockType(peek, type, name, mock);
this.mockTypes.push(rt);
}
public resolveType(type: any): any {
const t: MockType = this.mockTypes.find((tx) => tx.type === type);
return t ? t.replaced : type;
}
public map(
packageName: string,
packageUrl: string,
type: ("amd" | "global") = "amd",
exportVar?: string
): IPackage {
// ignore map if it exists already...
let existing: IPackage = this.pathMap[packageName];
if (existing) {
existing.url = packageUrl;
existing.exportVar = exportVar;
existing.type = type;
return existing;
}
existing = {
name: packageName,
url: packageUrl,
type,
exportVar,
version: ""
};
if (packageName === "reflect-metadata") {
type = "global";
}
this.pathMap[packageName] = existing;
return existing;
}
public resolveSource(name: string, defExt: string = ".js"): string {
try {
if (/^((\/)|((http|https)\:\/\/))/i.test(name)) {
// console.log(`ResolveSource fail: ${name}`);
return name;
}
let path: string = null;
for (const key in this.pathMap) {
if (this.pathMap.hasOwnProperty(key)) {
const packageName: string = key;
if (name.startsWith(packageName)) {
path = this.pathMap[key].url;
if (name.length !== packageName.length) {
if (name[packageName.length] !== "/") {
continue;
}
name = name.substr(packageName.length + 1);
} else {
return path;
}
if (path.endsWith("/")) {
path = path.substr(0, path.length - 1);
}
path = path + "/" + name;
if (defExt && !path.endsWith(defExt)) {
path = path + defExt;
}
return path;
}
}
}
return name;
} catch (e) {
// tslint:disable-next-line:no-console
console.error(`Failed to resolve ${name} with error ${JSON.stringify(e)}`);
// tslint:disable-next-line:no-console
console.error(e);
}
}
public resolveRelativePath(name: string, currentPackage: string): string {
if (name.charAt(0) !== ".") {
return name;
}
const tokens: string[] = name.split("/");
const currentTokens: string[] = currentPackage.split("/");
currentTokens.pop();
while (tokens.length) {
const first: string = tokens[0];
if (first === "..") {
currentTokens.pop();
tokens.splice(0, 1);
continue;
}
if (first === ".") {
tokens.splice(0, 1);
}
break;
}
return `${currentTokens.join("/")}/${tokens.join("/")}`;
}
public getPackageVersion(name: string): ({
packageName: string,
version: string,
name: string
}) {
let [scope, packageName] = name.split("/", 3);
let version: string = "";
if (scope[0] !== "@") {
packageName = scope;
scope = "";
} else {
scope += "/";
}
const versionTokens: string[] = packageName.split("@");
if (versionTokens.length > 1) {
// remove version and map it..
version = versionTokens[1];
name = name.replace("@" + version, "");
}
packageName = scope + packageName;
return { packageName, version, name };
}
public get(name1: string): Module {
let module: Module = this.modules[name1];
if (!module) {
// strip '@' version info
const { packageName, version, name } = this.getPackageVersion(name1);
module = new Module(name);
this.modules[name1] = module;
module.package = this.pathMap[packageName] ||
(this.pathMap[packageName] = {
type: "amd",
name: packageName,
version,
url: this.defaultUrl ?
(this.defaultUrl + packageName) : undefined
});
module.url = this.resolveSource(name);
if (!module.url) {
if (typeof require === "undefined") {
throw new Error(`No url mapped for ${name}`);
}
}
module.require = (n: string) => {
const an: string = this.resolveRelativePath(n, module.name);
const resolvedModule: Module = this.get(an);
return resolvedModule.getExports();
};
module.require.resolve = (n: string) => this.resolveRelativePath(n, module.name);
this.modules[name] = module;
}
return module;
}
public async import(name: string | Module): Promise {
if (typeof require !== "undefined") {
return Promise.resolve(require(name));
}
const module: Module = typeof name === "object" ? name as Module : this.get(name);
await this.load(module);
const e = await this.resolveModule(module);
return e;
}
public load(module: Module): Promise {
if (module.loader) {
return module.loader;
}
this.push(module);
module.loader = new Promise((resolve, reject) => {
AmdLoader.moduleLoader(module.name, module.url, () => {
try {
AmdLoader.current = module;
if (AmdLoader.instance.define) {
AmdLoader.instance.define();
AmdLoader.instance.define = null;
}
if (module.exportVar) {
module.exports = AmdLoader.globalVar[module.exportVar];
}
if (AmdLoader.moduleProgress) {
AmdLoader.moduleProgress(module.name, this.modules , "loading");
}
module.isLoaded = true;
setTimeout(() => {
this.loadDependencies(module);
}, 1);
resolve();
} catch (e) {
reject(e);
}
}, (error) => {
reject(error);
});
});
return module.loader;
}
public resolveModule(module: Module): Promise {
if (module.resolver) {
return module.resolver;
}
module.resolver = this._resolveModule(module);
return module.resolver;
}
public remove(m: Module): void {
if (this.tail === m) {
this.tail = m.previous;
}
if (m.next) {
m.next.previous = m.previous;
}
if (m.previous) {
m.previous.next = m.next;
}
m.next = null;
m.previous = null;
this.dirty = true;
this.queueResolveModules();
}
public queueResolveModules(n: number = 1): void {
if (this.lastTimeout) {
// clearTimeout(this.lastTimeout);
// this.lastTimeout = null;
return;
}
this.lastTimeout = setTimeout(() => {
this.lastTimeout = 0;
this.resolvePendingModules();
}, n);
}
private resolvePendingModules(): void {
if (!this.tail) {
return;
}
this.dirty = false;
// first resolve modules without any
// dependencies
const pending: Module[] = [];
let m = this.tail;
while (m) {
if (!m.dependencies.length) {
m.resolve();
} else {
pending.push(m);
}
m = m.previous;
}
if (this.dirty) {
this.dirty = false;
return;
}
for (const iterator of pending) {
iterator.resolve();
}
if (this.dirty) {
this.dirty = false;
return;
}
if (this.tail) {
this.queueResolveModules();
}
}
private push(m: Module): void {
if (this.tail) {
m.previous = this.tail;
this.tail.next = m;
}
this.tail = m;
}
private async _resolveModule(module: Module): Promise {
if (!this.root) {
this.root = module;
}
await new Promise((resolve, reject) => {
module.dependencyHooks = [resolve, reject];
});
// tslint:disable-next-line:typedef
const exports = module.getExports();
// load requested dependencies for mock or abstract injects
const pendingList: MockType[] = this.mockTypes.filter((t) => !t.loaded );
if (pendingList.length) {
for (const iterator of pendingList) {
iterator.loaded = true;
}
const tasks = pendingList.map(async (iterator) => {
const containerModule: Module = iterator.module;
const resolvedName: string = this.resolveRelativePath(iterator.moduleName, containerModule.name);
const im: Module = this.get(resolvedName);
im.ignoreModule = module;
const ex: any = await this.import(im);
const type: any = ex[iterator.exportName];
iterator.replaced = type;
});
await Promise.all(tasks);
}
const setHooks: Promise = new Promise((resolve, reject) => {
module.resolveHooks = [resolve, reject];
});
await setHooks;
if (this.root === module) {
this.root = null;
AmdLoader.moduleProgress(null, this.modules, "done");
}
module.isResolved = true;
return exports;
}
}
const a: AmdLoader = AmdLoader.instance;
a.map("global", "/", "global");
a.registerModule("global/document", { default: document });
a.registerModule("global/window", { default: typeof window !== "undefined" ? window : global });
AmdLoader.moduleLoader = (name, url, success, error) => {
const script: HTMLScriptElement = document.createElement("script");
script.type = "text/javascript";
script.src = url;
const s: any = script as any;
script.onload = s.onreadystatechange = () => {
if ((s.readyState && s.readyState !== "complete" && s.readyState !== "loaded")) {
return;
}
script.onload = s.onreadystatechange = null;
success();
};
script.onerror = (e) => { error(e); };
document.body.appendChild(script);
};
AmdLoader.moduleProgress = (() => {
if (!document) {
return (name, p) => {
// tslint:disable-next-line:no-console
console.log(`${name} ${p}%`);
};
}
const progressDiv: HTMLDivElement = document.createElement("div");
progressDiv.className = "web-atoms-progress-div";
const style: CSSStyleDeclaration = progressDiv.style;
style.position = "absolute";
style.margin = "auto";
style.width = "200px";
style.height = "100px";
style.top = style.right = style.left = style.bottom = "5px";
style.borderStyle = "solid";
style.borderWidth = "1px";
style.borderColor = "#A0A0A0";
style.borderRadius = "5px";
style.padding = "5px";
style.textAlign = "left";
style.verticalAlign = "bottom";
const progressLabel: HTMLPreElement = document.createElement("pre");
progressDiv.appendChild(progressLabel);
progressLabel.style.color = "#A0A0A0";
const ps: CSSStyleDeclaration = progressLabel.style;
ps.position = "absolute";
ps.left = "5px";
ps.bottom = "0";
function ready(): void {
document.body.appendChild(progressDiv);
}
function completed(): void {
document.removeEventListener( "DOMContentLoaded", completed );
window.removeEventListener( "load", completed );
ready();
}
if ( document.readyState === "complete" ||
// tslint:disable-next-line:no-string-literal
( document.readyState !== "loading" && !document.documentElement["doScroll"] ) ) {
window.setTimeout( ready );
} else {
document.addEventListener( "DOMContentLoaded", completed );
window.addEventListener( "load", completed );
}
return (name, n, status) => {
if (status === "done") {
progressDiv.style.display = "none";
return;
} else {
progressDiv.style.display = "block";
}
name = name.split("/").pop();
progressLabel.textContent = name;
};
})();