UNPKG

@jmodule/client

Version:
304 lines (270 loc) 9.57 kB
// 全局环境仅保留一份 JModuleManager,用于多 JModule, Resource 管理 import { ModuleStatus } from './config'; import { ModuleHook } from './hook'; import { JModule } from './module'; import { Resource } from './resource'; import { createDocument } from './utils/fakeDocument'; import { patchCreateElement } from './utils/patchCreateElement'; import { defineModule } from './utils/defineModule'; import { enableDevtool } from './utils/enableDevtool' import { eventToPromise } from './utils/eventToPromise'; import { Matcher } from './utils/matcher'; import { DepResolver } from './depResolver'; const originDocument = document; const originCreateElement = originDocument.createElement.bind(originDocument); const initialConfig = (((window as any).JModule || {}) as any).config || {}; export class JModuleManager extends ModuleHook { private static resourceCache: { [id: string]: Resource } = {}; private static jmoduleCache: { [id: string]: JModule } = {}; private static resourceUrlAndModuleKeyMap: Record<string, string[]> = {}; private static fileMapCache: Record<string, [string, string | undefined]> = {}; private static fileListCache: string[] = []; private static moduleExportsCache: Record<string, any> = {}; private static moduleExports: Record<string, any> = {}; static nextJModuleId = 0; static defaultJModule?: typeof JModule; static createDocument(options: { sourceUrl: string, currentUrl: string }) { return createDocument(originDocument, originCreateElement, options); } /** * 读取全局初始化配置 * * @return {Object|undefined} */ static getInitialConfig() { return initialConfig; } /** * @ignore */ static setFileMapCache(key: string, val: [string, string]) { const oldTarget = this.fileMapCache[key]?.[0]; if (oldTarget && oldTarget !== val[0]) { const errorMessage = `建立异步组件索引 "${key}" 出现冲突,可能会导致异步组件加载异常`; console.error(errorMessage, val); } return this.fileMapCache[key] = val; } /** * @ignore */ static appendFileList(url: string) { this.fileListCache.push(url); } /** * @ignore */ static findResolvedUrlByUrl(urlWithPublicPath: string) { const file = this.fileListCache.find(item => item && urlWithPublicPath.includes(item)); if (!file || !this.fileMapCache[file]) { return [urlWithPublicPath]; } return this.fileMapCache[file]; } /** * 根据资源地址查找对应的 resourceUrl * * @param {String} url * @returns {String|undefined} */ static getResourceUrlByUrl(url: string | undefined | null) { if (!url) { return undefined; } if (this.resourceCache[url]) { return url; } let resourceUrl: string | undefined = undefined; for (const scriptNode of document.scripts) { if (scriptNode.src === url) { resourceUrl = scriptNode.getAttribute('data-jmodule-from') || undefined; break; } } return resourceUrl; } /** * 读取/设置 url 与 Resource 之间的关系 * @param {String} url * @param {Resource|null} resource * @returns {Resource|undefined} */ static resource(url: string, resource?: Resource|null): Resource|undefined { if (resource === null) { delete this.resourceCache[url]; return; } if (resource) { this.resourceCache[url] = resource; return resource; } // 优先执行精确匹配, 否则当作异步资源先匹配resourceUrl再查找 return this.resourceCache[url]; } /** * 读取/设置 moduleKey 与 Module 之间的关系 * @param {String} moduleKey * @param {JModule|null} instance * @returns {JModule|undefined} */ static jmodule(moduleKey: string, instance?: JModule|null): JModule|undefined { if (instance === null) { delete this.jmoduleCache[moduleKey]; return; } if (instance) { this.jmoduleCache[moduleKey] = instance; return instance; } return this.jmoduleCache[moduleKey]; } /** * 获取已注册的应用列表 * @readOnly */ static get registeredModules(): JModule[] { return Object.keys(this.jmoduleCache) .map(key => this.jmodule(key)) .filter(item => !!item) as JModule[]; } /** * 记录 resourceUrl 与 moduleKey 之间的映射关系 * @ignore * @param {String} resourceUrl * @param {String} moduleKey */ static mapResourceUrlAndModuleKey(resourceUrl: string, moduleKey: string) { this.resourceUrlAndModuleKeyMap[resourceUrl] = [ ...(this.resourceUrlAndModuleKeyMap[resourceUrl] || []), moduleKey, ]; } /** * 基于 resourceUrl 查找关联的 Modules * @param resourceUrl * @returns { Array<JModule> } */ static getModulesByResourceUrl(resourceUrl: string): JModule[] { return (this.resourceUrlAndModuleKeyMap[resourceUrl] || []) .map(key => this.jmodule(key)) .filter(item => !!item) as JModule[]; } /** * 读取 moduleKey 对应的 JModule 构造函数 * @param moduleKey * @returns { typeof JModule } */ static getJModuleConstructor(moduleKey: string) { const module = this.jmodule(moduleKey); if (!module) { throw new Error(`Module ${moduleKey} not found`); } return module.constructor; } static registerJModule(JModuleConstructor: typeof JModule) { if (!this.defaultJModule) { // 首次注册的设为默认值,为兼容以前的 window.JModule this.defaultJModule = JModuleConstructor; } return this.nextJModuleId++; } /** * 存储模块暴露的组件 * * @ignore * @param {String} moduleKey * @param {any} data */ static cacheModuleExport(moduleKey: string, data: any): void { Object.assign(this.moduleExports, { [moduleKey]: data }); } /** * 等待模块加载完成 * * @param {String} moduleKey * @example * await JModuleManager.waitModuleComplete(moduleKey); * @return {Promise<Module>} */ static async waitModuleComplete(moduleKey: string): Promise<JModule> { const targetModule = this.jmodule(moduleKey); return targetModule && targetModule.status === ModuleStatus.done ? Promise.resolve(targetModule) : eventToPromise<JModule>(`module.${moduleKey}.${ModuleStatus.done}`); } // JModule 兼容功能 /** * 引用其它模块暴露的功能 * * @param {String} namespace * @example * JModule.require('pipeline.models.PipelineApp') * .then((PipelineApp) => { * // do something * }); * @return {Promise<var>} */ static async require(namespace: string): Promise<any> { const path = namespace.split('.'); await this.waitModuleComplete(path[0]); if (!this.moduleExportsCache[namespace]) { const res = path.reduce((obj, key) => (obj || {})[key], this.moduleExports); this.moduleExportsCache[namespace] = res; } return this.moduleExportsCache[namespace]; } /** * 定义模块 * @param {String} moduleKey 定义模块唯一标识 * @param {Object} metadata 定义模块 * @param {Function} [metadata.init<jModuleInstance>] 初始化函数,自动调用 * @param {Array<moduleKey>} [metadata.imports] 依赖的模块 * @param {Object} [metadata.exports] 对外暴露的功能 * @example * JModule.define('pipeline', { * init(module) {}, * routes, * imports: [], * exports: {}, * }); */ static define = defineModule.bind(this); /** * 暴露平台功能给模块使用 * 与 JModuleManager.defaultJModule 数据一致 * @param {object} obj 需要暴露的对象 * @example * JModuleManager.export({ * $platform: { * utils, event, router, * }, * $node_modules: { * vue: Vue, * }, * }, { scope: 'default' }); */ static export(obj = {}, matcher: Record<string, string | number> = {}) { new Matcher(matcher).cache(obj); } /** * 引用平台暴露的对象 * * @param {String} namespace * @param {Object} config 通过编译工具注入的相关环境参数 * @return {var} */ static import<T>(namespace = '', config: Record<string, string | number> = {}): T { // 用于导入平台接口 const matchedExports = new Matcher(config).getCache(); const res = namespace.split('.').reduce((res, key) => (res || {})[key], matchedExports); if (res && res instanceof DepResolver) { return res.resolve(config); } return res; } } if (!(window as any).JModuleManager) { (window as any).JModuleManager = JModuleManager; patchCreateElement(originCreateElement); enableDevtool(); } export default (window as any).JModuleManager as typeof JModuleManager;