From ffd8130269ea75a9a6ca1238336fd5e3385cd6c0 Mon Sep 17 00:00:00 2001 From: roymondchen Date: Wed, 15 Nov 2023 15:27:23 +0800 Subject: [PATCH] =?UTF-8?q?feat(data-source):=20=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=BA=90=E6=94=AF=E6=8C=81ssr?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/src/App.ts | 2 +- packages/data-source/src/DataSourceManager.ts | 92 ++++++++++--------- .../src/createDataSourceManager.ts | 44 +++++---- packages/data-source/src/data-sources/Base.ts | 19 ++-- packages/data-source/src/data-sources/Http.ts | 4 +- packages/data-source/src/types.ts | 15 +-- packages/schema/src/index.ts | 8 +- 7 files changed, 106 insertions(+), 78 deletions(-) diff --git a/packages/core/src/App.ts b/packages/core/src/App.ts index bbef6487..ec5f2338 100644 --- a/packages/core/src/App.ts +++ b/packages/core/src/App.ts @@ -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; diff --git a/packages/data-source/src/DataSourceManager.ts b/packages/data-source/src/DataSourceManager.ts index c990dd41..768d1f42 100644 --- a/packages/data-source/src/DataSourceManager.ts +++ b/packages/data-source/src/DataSourceManager.ts @@ -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(); @@ -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; diff --git a/packages/data-source/src/createDataSourceManager.ts b/packages/data-source/src/createDataSourceManager.ts index 51d3ee5a..443aee3f 100644 --- a/packages/data-source/src/createDataSourceManager.ts +++ b/packages/data-source/src/createDataSourceManager.ts @@ -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; }; diff --git a/packages/data-source/src/data-sources/Base.ts b/packages/data-source/src/data-sources/Base.ts index a9ae910f..d651b897 100644 --- a/packages/data-source/src/data-sources/Base.ts +++ b/packages/data-source/src/data-sources/Base.ts @@ -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() { diff --git a/packages/data-source/src/data-sources/Http.ts b/packages/data-source/src/data-sources/Http.ts index 8e93a98c..9de26159 100644 --- a/packages/data-source/src/data-sources/Http.ts +++ b/packages/data-source/src/data-sources/Http.ts @@ -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) { const { options: httpOptions } = options.schema; super(options); diff --git a/packages/data-source/src/types.ts b/packages/data-source/src/types.ts index e7305592..bab79d69 100644 --- a/packages/data-source/src/types.ts +++ b/packages/data-source/src/types.ts @@ -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 { + schema: T; app: AppCore; + initialData?: Record; 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; } diff --git a/packages/schema/src/index.ts b/packages/schema/src/index.ts index 38c361d9..5bd42abd 100644 --- a/packages/schema/src/index.ts +++ b/packages/schema/src/index.ts @@ -34,9 +34,13 @@ export interface HttpOptions { export type RequestFunction = (options: HttpOptions) => Promise; 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; }