feat(data-source): 数据源支持ssr

This commit is contained in:
roymondchen 2023-11-15 15:27:23 +08:00
parent ce0c941bf1
commit ffd8130269
7 changed files with 106 additions and 78 deletions

View File

@ -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;

View File

@ -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,51 +44,28 @@ 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 get(id: string) {
return this.dataSourceMap.get(id);
public async init() {
await Promise.all(
Array.from(this.dataSourceMap).map(async ([, ds]) => {
if (ds.isInit) {
return;
}
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;
ds = new DataSourceClass({
app: this.app,
schema: config,
useMock: this.useMock,
});
}
this.dataSourceMap.set(config.id, ds);
this.data[ds.id] = ds.data;
ds.on('change', () => {
this.setData(ds);
});
const beforeInit: ((...args: any[]) => any)[] = [];
const afterInit: ((...args: any[]) => any)[] = [];
@ -111,6 +88,37 @@ class DataSourceManager extends EventEmitter {
for (const method of afterInit) {
await method({ params: {}, dataSource: ds, app: this.app });
}
}),
);
}
public get(id: string) {
return this.dataSourceMap.get(id);
}
public async addDataSource(config?: DataSourceSchema) {
if (!config) return;
// eslint-disable-next-line @typescript-eslint/naming-convention
const DataSourceClass = DataSourceManager.dataSourceClassMap.get(config.type) || DataSource;
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);
this.data[ds.id] = ds.data;
ds.on('change', () => {
this.setData(ds);
});
this.init();
}
public setData(ds: DataSource) {
@ -226,4 +234,6 @@ class DataSourceManager extends EventEmitter {
}
}
DataSourceManager.registe('http', HttpDataSource);
export default DataSourceManager;

View File

@ -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,6 +49,9 @@ export const createDataSourceManager = (app: AppCore, useMock?: boolean) => {
});
}
// ssr环境下数据应该是提前准备好的放到initialData中不应该发生变化无需监听
// 有initialData不一定是在ssr环境下
if (app.jsEngine !== 'nodejs') {
dataSourceManager.on('change', (sourceId: string) => {
const dep = dsl.dataSourceDeps?.[sourceId] || {};
const condDep = dsl.dataSourceCondDeps?.[sourceId] || {};
@ -63,6 +68,7 @@ export const createDataSourceManager = (app: AppCore, useMock?: boolean) => {
sourceId,
);
});
}
return dataSourceManager;
};

View File

@ -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() {

View File

@ -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);

View File

@ -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;
}

View File

@ -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;
}