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