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

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 { 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,51 +44,28 @@ 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 get(id: string) { public async init() {
return this.dataSourceMap.get(id); 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 beforeInit: ((...args: any[]) => any)[] = [];
const afterInit: ((...args: any[]) => any)[] = []; const afterInit: ((...args: any[]) => any)[] = [];
@ -111,6 +88,37 @@ class DataSourceManager extends EventEmitter {
for (const method of afterInit) { for (const method of afterInit) {
await method({ params: {}, dataSource: ds, app: this.app }); 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) { public setData(ds: DataSource) {
@ -226,4 +234,6 @@ class DataSourceManager extends EventEmitter {
} }
} }
DataSourceManager.registe('http', HttpDataSource);
export default DataSourceManager; export default DataSourceManager;

View File

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

View File

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

View File

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

View File

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

View File

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