mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2025-06-17 10:35:11 +08:00
feat(data-source): 数据源支持ssr
This commit is contained in:
parent
ce0c941bf1
commit
ffd8130269
@ -45,7 +45,7 @@ interface AppOptionsConfig {
|
||||
ua?: string;
|
||||
config?: MApp;
|
||||
platform?: 'editor' | 'mobile' | 'tv' | 'pc';
|
||||
jsEngine?: 'browser' | 'hippy';
|
||||
jsEngine?: 'browser' | 'hippy' | 'nodejs';
|
||||
designWidth?: number;
|
||||
curPage?: Id;
|
||||
useMock?: boolean;
|
||||
|
@ -24,7 +24,7 @@ import type { AppCore, DataSourceSchema, Id, MNode } from '@tmagic/schema';
|
||||
import { compiledCond, compiledNode, DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX, isObject } from '@tmagic/utils';
|
||||
|
||||
import { DataSource, HttpDataSource } from './data-sources';
|
||||
import type { DataSourceManagerData, DataSourceManagerOptions, HttpDataSourceSchema } from './types';
|
||||
import type { DataSourceManagerData, DataSourceManagerOptions } from './types';
|
||||
|
||||
class DataSourceManager extends EventEmitter {
|
||||
private static dataSourceClassMap = new Map<string, typeof DataSource>();
|
||||
@ -44,17 +44,54 @@ class DataSourceManager extends EventEmitter {
|
||||
public data: DataSourceManagerData = {};
|
||||
public useMock?: boolean = false;
|
||||
|
||||
constructor({ app, useMock }: DataSourceManagerOptions) {
|
||||
constructor({ app, useMock, initialData }: DataSourceManagerOptions) {
|
||||
super();
|
||||
|
||||
this.app = app;
|
||||
this.useMock = useMock;
|
||||
|
||||
if (initialData) {
|
||||
this.data = initialData;
|
||||
}
|
||||
|
||||
app.dsl?.dataSources?.forEach((config) => {
|
||||
this.addDataSource(config);
|
||||
});
|
||||
}
|
||||
|
||||
public async init() {
|
||||
await Promise.all(
|
||||
Array.from(this.dataSourceMap).map(async ([, ds]) => {
|
||||
if (ds.isInit) {
|
||||
return;
|
||||
}
|
||||
|
||||
const beforeInit: ((...args: any[]) => any)[] = [];
|
||||
const afterInit: ((...args: any[]) => any)[] = [];
|
||||
|
||||
ds.methods.forEach((method) => {
|
||||
if (typeof method.content !== 'function') return;
|
||||
if (method.timing === 'beforeInit') {
|
||||
beforeInit.push(method.content);
|
||||
}
|
||||
if (method.timing === 'afterInit') {
|
||||
afterInit.push(method.content);
|
||||
}
|
||||
});
|
||||
|
||||
for (const method of beforeInit) {
|
||||
await method({ params: {}, dataSource: ds, app: this.app });
|
||||
}
|
||||
|
||||
await ds.init();
|
||||
|
||||
for (const method of afterInit) {
|
||||
await method({ params: {}, dataSource: ds, app: this.app });
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
public get(id: string) {
|
||||
return this.dataSourceMap.get(id);
|
||||
}
|
||||
@ -62,24 +99,16 @@ class DataSourceManager extends EventEmitter {
|
||||
public async addDataSource(config?: DataSourceSchema) {
|
||||
if (!config) return;
|
||||
|
||||
let ds: DataSource;
|
||||
if (config.type === 'http') {
|
||||
ds = new HttpDataSource({
|
||||
app: this.app,
|
||||
schema: config as HttpDataSourceSchema,
|
||||
request: this.app.request,
|
||||
useMock: this.useMock,
|
||||
});
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const DataSourceClass = DataSourceManager.dataSourceClassMap.get(config.type) || DataSource;
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const DataSourceClass = DataSourceManager.dataSourceClassMap.get(config.type) || DataSource;
|
||||
|
||||
ds = new DataSourceClass({
|
||||
app: this.app,
|
||||
schema: config,
|
||||
useMock: this.useMock,
|
||||
});
|
||||
}
|
||||
const ds = new DataSourceClass({
|
||||
app: this.app,
|
||||
schema: config,
|
||||
request: this.app.request,
|
||||
useMock: this.useMock,
|
||||
initialData: this.data[config.id],
|
||||
});
|
||||
|
||||
this.dataSourceMap.set(config.id, ds);
|
||||
|
||||
@ -89,28 +118,7 @@ class DataSourceManager extends EventEmitter {
|
||||
this.setData(ds);
|
||||
});
|
||||
|
||||
const beforeInit: ((...args: any[]) => any)[] = [];
|
||||
const afterInit: ((...args: any[]) => any)[] = [];
|
||||
|
||||
ds.methods.forEach((method) => {
|
||||
if (typeof method.content !== 'function') return;
|
||||
if (method.timing === 'beforeInit') {
|
||||
beforeInit.push(method.content);
|
||||
}
|
||||
if (method.timing === 'afterInit') {
|
||||
afterInit.push(method.content);
|
||||
}
|
||||
});
|
||||
|
||||
for (const method of beforeInit) {
|
||||
await method({ params: {}, dataSource: ds, app: this.app });
|
||||
}
|
||||
|
||||
await ds.init();
|
||||
|
||||
for (const method of afterInit) {
|
||||
await method({ params: {}, dataSource: ds, app: this.app });
|
||||
}
|
||||
this.init();
|
||||
}
|
||||
|
||||
public setData(ds: DataSource) {
|
||||
@ -226,4 +234,6 @@ class DataSourceManager extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
DataSourceManager.registe('http', HttpDataSource);
|
||||
|
||||
export default DataSourceManager;
|
||||
|
@ -21,18 +21,20 @@ import type { AppCore } from '@tmagic/schema';
|
||||
import { getDepNodeIds, getNodes, replaceChildNode } from '@tmagic/utils';
|
||||
|
||||
import DataSourceManager from './DataSourceManager';
|
||||
import { DataSourceManagerData } from './types';
|
||||
|
||||
/**
|
||||
* 创建数据源管理器
|
||||
* @param dsl DSL
|
||||
* @param httpDataSourceOptions http 数据源配置
|
||||
* @returns DataSourceManager
|
||||
* @param app AppCore
|
||||
* @param useMock 是否使用mock数据
|
||||
* @param initialData 初始化数据,ssr数据可以由此传入
|
||||
* @returns DataSourceManager | undefined
|
||||
*/
|
||||
export const createDataSourceManager = (app: AppCore, useMock?: boolean) => {
|
||||
export const createDataSourceManager = (app: AppCore, useMock?: boolean, initialData?: DataSourceManagerData) => {
|
||||
const { dsl, platform } = app;
|
||||
if (!dsl?.dataSources) return;
|
||||
|
||||
const dataSourceManager = new DataSourceManager({ app, useMock });
|
||||
const dataSourceManager = new DataSourceManager({ app, useMock, initialData });
|
||||
|
||||
if (dsl.dataSources && dsl.dataSourceCondDeps && platform !== 'editor') {
|
||||
getNodes(getDepNodeIds(dsl.dataSourceCondDeps), dsl.items).forEach((node) => {
|
||||
@ -47,22 +49,26 @@ export const createDataSourceManager = (app: AppCore, useMock?: boolean) => {
|
||||
});
|
||||
}
|
||||
|
||||
dataSourceManager.on('change', (sourceId: string) => {
|
||||
const dep = dsl.dataSourceDeps?.[sourceId] || {};
|
||||
const condDep = dsl.dataSourceCondDeps?.[sourceId] || {};
|
||||
// ssr环境下,数据应该是提前准备好的(放到initialData中),不应该发生变化,无需监听
|
||||
// 有initialData不一定是在ssr环境下
|
||||
if (app.jsEngine !== 'nodejs') {
|
||||
dataSourceManager.on('change', (sourceId: string) => {
|
||||
const dep = dsl.dataSourceDeps?.[sourceId] || {};
|
||||
const condDep = dsl.dataSourceCondDeps?.[sourceId] || {};
|
||||
|
||||
const nodeIds = union([...Object.keys(condDep), ...Object.keys(dep)]);
|
||||
const nodeIds = union([...Object.keys(condDep), ...Object.keys(dep)]);
|
||||
|
||||
dataSourceManager.emit(
|
||||
'update-data',
|
||||
getNodes(nodeIds, dsl.items).map((node) => {
|
||||
const newNode = cloneDeep(node);
|
||||
newNode.condResult = dataSourceManager.compliedConds(node);
|
||||
return dataSourceManager.compiledNode(newNode);
|
||||
}),
|
||||
sourceId,
|
||||
);
|
||||
});
|
||||
dataSourceManager.emit(
|
||||
'update-data',
|
||||
getNodes(nodeIds, dsl.items).map((node) => {
|
||||
const newNode = cloneDeep(node);
|
||||
newNode.condResult = dataSourceManager.compliedConds(node);
|
||||
return dataSourceManager.compiledNode(newNode);
|
||||
}),
|
||||
sourceId,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return dataSourceManager;
|
||||
};
|
||||
|
@ -51,15 +51,22 @@ export default class DataSource extends EventEmitter {
|
||||
this.setFields(options.schema.fields);
|
||||
this.setMethods(options.schema.methods || []);
|
||||
|
||||
const defaultData = this.getDefaultData();
|
||||
|
||||
if (this.app.platform === 'editor') {
|
||||
this.mockData = options.schema.mocks?.find((mock) => mock.useInEditor)?.data || defaultData;
|
||||
// 编辑器中有mock使用mock,没有使用默认值
|
||||
this.mockData = options.schema.mocks?.find((mock) => mock.useInEditor)?.data || this.getDefaultData();
|
||||
this.setData(this.mockData);
|
||||
} else if (typeof options.useMock === 'boolean' && options.useMock) {
|
||||
this.mockData = options.schema.mocks?.find((mock) => mock.enable)?.data;
|
||||
// 设置了使用mock就使用mock数据
|
||||
this.mockData = options.schema.mocks?.find((mock) => mock.enable)?.data || this.getDefaultData();
|
||||
this.setData(this.mockData);
|
||||
} else if (!options.initialData) {
|
||||
this.setData(this.getDefaultData());
|
||||
} else {
|
||||
// 在ssr模式下,会将server端获取的数据设置到initialData
|
||||
this.setData(options.initialData);
|
||||
// 设置isInit,防止manager中执行init方法
|
||||
this.isInit = true;
|
||||
}
|
||||
|
||||
this.setData(this.mockData || defaultData);
|
||||
}
|
||||
|
||||
public get id() {
|
||||
|
@ -18,7 +18,7 @@
|
||||
import { HttpOptions, RequestFunction } from '@tmagic/schema';
|
||||
import { getValueByKeyPath } from '@tmagic/utils';
|
||||
|
||||
import { HttpDataSourceOptions, HttpDataSourceSchema } from '@data-source/types';
|
||||
import { DataSourceOptions, HttpDataSourceSchema } from '@data-source/types';
|
||||
|
||||
import DataSource from './Base';
|
||||
|
||||
@ -86,7 +86,7 @@ export default class HttpDataSource extends DataSource {
|
||||
|
||||
#type = 'http';
|
||||
|
||||
constructor(options: HttpDataSourceOptions) {
|
||||
constructor(options: DataSourceOptions<HttpDataSourceSchema>) {
|
||||
const { options: httpOptions } = options.schema;
|
||||
|
||||
super(options);
|
||||
|
@ -2,10 +2,13 @@ import type { AppCore, DataSourceSchema, HttpOptions, RequestFunction } from '@t
|
||||
|
||||
import HttpDataSource from './data-sources/Http';
|
||||
|
||||
export interface DataSourceOptions {
|
||||
schema: DataSourceSchema;
|
||||
export interface DataSourceOptions<T = DataSourceSchema> {
|
||||
schema: T;
|
||||
app: AppCore;
|
||||
initialData?: Record<string, any>;
|
||||
useMock?: boolean;
|
||||
request?: RequestFunction;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface HttpDataSourceSchema extends DataSourceSchema {
|
||||
@ -21,13 +24,11 @@ export interface HttpDataSourceSchema extends DataSourceSchema {
|
||||
afterResponse: string | ((response: any, content: { app: AppCore; dataSource: HttpDataSource }) => any);
|
||||
}
|
||||
|
||||
export interface HttpDataSourceOptions extends DataSourceOptions {
|
||||
schema: HttpDataSourceSchema;
|
||||
request?: RequestFunction;
|
||||
}
|
||||
|
||||
export interface DataSourceManagerOptions {
|
||||
app: AppCore;
|
||||
/** 初始化数据,ssr数据可以由此传入 */
|
||||
initialData?: DataSourceManagerData;
|
||||
/** 是否使用mock数据 */
|
||||
useMock?: boolean;
|
||||
}
|
||||
|
||||
|
@ -34,9 +34,13 @@ export interface HttpOptions {
|
||||
export type RequestFunction = (options: HttpOptions) => Promise<any>;
|
||||
|
||||
export interface AppCore {
|
||||
/** 页面配置描述 */
|
||||
dsl?: MApp;
|
||||
platform?: string;
|
||||
jsEngine?: string;
|
||||
/** 允许平台,editor: 编辑器中,mobile: 手机端,tv: 电视端, pc: 电脑端 */
|
||||
platform?: 'editor' | 'mobile' | 'tv' | 'pc' | string;
|
||||
/** 代码运行环境 */
|
||||
jsEngine?: 'browser' | 'hippy' | 'nodejs' | string;
|
||||
/** 网络请求函数 */
|
||||
request?: RequestFunction;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user