UNPKG

@v4fire/client

Version:

V4Fire client core library

287 lines (230 loc) • 5.63 kB
/*! * 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; } }