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') { if (process.env.ICE_CORE_ERROR_BOUNDARY === 'true') {
console.error('__REMOVED__'); console.error('__REMOVED__');
@ -9,16 +9,21 @@ console.warn('__WARN__');
console.error('__ERROR__'); console.error('__ERROR__');
console.log('process.env.HAHA', process.env.HAHA); console.log('process.env.HAHA', process.env.HAHA);
export default defineAppConfig({ export const getAppData: GetAppData = () => {
app: { return new Promise((resolve) => {
// @ts-expect-error loss tslib dependency resolve({
getData: async () => { title: 'gogogogo',
return {
title: 'gogogo',
auth: { auth: {
admin: true, 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); console.log('render Layout', 'data', data, 'config', config);
return ( return (
<div> <div>
<h1>Layout</h1> <h1>Layout</h1>

View File

@ -1,23 +1,24 @@
import { defineAppConfig } from 'ice'; import { GetAppData, GetAppConfig } from 'ice';
if (process.env.ICE_CORE_ERROR_BOUNDARY) {
console.error('__REMOVED__');
}
console.log('__LOG__'); console.log('__LOG__');
console.warn('__WARN__'); console.warn('__WARN__');
console.error('__ERROR__'); console.error('__ERROR__');
export default defineAppConfig({ export const getAppData: GetAppData = () => {
app: { return new Promise((resolve) => {
// @ts-expect-error loss tslib dependency resolve({
getData: async () => { title: 'gogogogo',
return {
title: 'gogogo',
auth: { auth: {
admin: true, 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); console.log('render Layout', 'data', data, 'config', config);
return ( return (
<div> <div>
<h1>Layout</h1> <h1>Layout</h1>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
import * as React from 'react'; 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); const Context = React.createContext<AppData | undefined>(undefined);
@ -15,7 +15,7 @@ const AppDataProvider = Context.Provider;
/** /**
* Call the getData of app config. * 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__; const hasGlobalLoader = typeof window !== 'undefined' && (window as any).__ICE_DATA_LOADER__;
if (hasGlobalLoader) { if (hasGlobalLoader) {
@ -23,8 +23,8 @@ async function getAppData(appConfig: AppConfig, requestContext: RequestContext):
return await load('__app'); return await load('__app');
} }
if (appConfig?.app.getData) { if (appEntry?.getAppData) {
return await appConfig.app.getData(requestContext); 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 = { const defaultAppConfig: AppConfig = {
app: { 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 { return {
app: { app: {
...defaultAppConfig.app, ...defaultAppConfig.app,
@ -19,5 +23,6 @@ export default function defineAppConfig(appConfig: AppConfig) {
...defaultAppConfig.router, ...defaultAppConfig.router,
...(appConfig.router || {}), ...(appConfig.router || {}),
}, },
...others,
}; };
} }

View File

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

View File

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

View File

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

View File

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