import * as ReactDOM from 'react-dom';
import { log } from '@ae.sdp.nd/utils/src/debug';
type ModuleConfig = any;
type ModuleParser = any;
type ModuleRender = any;
export default class Core {
public modules: ModuleConfig[] = [];
public parsers: ModuleParser[] = [];
public renderers: ModuleRender[] = [];
public container: HTMLElement | string = '#app';
public parsedModules: ModuleConfig[];
public renderedModules: React.ReactElement<any>;
// for extends
[key: string]: any;
constructor(options?: any) {
this.setProps(options);
}
public setProps(Eoptions = {}) {
(<any>Object).assign(this, options);
}
public async mount() {
const { container } = this;
const renderedModules = await this.getRenderedModules();
// 挂载
const mountingElement: HTMLElement = typeof container === 'string'
? document.querySelector(container)
: container;
if (process.env.NODE_ENV !== 'production') log('挂载组件树 %O', mountingElement);
ReactDOM.render(renderedModules, mountingElement);
}
/**
* 递归处理模块,顺序执行 parser
* 暴露出去,给动态模块用(Creator,Ref)
* @todo 动态注册的模块,是否需要合并到现有模块树?
* @todo 支持使用 express/koa 类的 next 来控制顺序?
* @example
* // modules: [m1, m2]
* // parsers: [p1, p2]
* p1.pre(m1) -> p2.pre(m1) -> p2.post(m1) -> p1.post(m1)
* p1.pre(m2) -> p2.pre(m2) -> p2.post(m2) -> p1.post(m2)
* // 如果有子模块(深度优先)
* p1.pre(m1) -> p2.pre(m1) -> (子模块流程,同父模块) -> p2.post(m1) -> p1.post(m1)
*/
public async parseModules(modules: any, parent = {}) {
const { parsers } = this;
// 并行处理各模块
return (<any>Promise).all(modules.map(async (mc: any) => {
mc = await parsers.reduce(async (_mc, parser) => {
return parser.pre ? parser.pre(await interopDefaultExports(_mc), parent) : _mc;
}, mc);
// 子模块在 post 之前处理掉
if (mc.modules) {
mc.modules = await this.parseModules(mc.modules, mc);
}
mc = await parsers.reduceRight(async (_mc, parser) => {
return parser.post ? parser.post(await interopDefaultExports(_mc), parent) : _mc;
}, mc);
return mc;
}));
}
private async getRenderedModules() {
if (!this.renderedModules) {
const parsedModules = await this.getParsedModules();
if (process.env.NODE_ENV !== 'production') log('渲染模块树 开始');
this.renderedModules = await this.renderModules(parsedModules);
if (process.env.NODE_ENV !== 'production') log('渲染模块树 结束 %O', this.renderedModules);
}
return this.renderedModules;
}
/**
* 渲染模块树
* @todo 支持使用 express/koa 类的 next 来控制顺序?
* @param {Object[]} parsedModules Parsers 解析后的原始模块树
* @return {any}
*/
private async renderModules(parsedModules) {
const { renderers } = this;
let renderedModules = parsedModules;
// 前置处理
renderedModules = await renderers.reduce(async (modules, renderer) => {
return renderer.pre ? renderer.pre(await modules) : modules;
}, renderedModules);
// 后置处理
renderedModules = await renderers.reduceRight(async (modules, renderer) => {
return renderer.post ? renderer.post(await modules) : modules;
}, renderedModules);
return renderedModules;
}
private async getParsedModules() {
if (!this.parsedModules) {
if (process.env.NODE_ENV !== 'production') log('解析模块树 开始');
this.parsedModules = await this.parseModules(this.modules);
if (process.env.NODE_ENV !== 'production') log('解析模块树 结束 %O', this.parsedModules);
// 清理内存?
this.modules = null;
this.parsers = null;
}
return this.parsedModules;
}
}
// 对于使用 import() 引入的模块,需要转换
async function interopDefaultExports(m: any) {
const _ = await m;
if ('default' in _) {
return _.default;
}
return _;
}
|