mirror of https://github.com/alibaba/ice.git
				
				
				
			refactor: app data loader (#689)
* refactor: getAppData to dataLoader * refactor: merge data loader logic in one file * fix: comments
This commit is contained in:
		
							parent
							
								
									e21fb48509
								
							
						
					
					
						commit
						6ce0835d9b
					
				|  | @ -1,6 +1,5 @@ | |||
| import { defineAppConfig } from 'ice'; | ||||
| import { defineAppConfig, defineDataLoader } from 'ice'; | ||||
| import { isWeb, isNode } from '@uni/env'; | ||||
| import type { GetAppData } from 'ice'; | ||||
| 
 | ||||
| if (process.env.ICE_CORE_ERROR_BOUNDARY === 'true') { | ||||
|   console.error('__REMOVED__'); | ||||
|  | @ -26,10 +25,10 @@ export default defineAppConfig(() => ({ | |||
|   }, | ||||
| })); | ||||
| 
 | ||||
| export const getAppData: GetAppData = () => { | ||||
| export const dataLoader = defineDataLoader(() => { | ||||
|   return new Promise((resolve) => { | ||||
|     resolve({ | ||||
|       title: 'gogogogo', | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| }); | ||||
|  |  | |||
|  | @ -1,5 +1,4 @@ | |||
| import { defineAppConfig } from 'ice'; | ||||
| import type { GetAppData } from 'ice'; | ||||
| import { defineAppConfig, defineDataLoader } from 'ice'; | ||||
| 
 | ||||
| export default defineAppConfig(() => ({ | ||||
|   app: { | ||||
|  | @ -7,7 +6,7 @@ export default defineAppConfig(() => ({ | |||
|   }, | ||||
| })); | ||||
| 
 | ||||
| export const getAppData: GetAppData = () => { | ||||
| export const dataLoader = defineDataLoader(() => { | ||||
|   return new Promise((resolve) => { | ||||
|     resolve({ | ||||
|       title: 'gogogogo', | ||||
|  | @ -16,4 +15,4 @@ export const getAppData: GetAppData = () => { | |||
|       }, | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| }); | ||||
|  |  | |||
|  | @ -1,13 +1,13 @@ | |||
| import { defineAppConfig, type GetAppData } from 'ice'; | ||||
| import { defineAppConfig, defineDataLoader } from 'ice'; | ||||
| 
 | ||||
| export const getAppData: GetAppData = () => { | ||||
| export const dataLoader = defineDataLoader(() => { | ||||
|   return new Promise((resolve) => { | ||||
|     resolve({ | ||||
|       success: true, | ||||
|       id: 34293, | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| }); | ||||
| 
 | ||||
| export const miniappManifest = { | ||||
|   title: 'miniapp test', | ||||
|  |  | |||
|  | @ -1,5 +1,4 @@ | |||
| import { defineAppConfig, Link } from 'ice'; | ||||
| import type { GetAppData } from 'ice'; | ||||
| import { defineAppConfig, defineDataLoader, Link } from 'ice'; | ||||
| import { defineAuthConfig } from '@ice/plugin-auth/esm/types'; | ||||
| 
 | ||||
| export default defineAppConfig(() => ({})); | ||||
|  | @ -21,7 +20,7 @@ export const authConfig = defineAuthConfig((data) => { | |||
|   }; | ||||
| }); | ||||
| 
 | ||||
| export const getAppData: GetAppData = () => { | ||||
| export const dataLoader = defineDataLoader(() => { | ||||
|   return new Promise((resolve) => { | ||||
|     resolve({ | ||||
|       auth: { | ||||
|  | @ -29,4 +28,4 @@ export const getAppData: GetAppData = () => { | |||
|       }, | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| }); | ||||
|  |  | |||
|  | @ -1,13 +1,13 @@ | |||
| import { request as requestAPI } from 'ice'; | ||||
| import { request as requestAPI, defineDataLoader } from 'ice'; | ||||
| import { defineRequestConfig } from '@ice/plugin-request/esm/types'; | ||||
| 
 | ||||
| export async function getAppData() { | ||||
| export const dataLader = defineDataLoader(async () => { | ||||
|   try { | ||||
|     return await requestAPI('/user'); | ||||
|   } catch (err) { | ||||
|     console.log('request error', err); | ||||
|   } | ||||
| } | ||||
| }); | ||||
| 
 | ||||
| export default { | ||||
|   app: { | ||||
|  |  | |||
|  | @ -1,5 +1,4 @@ | |||
| import type { GetAppData } from 'ice'; | ||||
| import { defineAppConfig } from 'ice'; | ||||
| import { defineAppConfig, defineDataLoader } from 'ice'; | ||||
| import { defineStoreConfig } from '@ice/plugin-store/esm/types'; | ||||
| 
 | ||||
| export const store = defineStoreConfig(async (appData) => { | ||||
|  | @ -10,7 +9,7 @@ export const store = defineStoreConfig(async (appData) => { | |||
|   }; | ||||
| }); | ||||
| 
 | ||||
| export const getAppData: GetAppData = () => { | ||||
| export const dataLoader = defineDataLoader(() => { | ||||
|   return new Promise((resolve) => { | ||||
|     resolve({ | ||||
|       user: { | ||||
|  | @ -18,6 +17,6 @@ export const getAppData: GetAppData = () => { | |||
|       }, | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| }); | ||||
| 
 | ||||
| export default defineAppConfig(() => ({})); | ||||
|  |  | |||
|  | @ -1,19 +0,0 @@ | |||
| import { defineAppConfig } from '../disable-data-loader/.ice'; | ||||
| import type { GetAppData } from '../disable-data-loader/.ice'; | ||||
| 
 | ||||
| export default defineAppConfig(() => ({ | ||||
|   app: { | ||||
|     rootId: 'app', | ||||
|   }, | ||||
| })); | ||||
| 
 | ||||
| export const getAppData: GetAppData = () => { | ||||
|   return new Promise((resolve) => { | ||||
|     resolve({ | ||||
|       title: 'gogogogo', | ||||
|       auth: { | ||||
|         admin: true, | ||||
|       }, | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
|  | @ -1,32 +0,0 @@ | |||
| { | ||||
|   "compileOnSave": false, | ||||
|   "buildOnSave": false, | ||||
|   "compilerOptions": { | ||||
|     "baseUrl": "../disable-data-loader", | ||||
|     "outDir": "../disable-data-loader/build", | ||||
|     "module": "esnext", | ||||
|     "target": "es6", | ||||
|     "jsx": "react-jsx", | ||||
|     "moduleResolution": "node", | ||||
|     "allowSyntheticDefaultImports": true, | ||||
|     "lib": ["es6", "dom"], | ||||
|     "sourceMap": true, | ||||
|     "allowJs": true, | ||||
|     "rootDir": "../disable-data-loader", | ||||
|     "forceConsistentCasingInFileNames": true, | ||||
|     "noImplicitReturns": true, | ||||
|     "noImplicitThis": true, | ||||
|     "noImplicitAny": false, | ||||
|     "importHelpers": true, | ||||
|     "strictNullChecks": true, | ||||
|     "suppressImplicitAnyIndexErrors": true, | ||||
|     "noUnusedLocals": true, | ||||
|     "skipLibCheck": true, | ||||
|     "paths": { | ||||
|       "@/*": ["../disable-data-loader/src/*"], | ||||
|       "ice": ["../disable-data-loader/.ice"] | ||||
|     } | ||||
|   }, | ||||
|   "include": ["../disable-data-loader/src", "../disable-data-loader/.ice", "../disable-data-loader/ice.config.*"], | ||||
|   "exclude": ["../disable-data-loader/node_modules", "../disable-data-loader/build", "../disable-data-loader/public"] | ||||
| } | ||||
|  | @ -141,7 +141,7 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt | |||
|   const coreEnvKeys = getCoreEnvKeys(); | ||||
| 
 | ||||
|   const routesInfo = await generateRoutesInfo(rootDir, routesConfig); | ||||
|   const hasExportAppData = (await getFileExports({ rootDir, file: 'src/app' })).includes('getAppData'); | ||||
|   const hasExportAppData = (await getFileExports({ rootDir, file: 'src/app' })).includes('dataLoader'); | ||||
|   const csr = !userConfig.ssr && !userConfig.ssg; | ||||
| 
 | ||||
|   const disableRouter = userConfig?.optimization?.router && routesInfo.routesCount <= 1; | ||||
|  |  | |||
|  | @ -92,7 +92,7 @@ const getWatchEvents = (options: Options): WatchEvent[] => { | |||
|     /src\/app.(js|jsx|ts|tsx)/, | ||||
|     async (event: string) => { | ||||
|       if (event === 'change') { | ||||
|         const hasExportAppData = (await getFileExports({ rootDir, file: 'src/app' })).includes('getAppData'); | ||||
|         const hasExportAppData = (await getFileExports({ rootDir, file: 'src/app' })).includes('dataLoader'); | ||||
|         if (hasExportAppData !== !!cache.get('hasExportAppData')) { | ||||
|           cache.set('hasExportAppData', hasExportAppData ? 'true' : ''); | ||||
|           renderExportsTemplate({ | ||||
|  |  | |||
|  | @ -33,7 +33,7 @@ export default class DataLoaderPlugin { | |||
|   public apply(compiler: Compiler) { | ||||
|     const plugins = this.getAllPlugin(['keepExports']) as PluginData[]; | ||||
| 
 | ||||
|     let keepExports = ['dataLoader', 'getAppData']; | ||||
|     let keepExports = ['dataLoader']; | ||||
|     plugins.forEach(plugin => { | ||||
|       if (plugin.keepExports) { | ||||
|         keepExports = keepExports.concat(plugin.keepExports); | ||||
|  |  | |||
|  | @ -17,7 +17,7 @@ import * as app from '@/app'; | |||
| const loaders = {}; | ||||
| <% } -%> | ||||
| 
 | ||||
| <% if(hasExportAppData) {-%>loaders['__app'] = app.getAppData;<% } -%> | ||||
| <% if(hasExportAppData) {-%>loaders['__app'] = app.dataLoader;<% } -%> | ||||
| 
 | ||||
| <% if(!dataLoaderImport.imports) {-%> | ||||
| let fetcher = (options) => { | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| import * as React from 'react'; | ||||
| import type { AppExport, AppData, RequestContext } from './types.js'; | ||||
| import { callDataLoader } from './dataLoader.js'; | ||||
| 
 | ||||
| const Context = React.createContext<AppData | undefined>(undefined); | ||||
| 
 | ||||
|  | @ -23,9 +24,15 @@ async function getAppData(appExport: AppExport, requestContext?: RequestContext) | |||
|     return await globalLoader.getData('__app'); | ||||
|   } | ||||
| 
 | ||||
|   if (appExport?.getAppData) { | ||||
|     return await appExport.getAppData(requestContext); | ||||
|   if (appExport?.dataLoader) { | ||||
|     return await appExport.dataLoader(requestContext); | ||||
|   } | ||||
| 
 | ||||
|   const loader = appExport?.dataLoader; | ||||
| 
 | ||||
|   if (!loader) return null; | ||||
| 
 | ||||
|   await callDataLoader(loader, requestContext); | ||||
| } | ||||
| 
 | ||||
| export { | ||||
|  |  | |||
|  | @ -1,16 +1,21 @@ | |||
| import type { DataLoaderConfig, RuntimeModules, AppExport, RuntimePlugin, CommonJsRuntime } from './types.js'; | ||||
| import type { DataLoaderConfig, DataLoaderResult, RuntimeModules, AppExport, RuntimePlugin, CommonJsRuntime } from './types.js'; | ||||
| import getRequestContext from './requestContext.js'; | ||||
| import { setFetcher, loadDataByCustomFetcher } from './dataLoaderFetcher.js'; | ||||
| 
 | ||||
| interface Loaders { | ||||
|   [routeId: string]: DataLoaderConfig; | ||||
| } | ||||
| 
 | ||||
| interface Result { | ||||
| interface CachedResult { | ||||
|   value: any; | ||||
|   status: string; | ||||
| } | ||||
| 
 | ||||
| interface LoaderOptions { | ||||
|   fetcher: Function; | ||||
|   runtimeModules: RuntimeModules['statics']; | ||||
|   appExport: AppExport; | ||||
| } | ||||
| 
 | ||||
| export function defineDataLoader(dataLoaderConfig: DataLoaderConfig): DataLoaderConfig { | ||||
|   return dataLoaderConfig; | ||||
| } | ||||
|  | @ -23,12 +28,43 @@ export function defineStaticDataLoader(dataLoaderConfig: DataLoaderConfig): Data | |||
|   return dataLoaderConfig; | ||||
| } | ||||
| 
 | ||||
| const cache = new Map<string, Result>(); | ||||
| /** | ||||
|  * Custom fetcher for load static data loader config. | ||||
|  * Set globally to avoid passing this fetcher too deep. | ||||
|  */ | ||||
| let dataLoaderFetcher; | ||||
| 
 | ||||
| export function setFetcher(customFetcher) { | ||||
|   dataLoaderFetcher = customFetcher; | ||||
| } | ||||
| 
 | ||||
| export function loadDataByCustomFetcher(config) { | ||||
|   return dataLoaderFetcher(config); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Start getData once loader is ready, and set to cache. | ||||
|  * Handle for different dataLoader. | ||||
|  */ | ||||
| function loadInitialData(loaders: Loaders) { | ||||
| export function callDataLoader(dataLoader: DataLoaderConfig, requestContext): DataLoaderResult { | ||||
|   if (Array.isArray(dataLoader)) { | ||||
|     return dataLoader.map(loader => { | ||||
|       return typeof loader === 'object' ? loadDataByCustomFetcher(loader) : loader(requestContext); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   if (typeof dataLoader === 'object') { | ||||
|     return loadDataByCustomFetcher(dataLoader); | ||||
|   } | ||||
| 
 | ||||
|   return dataLoader(requestContext); | ||||
| } | ||||
| 
 | ||||
| const cache = new Map<string, CachedResult>(); | ||||
| 
 | ||||
| /** | ||||
|  * Start getData once data-loader.js is ready in client, and set to cache. | ||||
|  */ | ||||
| function loadInitialDataInClient(loaders: Loaders) { | ||||
|   const context = (window as any).__ICE_APP_CONTEXT__ || {}; | ||||
|   const matchedIds = context.matchedIds || []; | ||||
|   const routesData = context.routesData || {}; | ||||
|  | @ -48,22 +84,7 @@ function loadInitialData(loaders: Loaders) { | |||
| 
 | ||||
|     if (dataLoader) { | ||||
|       const requestContext = getRequestContext(window.location); | ||||
| 
 | ||||
|       let loader; | ||||
| 
 | ||||
|       if (Array.isArray(dataLoader)) { | ||||
|         loader = dataLoader.map(loader => { | ||||
|           if (typeof loader === 'object') { | ||||
|             return loadDataByCustomFetcher(loader); | ||||
|           } | ||||
| 
 | ||||
|           return loader(requestContext); | ||||
|         }); | ||||
|       } else if (typeof dataLoader === 'object') { | ||||
|         return loadDataByCustomFetcher(loader); | ||||
|       } else { | ||||
|         loader = dataLoader(requestContext); | ||||
|       } | ||||
|       const loader = callDataLoader(dataLoader, requestContext); | ||||
| 
 | ||||
|       cache.set(id, { | ||||
|         value: loader, | ||||
|  | @ -73,17 +94,12 @@ function loadInitialData(loaders: Loaders) { | |||
|   }); | ||||
| } | ||||
| 
 | ||||
| interface Options { | ||||
|   fetcher: Function; | ||||
|   runtimeModules: RuntimeModules['statics']; | ||||
|   appExport: AppExport; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Init data loader in client side. | ||||
|  * Load initial data and register global loader. | ||||
|  * In order to load data, JavaScript modules, CSS and other assets in parallel. | ||||
|  */ | ||||
| async function init(loadersConfig: Loaders, options: Options) { | ||||
| async function init(loadersConfig: Loaders, options: LoaderOptions) { | ||||
|   const { | ||||
|     fetcher, | ||||
|     runtimeModules, | ||||
|  | @ -108,7 +124,7 @@ async function init(loadersConfig: Loaders, options: Options) { | |||
|   } | ||||
| 
 | ||||
|   try { | ||||
|     loadInitialData(loadersConfig); | ||||
|     loadInitialDataInClient(loadersConfig); | ||||
|   } catch (error) { | ||||
|     console.error('Load initial data error: ', error); | ||||
|   } | ||||
|  |  | |||
|  | @ -1,14 +0,0 @@ | |||
| /** | ||||
|  * custom fetcher for load static data loader config | ||||
|  * set globally to avoid passing this fetcher too deep | ||||
|  */ | ||||
| 
 | ||||
| let fetcher; | ||||
| 
 | ||||
| export function setFetcher(customFetcher) { | ||||
|   fetcher = customFetcher; | ||||
| } | ||||
| 
 | ||||
| export function loadDataByCustomFetcher(config) { | ||||
|   return fetcher(config); | ||||
| } | ||||
|  | @ -15,7 +15,6 @@ import type { | |||
|   AppProvider, | ||||
|   RouteWrapper, | ||||
|   RenderMode, | ||||
|   GetAppData, | ||||
|   DataLoaderConfig, | ||||
|   RouteWrapperConfig, | ||||
| } from './types.js'; | ||||
|  | @ -94,7 +93,6 @@ export type { | |||
|   AppProvider, | ||||
|   RouteWrapper, | ||||
|   RenderMode, | ||||
|   GetAppData, | ||||
|   DataLoaderConfig, | ||||
|   RunClientAppOptions, | ||||
| }; | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ import { RouteComponent } from './types.js'; | |||
| import type { RouteItem, RouteModules, RouteWrapperConfig, RouteMatch, RequestContext, RoutesConfig, RoutesData, RenderMode } from './types.js'; | ||||
| import RouteWrapper from './RouteWrapper.js'; | ||||
| import { useAppContext } from './AppContext.js'; | ||||
| import { loadDataByCustomFetcher } from './dataLoaderFetcher.js'; | ||||
| import { callDataLoader } from './dataLoader.js'; | ||||
| 
 | ||||
| type RouteModule = Pick<RouteItem, 'id' | 'load'>; | ||||
| 
 | ||||
|  | @ -79,16 +79,8 @@ export async function loadRoutesData( | |||
|         loader = dataLoader; | ||||
|       } | ||||
| 
 | ||||
|       if (Array.isArray(loader)) { | ||||
|         routesData[id] = await Promise.all(loader.map(load => { | ||||
|           if (typeof load === 'object') { | ||||
|             return loadDataByCustomFetcher(load); | ||||
|           } | ||||
| 
 | ||||
|           return load(requestContext); | ||||
|         })); | ||||
|       } else if (loader) { | ||||
|         routesData[id] = await loader(requestContext); | ||||
|       if (loader) { | ||||
|         routesData[id] = await callDataLoader(loader, requestContext); | ||||
|       } | ||||
|     }), | ||||
|   ); | ||||
|  |  | |||
|  | @ -18,7 +18,7 @@ import getRequestContext from './requestContext.js'; | |||
| import getAppConfig from './appConfig.js'; | ||||
| import matchRoutes from './matchRoutes.js'; | ||||
| import DefaultAppRouter from './AppRouter.js'; | ||||
| import { setFetcher } from './dataLoaderFetcher.js'; | ||||
| import { setFetcher } from './dataLoader.js'; | ||||
| 
 | ||||
| export interface RunClientAppOptions { | ||||
|   app: AppExport; | ||||
|  |  | |||
|  | @ -31,12 +31,11 @@ export type RouteConfig<T = {}> = T & { | |||
| export interface AppExport { | ||||
|   default?: AppConfig; | ||||
|   [key: string]: any; | ||||
|   getAppData?: GetAppData; | ||||
|   dataLoader?: DataLoader; | ||||
| } | ||||
| 
 | ||||
| export type GetAppData = (ctx: RequestContext) => (Promise<AppData> | AppData); | ||||
| 
 | ||||
| export type DataLoader = (ctx: RequestContext) => (Promise<RouteData> | RouteData) | RouteData; | ||||
| export type DataLoaderResult = (Promise<RouteData> | RouteData) | RouteData; | ||||
| export type DataLoader = (ctx: RequestContext) => DataLoaderResult; | ||||
| 
 | ||||
| interface StaticDataLoader { | ||||
|   key?: string; | ||||
|  |  | |||
|  | @ -101,7 +101,7 @@ describe('run client app', () => { | |||
|   it('run with static runtime', async () => { | ||||
|     await runClientApp({ | ||||
|       app: { | ||||
|         getAppData: async () => { | ||||
|         dataLoader: async () => { | ||||
|           return { msg: staticMsg }; | ||||
|         }, | ||||
|       }, | ||||
|  | @ -258,7 +258,7 @@ describe('run client app', () => { | |||
|     let executed = false; | ||||
|     await runClientApp({ | ||||
|       app: { | ||||
|         getAppData: async () => { | ||||
|         dataLoader: async () => { | ||||
|           executed = true; | ||||
|           return { msg: '-getAppData' }; | ||||
|         }, | ||||
|  | @ -287,7 +287,7 @@ describe('run client app', () => { | |||
| 
 | ||||
|     await runClientApp({ | ||||
|       app: { | ||||
|         getAppData: async () => { | ||||
|         dataLoader: async () => { | ||||
|           executed = true; | ||||
|           return { msg: 'app' }; | ||||
|         }, | ||||
|  |  | |||
|  | @ -140,12 +140,14 @@ function Home() { | |||
| 
 | ||||
| ### useAppData | ||||
| 
 | ||||
| useAppData 返回应用全局数据,需要搭配 `src/app.ts` 中导出的 getAppData 使用: | ||||
| useAppData 返回应用全局数据,需要搭配 `src/app.ts` 中导出的 `dataLoader` 使用: | ||||
| 
 | ||||
| ```ts title="src/app.ts" | ||||
| export async function getAppData() { | ||||
| import { defineDataLoader } from 'ice'; | ||||
| 
 | ||||
| export const dataLoader = defineDataLoader(() => { | ||||
|   return await fetch('/api/user'); | ||||
| } | ||||
| }) | ||||
| ``` | ||||
| 
 | ||||
| 在任意组件内进行消费: | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue