refactor: getAppData (#183)

* feat: data loader

* refactor: put load logic to runtime

* feat: pass matched ids

* refactor: reuse initial context

* feat: generate loader template

* fix: name with slash

* fix: name with slash

* feat: throw error

* feat: preload app data

* refactor: initail context to request context

* chore: add comment

* chore: add comment

* fix: type

* fix: merge conflict

* refactor: get app data

* fix: conflict

* fix: csr app data

Co-authored-by: luhc228 <luhengchang228@126.com>
This commit is contained in:
水澜 2022-04-29 20:23:31 +08:00 committed by ClarkXia
parent 5af5199972
commit 27599db870
18 changed files with 92 additions and 64 deletions

View File

@ -1,4 +1,4 @@
import { defineAppConfig } from 'ice';
import { GetAppData, GetAppConfig } from 'ice';
if (process.env.ICE_CORE_ERROR_BOUNDARY === 'true') {
console.error('__REMOVED__');
@ -9,16 +9,21 @@ console.warn('__WARN__');
console.error('__ERROR__');
console.log('process.env.HAHA', process.env.HAHA);
export default defineAppConfig({
app: {
// @ts-expect-error loss tslib dependency
getData: async () => {
return {
title: 'gogogo',
auth: {
admin: true,
},
};
export const getAppData: GetAppData = () => {
return new Promise((resolve) => {
resolve({
title: 'gogogogo',
auth: {
admin: true,
},
});
});
};
export const getAppConfig: GetAppConfig = (appData) => {
return {
auth: {
initialAuth: appData?.auth,
},
},
});
};
};

View File

@ -6,7 +6,6 @@ export default () => {
console.log('render Layout', 'data', data, 'config', config);
return (
<div>
<h1>Layout</h1>

View File

@ -1,23 +1,24 @@
import { defineAppConfig } from 'ice';
if (process.env.ICE_CORE_ERROR_BOUNDARY) {
console.error('__REMOVED__');
}
import { GetAppData, GetAppConfig } from 'ice';
console.log('__LOG__');
console.warn('__WARN__');
console.error('__ERROR__');
export default defineAppConfig({
app: {
// @ts-expect-error loss tslib dependency
getData: async () => {
return {
title: 'gogogo',
auth: {
admin: true,
},
};
export const getAppData: GetAppData = () => {
return new Promise((resolve) => {
resolve({
title: 'gogogogo',
auth: {
admin: true,
},
});
});
};
export const getAppConfig: GetAppConfig = (appData) => {
return {
auth: {
initialAuth: appData?.auth,
},
},
});
};
};

View File

@ -6,7 +6,6 @@ export default () => {
console.log('render Layout', 'data', data, 'config', config);
return (
<div>
<h1>Layout</h1>

View File

@ -1,3 +1,3 @@
import { defineAppConfig } from 'ice';
export default defineAppConfig({});
export function getAppConfig() {
return {};
}

View File

@ -1,8 +1,8 @@
import { dataLoader } from '@ice/runtime';
import appConfig from '@/app';
import { getAppData } from '@/app';
<%- loaders %>
loaders['__app'] = appConfig?.app.getData;
loaders['__app'] = getAppData;
dataLoader.init(loaders);

View File

@ -1,11 +1,11 @@
import { runClientApp } from '@ice/runtime';
import appConfig from '@/app';
import * as app from '@/app';
import runtimeModules from './runtimeModules';
import routes from './routes';
import Document from '@/document';
runClientApp({
appConfig,
app,
runtimeModules,
routes,
Document

View File

@ -5,7 +5,7 @@ process.env.<%= key %> = __process.env.<%= key %>__;
<% }) %>
import * as runtime from '@ice/runtime/server';
import appConfig from '@/app';
import * as app from '@/app';
import runtimeModules from './runtimeModules';
import Document from '@/document';
import assetsManifest from './assets-manifest.json';
@ -13,7 +13,7 @@ import routes from './routes';
export async function renderToHTML(requestContext, documentOnly) {
return await runtime.renderToHTML(requestContext, {
appConfig,
app,
assetsManifest,
routes,
runtimeModules,
@ -24,7 +24,7 @@ export async function renderToHTML(requestContext, documentOnly) {
export async function renderToResponse(requestContext, documentOnly) {
runtime.renderToResponse(requestContext, {
appConfig,
app,
assetsManifest,
routes,
runtimeModules,

View File

@ -30,7 +30,6 @@ export {
};
export {
defineAppConfig,
useAppData,
useData,
useConfig,

View File

@ -1,5 +1,7 @@
import type { AppConfig as DefaultAppConfig } from '@ice/runtime';
export type { GetAppConfig, GetAppData } from '@ice/runtime';
<%- configTypes.imports %>
<% if (configTypes.imports) {%>
@ -12,4 +14,4 @@ interface ExtendsAppConfig extends DefaultAppConfig {
export type AppConfig = ExtendsAppConfig;
<% } else { %>
export type AppConfig = DefaultAppConfig;
<% } %>
<% } %>

View File

@ -5,9 +5,9 @@ import type { InjectProps } from './Auth';
import type { AuthConfig, AuthType } from './types';
const runtime: RuntimePlugin = ({ appContext, useConfig, addProvider, addWrapper }) => {
const { appConfig, appData = {} } = appContext;
const initialAuth = appData.auth || {};
const { appConfig } = appContext;
const authConfig: AuthConfig = appConfig.auth || {};
const initialAuth = authConfig.initialAuth || {};
const AuthProviderWrapper: AppProvider = ({ children }) => {
const [state, setState] = React.useState<AuthType>(initialAuth);

View File

@ -1,6 +1,9 @@
import type * as React from 'react';
import type { RouteConfig } from '@ice/types';
export interface AuthConfig {
initialAuth: {
[auth: string]: boolean;
};
NoAuthFallback?: React.ComponentType<{routeConfig: RouteConfig}>;
}

View File

@ -1,5 +1,5 @@
import * as React from 'react';
import type { AppConfig, AppData, RequestContext } from './types';
import type { AppEntry, AppData, RequestContext } from './types';
const Context = React.createContext<AppData | undefined>(undefined);
@ -15,7 +15,7 @@ const AppDataProvider = Context.Provider;
/**
* Call the getData of app config.
*/
async function getAppData(appConfig: AppConfig, requestContext: RequestContext): Promise<AppData> {
async function getAppData(appEntry: AppEntry, requestContext: RequestContext): Promise<AppData> {
const hasGlobalLoader = typeof window !== 'undefined' && (window as any).__ICE_DATA_LOADER__;
if (hasGlobalLoader) {
@ -23,8 +23,8 @@ async function getAppData(appConfig: AppConfig, requestContext: RequestContext):
return await load('__app');
}
if (appConfig?.app.getData) {
return await appConfig.app.getData(requestContext);
if (appEntry?.getAppData) {
return await appEntry.getAppData(requestContext);
}
}

View File

@ -1,4 +1,4 @@
import type { AppConfig } from './types';
import type { AppConfig, AppData, AppEntry } from './types';
const defaultAppConfig: AppConfig = {
app: {
@ -9,7 +9,11 @@ const defaultAppConfig: AppConfig = {
},
};
export default function defineAppConfig(appConfig: AppConfig) {
export default function getAppConfig(appEntry: AppEntry, appData: AppData): AppConfig {
const appConfig = appEntry.getAppConfig(appData);
const { app, router, ...others } = appConfig;
return {
app: {
...defaultAppConfig.app,
@ -19,5 +23,6 @@ export default function defineAppConfig(appConfig: AppConfig) {
...defaultAppConfig.router,
...(appConfig.router || {}),
},
...others,
};
}

View File

@ -32,8 +32,9 @@ import type {
ServerContext,
AppProvider,
RouteWrapper,
GetAppData,
GetAppConfig,
} from './types.js';
import defineAppConfig from './defineAppConfig.js';
import { matchRoutes } from './routes.js';
import dataLoader from './dataLoader.js';
@ -51,7 +52,6 @@ export {
Links,
Scripts,
Main,
defineAppConfig,
// react-router-dom API
Link,
Outlet,
@ -73,4 +73,6 @@ export type {
ServerContext,
AppProvider,
RouteWrapper,
GetAppData,
GetAppConfig,
};

View File

@ -8,15 +8,16 @@ import App from './App.js';
import { AppContextProvider } from './AppContext.js';
import { AppDataProvider, getAppData } from './AppData.js';
import type {
AppContext, AppConfig, RouteItem, AppRouterProps, RoutesData, RoutesConfig,
AppContext, AppEntry, RouteItem, AppRouterProps, RoutesData, RoutesConfig,
RouteWrapperConfig, RuntimeModules, RouteMatch, ComponentWithChildren,
} from './types';
import { loadRouteModules, loadRoutesData, getRoutesConfig, matchRoutes, filterMatchesToLoad } from './routes.js';
import { updateRoutesConfig } from './routesConfig.js';
import getRequestContext from './requestContext.js';
import getAppConfig from './appConfig.js';
interface RunClientAppOptions {
appConfig: AppConfig;
app: AppEntry;
routes: RouteItem[];
runtimeModules: RuntimeModules;
Document: ComponentWithChildren<{}>;
@ -24,7 +25,7 @@ interface RunClientAppOptions {
export default async function runClientApp(options: RunClientAppOptions) {
const {
appConfig,
app,
routes,
runtimeModules,
Document,
@ -39,9 +40,11 @@ export default async function runClientApp(options: RunClientAppOptions) {
const requestContext = getRequestContext(window.location);
if (!appData) {
appData = await getAppData(appConfig, requestContext);
appData = await getAppData(app, requestContext);
}
const appConfig = getAppConfig(app, appData);
if (!routesData) {
routesData = await loadRoutesData(matches, requestContext);
}

View File

@ -7,6 +7,7 @@ import Runtime from './runtime.js';
import App from './App.js';
import { AppContextProvider } from './AppContext.js';
import { AppDataProvider, getAppData } from './AppData.js';
import getAppConfig from './appConfig.js';
import { DocumentContextProvider } from './Document.js';
import { loadRouteModules, loadRoutesData, getRoutesConfig, matchRoutes } from './routes.js';
import { piperToString, renderToNodeStream } from './server/streamRender.js';
@ -14,13 +15,13 @@ import { createStaticNavigator } from './server/navigator.js';
import type { NodeWritablePiper } from './server/streamRender.js';
import type {
AppContext, RouteItem, ServerContext,
AppConfig, RuntimePlugin, CommonJsRuntime, AssetsManifest,
AppEntry, RuntimePlugin, CommonJsRuntime, AssetsManifest,
ComponentWithChildren,
} from './types';
import getRequestContext from './requestContext.js';
interface RenderOptions {
appConfig: AppConfig;
app: AppEntry;
assetsManifest: AssetsManifest;
routes: RouteItem[];
runtimeModules: (RuntimePlugin | CommonJsRuntime)[];
@ -157,7 +158,7 @@ export async function renderServerEntry(
): Promise<RenderResult> {
const {
assetsManifest,
appConfig,
app,
runtimeModules,
routes,
Document,
@ -165,7 +166,8 @@ export async function renderServerEntry(
const requestContext = getRequestContext(location, serverContext);
const appData = await getAppData(appConfig, requestContext);
const appData = await getAppData(app, requestContext);
const appConfig = getAppConfig(app, appData);
const routesData = await loadRoutesData(matches, requestContext);
const routesConfig = getRoutesConfig(matches, routesData);
@ -232,13 +234,14 @@ export function renderDocument(matches, options: RenderOptions): RenderResult {
const {
routes,
assetsManifest,
appConfig,
app,
Document,
} = options;
// renderDocument needn't to load routesData and appData.
const appData = null;
const routesData = null;
const appConfig = getAppConfig(app, appData);
const routesConfig = getRoutesConfig(matches, {});
const appContext: AppContext = {

View File

@ -10,7 +10,6 @@ type AppLifecycle = 'onShow' | 'onHide' | 'onPageNotFound' | 'onShareAppMessage'
type App = Partial<{
strict?: boolean;
addProvider?: ({ children }: { children: ReactNode }) => ReactNode;
getData?: GetData;
} & Record<AppLifecycle, VoidFunction>>;
export type AppData = any;
@ -28,6 +27,14 @@ export interface RouteConfig {
auth?: string[];
}
export interface AppEntry {
getAppConfig?: GetAppConfig;
getAppData?: GetAppData;
}
export type GetAppData = (ctx: RequestContext) => Promise<AppData> | AppData;
export type GetAppConfig = (appData: AppData) => AppConfig;
// app.getData & route.getData
export type GetData = (ctx: RequestContext) => Promise<RouteData> | RouteData;
// route.getConfig