adajs
Version:
Integrated Web Framework
531 lines (483 loc) • 17.3 kB
JavaScript
let {getMappedPath, isFunction, setProp} = require("../../util/helper");
let {MODULEPATH} = require("../../util/const");
class EmptyPersistence {
constructor(context) {
this.context = context;
}
getAll() {
return Promise.resolve({}).then(r => {
this.context._invokeHook("sourceready", r);
return r;
});
}
saveAll(data) {
this.context._invokeHook("sourcepersistence", data);
return Promise.resolve();
}
clean() {
return Promise.resolve();
}
}
class StoragePersistence {
constructor(context) {
this.context = context;
}
getAll() {
let r = {};
try {
r = this.context.window.localStorage.getItem("ada-local-source");
} catch (e) {
}
this.context._invokeHook("sourceready", r);
return Promise.resolve(r);
}
saveAll(data) {
try {
this.context._invokeHook("sourcepersistence", data);
this.context.window.localStorage.setItem("ada-local-source", JSON.stringify(data));
} catch (e) {
}
return Promise.resolve();
}
clean() {
try {
this.context.window.localStorage.removeItem("ada-local-source");
} catch (e) {
}
return Promise.resolve();
}
}
class DatabasePersistence {
constructor(context) {
this.context = context;
this.db = null;
this.info = {
name: "ada",
version: 1,
store: "ada-local-source",
key: "name",
value: "local"
}
}
ready() {
if (!this.db) {
if (!this.context.window.indexedDB) {
this.context.window.indexedDB = this.context.window.mozIndexedDB || this.context.window.webkitIndexedDB;
}
return new Promise(resolve => {
let request = this.context.window.indexedDB.open(this.info.name, this.info.version);
request.onupgradeneeded = () => {
let db = request.result;
if (db.objectStoreNames.contains(this.info.store)) {
db.deleteObjectStore(this.info.store);
}
let store = db.createObjectStore(this.info.store, {keyPath: this.info.key});
store.put({name: this.info.value, data: {}});
};
request.onsuccess = () => {
this.db = request.result;
resolve();
};
request.onerror = () => {
resolve();
};
});
} else {
return Promise.resolve();
}
}
getAll() {
return new Promise(resolve => {
this.ready().then(() => {
let transaction = this.db.transaction([this.info.store], "readwrite");
transaction.onerror = function (event) {
resolve({});
};
let request = transaction.objectStore(this.info.store).get(this.info.value);
request.onerror = () => {
resolve({});
};
request.onsuccess = () => {
resolve(request.result ? request.result.data : {});
};
});
}).then(source => {
this.context._invokeHook("sourceready", source);
return source;
});
}
saveAll(data) {
return new Promise(resolve => {
this.ready().then(() => {
let transaction = this.db.transaction([this.info.store], "readwrite");
transaction.oncomplete = () => {
resolve();
};
transaction.onerror = () => {
resolve();
};
this.context._invokeHook("sourcepersistence", data);
transaction.objectStore(this.info.store).put({
name: this.info.value,
data: data
});
});
});
}
clean() {
return new Promise(resolve => {
this.ready().then(() => {
let transaction = this.db.transaction([this.info.store], "readwrite");
transaction.oncomplete = () => {
resolve();
};
transaction.onerror = () => {
resolve();
};
transaction.objectStore(this.info.store).delete(this.info.value);
});
});
}
}
class ModuleLoader {
constructor(context, loader) {
this.installed = {};
this.moduleFnsLoaded = {};
this._loader = loader;
this._context = context;
}
getExcutor(path, code) {
let suffix = path.split("/").pop().split(".").pop();
let source = code;
if (suffix === "js") {
source = code;
} else if (suffix === "css" || suffix === "scss" || suffix === "less") {
source = `var styles={_linkElement:null,active:function(){var et=document.getElementById("${path}");if(!et){var _a = document.createElement("style");_a.setAttribute("media", "screen");_a.setAttribute("type", "text/css");_a.setAttribute("id","${path.replace(/\//g, "-")}");_a.appendChild(document.createTextNode(${JSON.stringify(code)}));styles._linkElement=_a;document.getElementsByTagName("head")[0].appendChild(_a);}else{et.innerText="";et.appendChild(document.createTextNode(${JSON.stringify(code)}));}}};if (/complete|loaded|interactive/.test(window.document.readyState)) {styles.active();} else {window.document.addEventListener('DOMContentLoaded', function () {styles.active();}, false);}module.exports={isReady:function(){return styles._linkElement!==null;},getElement:function(){return styles._linkElement;},remove:function(){if(styles._linkElement){styles._linkElement.parentNode.removeChild(styles._linkElement);}}};`;
} else if (suffix === "json") {
source = `module.exports=${code}`;
} else if (suffix === "html" || suffix === "text") {
source = `module.exports=JSON.stringify(${code})`;
} else {
source = code;
}
let info = {path, code: source};
this._context._invokeSyncHook("sourceexcute", info);
return new Function("module", "exports", "require", "imports", "babelHelpers", "window", "document", info.code);
}
getModuleDependenceInfo(path, code) {
let paths = [];
code = code.replace(/require\(.*?\)/g, (one) => {
let _path = one.substring(8, one.length - 1).replace(/['|"|`]/g, "").trim();
paths.push(_path);
return `require("${_path}")`;
});
return {code, paths};
}
getModuleDependenceMap(path) {
return this._loader.get(path).then(source => {
let {paths, code: moduleCode} = this.getModuleDependenceInfo(path, source);
let result = {[path]: moduleCode};
let works = [];
paths.map(path => {
if (!this.installed[path]) {
works.push(this.getModuleDependenceMap(path));
}
});
return Promise.all(works).then(maps => {
Object.assign(result, ...maps);
return result;
});
});
}
excute(path) {
if (this.installed[path]) {
return this.installed[path].exports;
}
let module = this.installed[path] = {
path: path,
exports: {}
};
this.moduleFnsLoaded[path].call(module.exports, module, module.exports, (path) => this.excute(path), (path) => {
return this.load(path).then(_result => _result.__esModule ? _result.default : _result).then(result => {
if (isFunction(result)) {
setProp(result, MODULEPATH, path);
}
return result;
});
}, this._loader._context.window.babelHelpers || {}, this._loader._context.window, this._loader._context.document);
return module.exports;
}
load(path) {
let current = this.installed[path];
if (current) {
return Promise.resolve(current.exports);
} else {
return this.getModuleDependenceMap(path).then(moduleMap => {
for (let i in moduleMap) {
this.moduleFnsLoaded[i] = this.getExcutor(i, moduleMap[i]);
}
return this.excute(path);
});
}
}
excuteScript(path) {
return this.load(path);
}
scan(fn) {
if (fn) {
for (let i in this.installed) {
let a = this.installed[i];
let r = fn(i, a.exports);
if (r === false) {
break;
}
}
}
}
scanClass(fn) {
if (fn) {
for (let i in this.installed) {
let a = this.installed[i];
if (!a.exports.__esModule && isFunction(a.exports)) {
if (fn(i, a.exports) === false) {
break;
}
}
for (let t in a.exports) {
let e = a.exports[t];
if (isFunction(e)) {
if (fn(i, e) === false) {
break;
}
}
}
}
}
}
filter(fn) {
let result = [];
if (fn) {
for (let i in this.installed) {
let a = this.installed[i];
let r = fn(i, a.exports.__esModule ? a.exports.default : a.exports);
if (r !== undefined) {
result.push(r);
}
}
}
return result;
}
get(path) {
let a = this.installed[path];
if (a) {
return a.exports.__esModule ? a.exports.default : a.exports;
}
return null;
}
has(path) {
return this.installed[path] !== undefined;
}
set(path, _exports) {
this.installed[path] = {exports: _exports};
return this;
}
}
class SourceQueue {
constructor(loader) {
this.tasks = [];
this.isRunning = false;
this._loader = loader;
}
add(path, fn, error) {
this.tasks.push({path, fn, error});
this.run();
}
run() {
if (!this.isRunning) {
let task = this.tasks.shift();
if (task) {
this.isRunning = true;
this._loader.getSource(task.path).then(info => {
this.isRunning = false;
task.fn(info);
this.run();
}, (err) => {
this.isRunning = false;
task.error(err);
this.run();
});
}
}
}
}
class ActiveSource {
constructor(loader) {
this.cache = [];
this._loader = loader;
}
excute(path) {
if (!this.cache[path]) {
return this._loader.get(path).then(code => {
this.cache[path] = code;
return code;
});
} else {
return Promise.resolve(this.cache[path]);
}
}
has(path) {
return this.cache[path] !== undefined;
}
}
class Loader {
constructor(context) {
this._moduleLoader = new ModuleLoader(context, this);
this._sourceQueue = new SourceQueue(this);
this._activeSource = new ActiveSource(this);
this._source = {};
this._context = context;
this._isinit = false;
let target = null;
if (context.window.indexedDB || context.window.mozIndexedDB || context.window.webkitIndexedDB) {
target = new DatabasePersistence(context);
} else if (context.window.localStorage) {
target = new StoragePersistence(context);
} else {
target = new EmptyPersistence(context);
}
this.persistence = target;
}
get source() {
return this._source;
}
get moduleLoader() {
return this._moduleLoader;
}
get sourceQueue() {
return this._sourceQueue;
}
get activeSource() {
return this._activeSource;
}
get context() {
return this._context;
}
ready() {
if (!this._isinit) {
return this.persistence.getAll().then(source => {
this._isinit = true;
this._source = Object.assign({}, source, this.source);
return this.persistence.saveAll(this.source);
});
} else {
return Promise.resolve();
}
}
get(path) {
return new Promise((resolve, reject) => {
this.sourceQueue.add(path, (info) => {
resolve(info);
}, (err) => {
reject(err);
});
});
}
getSourceCode(path) {
return this.context.request.origin({url: path, method: 'get'}).promise.then(info => info.data);
}
getRealPath(path, ispackage = false) {
let r = path, hash = "", option = this.context.config;
if (ispackage) {
hash = option.sourceMap[path.split(".").shift()];
} else {
hash = option.sourceMap[getMappedPath(path)];
}
if (!option.develop) {
let a = path.split("/");
let b = a.pop();
let c = b.split(".");
a.push(`${hash}.${c[1]}`);
r = a.join("/");
} else {
r = `${path}?h=${new Date().getTime()}`;
}
return `${option.basePath[option.basePath.length - 1] === "/" ? option.basePath : option.basePath + "/"}${r}`;
}
getSourceByHash(filepath, path, rhash) {
let r = 0, option = this.context.config;
let file = Reflect.ownKeys(option.sourceMap.packages).filter(packetname => {
return option.sourceMap.packages[packetname].indexOf(rhash) !== -1;
})[0];
if (file) {
option.sourceMap.packages[file].split("|").forEach(key => {
let name = Reflect.ownKeys(option.sourceMap).filter(name => option.sourceMap[name] === key)[0];
if (!this.source[name] || this.source[name].hash !== key) {
r += 1;
}
});
if (r > 1) {
return this.getSourceCode(`${this.getRealPath(file + ".js", true)}`).then(code => {
let info = JSON.parse(code.substring(11, code.length - 1));
Object.assign(this.source, info);
return this.persistence.saveAll(this.source).then(() => {
return this.source[path].code;
});
});
} else {
return this.getSourceCode(`${this.getRealPath(filepath)}`).then(code => {
this.source[path] = {code, hash: rhash};
return this.persistence.saveAll(this.source).then(() => code);
});
}
} else {
if (rhash) {
return this.getSourceCode(`${this.getRealPath(filepath)}`).then(code => {
this.source[path] = {code, hash: rhash};
return this.persistence.saveAll(this.source).then(() => code);
});
} else {
return this.getSourceCode(`${this.getRealPath(filepath)}`);
}
}
}
getSource(filepath) {
return this.ready().then(() => {
let path = getMappedPath(filepath), option = this.context.config;
if (this.source[path]) {
let hash = this.source[path].hash, rhash = option.sourceMap[path];
if (hash && rhash) {
if (rhash === hash) {
return this.source[path].code;
} else {
return this.getSourceByHash(filepath, path, rhash);
}
} else {
return this.getSourceCode(`${this.getRealPath(filepath)}`);
}
} else {
if (option.sourceMap) {
return this.getSourceByHash(filepath, path, option.sourceMap[path]);
} else {
return this.getSourceCode(`${this.getRealPath(filepath)}`);
}
}
});
}
loadModule(path) {
return this.moduleLoader.excuteScript(path).then(_exports => {
if (_exports.__esModule === true) {
return _exports.default;
}
});
}
loadSource(path) {
return this.activeSource.excute(path);
}
decompress(source = {}) {
if (!this.source) {
this.source = {};
}
Object.assign(this.source, source);
}
}
module.exports = Loader;