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;
|
ua?: string;
|
||||||
config?: MApp;
|
config?: MApp;
|
||||||
platform?: 'editor' | 'mobile' | 'tv' | 'pc';
|
platform?: 'editor' | 'mobile' | 'tv' | 'pc';
|
||||||
jsEngine?: 'browser' | 'hippy';
|
jsEngine?: 'browser' | 'hippy' | 'nodejs';
|
||||||
designWidth?: number;
|
designWidth?: number;
|
||||||
curPage?: Id;
|
curPage?: Id;
|
||||||
useMock?: boolean;
|
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 { compiledCond, compiledNode, DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX, isObject } from '@tmagic/utils';
|
||||||
|
|
||||||
import { DataSource, HttpDataSource } from './data-sources';
|
import { DataSource, HttpDataSource } from './data-sources';
|
||||||
import type { DataSourceManagerData, DataSourceManagerOptions, HttpDataSourceSchema } from './types';
|
import type { DataSourceManagerData, DataSourceManagerOptions } from './types';
|
||||||
|
|
||||||
class DataSourceManager extends EventEmitter {
|
class DataSourceManager extends EventEmitter {
|
||||||
private static dataSourceClassMap = new Map<string, typeof DataSource>();
|
private static dataSourceClassMap = new Map<string, typeof DataSource>();
|
||||||
@ -44,17 +44,54 @@ class DataSourceManager extends EventEmitter {
|
|||||||
public data: DataSourceManagerData = {};
|
public data: DataSourceManagerData = {};
|
||||||
public useMock?: boolean = false;
|
public useMock?: boolean = false;
|
||||||
|
|
||||||
constructor({ app, useMock }: DataSourceManagerOptions) {
|
constructor({ app, useMock, initialData }: DataSourceManagerOptions) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.app = app;
|
this.app = app;
|
||||||
this.useMock = useMock;
|
this.useMock = useMock;
|
||||||
|
|
||||||
|
if (initialData) {
|
||||||
|
this.data = initialData;
|
||||||
|
}
|
||||||
|
|
||||||
app.dsl?.dataSources?.forEach((config) => {
|
app.dsl?.dataSources?.forEach((config) => {
|
||||||
this.addDataSource(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) {
|
public get(id: string) {
|
||||||
return this.dataSourceMap.get(id);
|
return this.dataSourceMap.get(id);
|
||||||
}
|
}
|
||||||
@ -62,24 +99,16 @@ class DataSourceManager extends EventEmitter {
|
|||||||
public async addDataSource(config?: DataSourceSchema) {
|
public async addDataSource(config?: DataSourceSchema) {
|
||||||
if (!config) return;
|
if (!config) return;
|
||||||
|
|
||||||
let ds: DataSource;
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
if (config.type === 'http') {
|
const DataSourceClass = DataSourceManager.dataSourceClassMap.get(config.type) || DataSource;
|
||||||
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;
|
|
||||||
|
|
||||||
ds = new DataSourceClass({
|
const ds = new DataSourceClass({
|
||||||
app: this.app,
|
app: this.app,
|
||||||
schema: config,
|
schema: config,
|
||||||
useMock: this.useMock,
|
request: this.app.request,
|
||||||
});
|
useMock: this.useMock,
|
||||||
}
|
initialData: this.data[config.id],
|
||||||
|
});
|
||||||
|
|
||||||
this.dataSourceMap.set(config.id, ds);
|
this.dataSourceMap.set(config.id, ds);
|
||||||
|
|
||||||
@ -89,28 +118,7 @@ class DataSourceManager extends EventEmitter {
|
|||||||
this.setData(ds);
|
this.setData(ds);
|
||||||
});
|
});
|
||||||
|
|
||||||
const beforeInit: ((...args: any[]) => any)[] = [];
|
this.init();
|
||||||
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 setData(ds: DataSource) {
|
public setData(ds: DataSource) {
|
||||||
@ -226,4 +234,6 @@ class DataSourceManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DataSourceManager.registe('http', HttpDataSource);
|
||||||
|
|
||||||
export default DataSourceManager;
|
export default DataSourceManager;
|
||||||
|
@ -21,18 +21,20 @@ import type { AppCore } from '@tmagic/schema';
|
|||||||
import { getDepNodeIds, getNodes, replaceChildNode } from '@tmagic/utils';
|
import { getDepNodeIds, getNodes, replaceChildNode } from '@tmagic/utils';
|
||||||
|
|
||||||
import DataSourceManager from './DataSourceManager';
|
import DataSourceManager from './DataSourceManager';
|
||||||
|
import { DataSourceManagerData } from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建数据源管理器
|
* 创建数据源管理器
|
||||||
* @param dsl DSL
|
* @param app AppCore
|
||||||
* @param httpDataSourceOptions http 数据源配置
|
* @param useMock 是否使用mock数据
|
||||||
* @returns DataSourceManager
|
* @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;
|
const { dsl, platform } = app;
|
||||||
if (!dsl?.dataSources) return;
|
if (!dsl?.dataSources) return;
|
||||||
|
|
||||||
const dataSourceManager = new DataSourceManager({ app, useMock });
|
const dataSourceManager = new DataSourceManager({ app, useMock, initialData });
|
||||||
|
|
||||||
if (dsl.dataSources && dsl.dataSourceCondDeps && platform !== 'editor') {
|
if (dsl.dataSources && dsl.dataSourceCondDeps && platform !== 'editor') {
|
||||||
getNodes(getDepNodeIds(dsl.dataSourceCondDeps), dsl.items).forEach((node) => {
|
getNodes(getDepNodeIds(dsl.dataSourceCondDeps), dsl.items).forEach((node) => {
|
||||||
@ -47,22 +49,26 @@ export const createDataSourceManager = (app: AppCore, useMock?: boolean) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
dataSourceManager.on('change', (sourceId: string) => {
|
// ssr环境下,数据应该是提前准备好的(放到initialData中),不应该发生变化,无需监听
|
||||||
const dep = dsl.dataSourceDeps?.[sourceId] || {};
|
// 有initialData不一定是在ssr环境下
|
||||||
const condDep = dsl.dataSourceCondDeps?.[sourceId] || {};
|
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(
|
dataSourceManager.emit(
|
||||||
'update-data',
|
'update-data',
|
||||||
getNodes(nodeIds, dsl.items).map((node) => {
|
getNodes(nodeIds, dsl.items).map((node) => {
|
||||||
const newNode = cloneDeep(node);
|
const newNode = cloneDeep(node);
|
||||||
newNode.condResult = dataSourceManager.compliedConds(node);
|
newNode.condResult = dataSourceManager.compliedConds(node);
|
||||||
return dataSourceManager.compiledNode(newNode);
|
return dataSourceManager.compiledNode(newNode);
|
||||||
}),
|
}),
|
||||||
sourceId,
|
sourceId,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return dataSourceManager;
|
return dataSourceManager;
|
||||||
};
|
};
|
||||||
|
@ -51,15 +51,22 @@ export default class DataSource extends EventEmitter {
|
|||||||
this.setFields(options.schema.fields);
|
this.setFields(options.schema.fields);
|
||||||
this.setMethods(options.schema.methods || []);
|
this.setMethods(options.schema.methods || []);
|
||||||
|
|
||||||
const defaultData = this.getDefaultData();
|
|
||||||
|
|
||||||
if (this.app.platform === 'editor') {
|
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) {
|
} 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() {
|
public get id() {
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
import { HttpOptions, RequestFunction } from '@tmagic/schema';
|
import { HttpOptions, RequestFunction } from '@tmagic/schema';
|
||||||
import { getValueByKeyPath } from '@tmagic/utils';
|
import { getValueByKeyPath } from '@tmagic/utils';
|
||||||
|
|
||||||
import { HttpDataSourceOptions, HttpDataSourceSchema } from '@data-source/types';
|
import { DataSourceOptions, HttpDataSourceSchema } from '@data-source/types';
|
||||||
|
|
||||||
import DataSource from './Base';
|
import DataSource from './Base';
|
||||||
|
|
||||||
@ -86,7 +86,7 @@ export default class HttpDataSource extends DataSource {
|
|||||||
|
|
||||||
#type = 'http';
|
#type = 'http';
|
||||||
|
|
||||||
constructor(options: HttpDataSourceOptions) {
|
constructor(options: DataSourceOptions<HttpDataSourceSchema>) {
|
||||||
const { options: httpOptions } = options.schema;
|
const { options: httpOptions } = options.schema;
|
||||||
|
|
||||||
super(options);
|
super(options);
|
||||||
|
@ -2,10 +2,13 @@ import type { AppCore, DataSourceSchema, HttpOptions, RequestFunction } from '@t
|
|||||||
|
|
||||||
import HttpDataSource from './data-sources/Http';
|
import HttpDataSource from './data-sources/Http';
|
||||||
|
|
||||||
export interface DataSourceOptions {
|
export interface DataSourceOptions<T = DataSourceSchema> {
|
||||||
schema: DataSourceSchema;
|
schema: T;
|
||||||
app: AppCore;
|
app: AppCore;
|
||||||
|
initialData?: Record<string, any>;
|
||||||
useMock?: boolean;
|
useMock?: boolean;
|
||||||
|
request?: RequestFunction;
|
||||||
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HttpDataSourceSchema extends DataSourceSchema {
|
export interface HttpDataSourceSchema extends DataSourceSchema {
|
||||||
@ -21,13 +24,11 @@ export interface HttpDataSourceSchema extends DataSourceSchema {
|
|||||||
afterResponse: string | ((response: any, content: { app: AppCore; dataSource: HttpDataSource }) => any);
|
afterResponse: string | ((response: any, content: { app: AppCore; dataSource: HttpDataSource }) => any);
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HttpDataSourceOptions extends DataSourceOptions {
|
|
||||||
schema: HttpDataSourceSchema;
|
|
||||||
request?: RequestFunction;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DataSourceManagerOptions {
|
export interface DataSourceManagerOptions {
|
||||||
app: AppCore;
|
app: AppCore;
|
||||||
|
/** 初始化数据,ssr数据可以由此传入 */
|
||||||
|
initialData?: DataSourceManagerData;
|
||||||
|
/** 是否使用mock数据 */
|
||||||
useMock?: boolean;
|
useMock?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,9 +34,13 @@ export interface HttpOptions {
|
|||||||
export type RequestFunction = (options: HttpOptions) => Promise<any>;
|
export type RequestFunction = (options: HttpOptions) => Promise<any>;
|
||||||
|
|
||||||
export interface AppCore {
|
export interface AppCore {
|
||||||
|
/** 页面配置描述 */
|
||||||
dsl?: MApp;
|
dsl?: MApp;
|
||||||
platform?: string;
|
/** 允许平台,editor: 编辑器中,mobile: 手机端,tv: 电视端, pc: 电脑端 */
|
||||||
jsEngine?: string;
|
platform?: 'editor' | 'mobile' | 'tv' | 'pc' | string;
|
||||||
|
/** 代码运行环境 */
|
||||||
|
jsEngine?: 'browser' | 'hippy' | 'nodejs' | string;
|
||||||
|
/** 网络请求函数 */
|
||||||
request?: RequestFunction;
|
request?: RequestFunction;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user