@v4fire/client
Version:
V4Fire client core library
287 lines (230 loc) • 5.63 kB
text/typescript
/*!
* V4Fire Client Core
* https://github.com/V4Fire/Client
*
* Released under the MIT license
* https://github.com/V4Fire/Client/blob/master/LICENSE
*/
/**
* [[include:super/i-block/modules/module-loader/README.md]]
* @packageDocumentation
*/
import Friend from 'super/i-block/modules/friend';
import { cache, cachedModules } from 'super/i-block/modules/module-loader/const';
import type { Module } from 'super/i-block/modules/module-loader/interface';
export * from 'super/i-block/modules/module-loader/interface';
let
resolve,
cursor;
/**
* Class to load the dynamic dependencies of the component
*/
export default class ModuleLoader extends Friend {
/**
* Number of added modules
*/
get size(): number {
return cachedModules.length;
}
/**
* Returns true if the specified module already exists in the cache
* @param module
*/
has(module: Module): boolean {
return module.id != null ? cache.has(module.id) : false;
}
/**
* Adds the specified modules to a queue to load.
* The method returns the number of added modules in the cache.
*
* @param modules
*/
add(...modules: Module[]): number {
for (let i = 0; i < modules.length; i++) {
const
module = modules[i];
if (module.id != null) {
if (this.has(module)) {
continue;
}
cache.set(module.id, module);
}
if (Object.isFunction(resolve)) {
resolve(module);
}
}
return cache.size;
}
/**
* Loads the specified modules.
* The method returns false if there is nothing to load.
*/
load(...modules: Module[]): CanPromise<boolean> {
const
toLoad = <Array<Promise<unknown>>>[];
for (let i = 0; i < modules.length; i++) {
const
module = modules[i],
val = this.resolveModule(module);
if (Object.isPromise(val)) {
toLoad.push(val);
if (module.id != null) {
cache.set(module.id, module);
}
cachedModules.push(module);
}
}
if (toLoad.length === 0) {
return false;
}
return this.async.promise(Promise.all(toLoad).then(() => true));
}
[Symbol.iterator](): IterableIterator<CanArray<Module>> {
return this.values();
}
/**
* Returns an iterator to iterate the added modules.
* If there is no provided id to check, the iterator will never stop.
* The method should be used with [[AsyncRender]].
*
* @param [ids] - module identifiers to filter
*/
values(...ids: unknown[]): IterableIterator<CanArray<Module>> {
const
{async: $a} = this;
let
iterPos = 0,
done = false,
cachedLength = cachedModules.length;
const
idsSet = new Set(ids),
subTasks = <Array<Promise<Module>>>[],
subValues = <Array<CanPromise<Module>>>[];
const iterator = {
[Symbol.iterator]: () => iterator,
next: () => {
if (done) {
return {
done: true,
value: undefined
};
}
const initModule = (module: Module) => {
cursor = undefined;
resolve = undefined;
if (ids.length > 0 && idsSet.has(module.id)) {
idsSet.delete(module.id);
done = idsSet.size === 0;
} else if (cachedLength !== cachedModules.length) {
iterPos++;
cachedLength = cachedModules.length;
}
return this.resolveModule(module);
};
if (ids.length > 0) {
for (let o = idsSet.values(), el = o.next(); !el.done; el = o.next()) {
const
id = el.value,
module = cache.get(id);
if (module != null) {
idsSet.delete(id);
const
val = initModule(module);
if (Object.isPromise(val)) {
subTasks.push(val);
}
subValues.push(val);
if (idsSet.size === 0) {
done = true;
return {
done: false,
value: subTasks.length > 0 ?
$a.promise(Promise.all(subTasks).then(() => Promise.allSettled(subValues))) :
subValues
};
}
}
}
} else if (iterPos !== cachedLength) {
return {
done: false,
value: initModule(cachedModules[iterPos++])
};
}
if (cursor != null) {
if (ids.length > 0) {
return {
done: false,
value: cursor.value.then((module) => {
if (done) {
return module;
}
return iterator.next().value;
})
};
}
return cursor;
}
cursor = {
done: false,
value: $a.promise(new Promise((r) => {
resolve = (module: Module) => {
const
val = initModule(module);
if (Object.isPromise(val)) {
return val.then(r);
}
r(val);
};
}))
};
return cursor;
}
};
return iterator;
}
/**
* Resolves the specified module: if the module already exists in the cache, the method simply returns it.
* Otherwise, the module will be loaded.
*
* @param module
*/
protected resolveModule(module: Module): CanPromise<Module> {
const
{async: $a} = this;
if (module.id != null) {
module = cache.get(module.id) ?? module;
}
let
promise;
switch (module.status) {
case 'loaded':
break;
case 'pending':
promise = module.promise;
break;
default: {
module.status = 'pending';
module.promise = $a.promise(new Promise((r) => {
if (module.wait) {
r($a.promise(module.wait()).then(module.load.bind(module)));
} else {
r(module.load());
}
}));
promise = module.promise
.then(() => {
module.status = 'loaded';
})
.catch((err) => {
stderr(err);
module.status = 'failed';
});
}
}
if (promise != null) {
return promise.then(() => module);
}
return module;
}
}