mirror of https://github.com/alibaba/ice.git
feat: getAppData (#375)
* feat: getAppData * fix: remove dead code * fix: should remove default export for app entry too * docs: app data
This commit is contained in:
parent
7661e2967f
commit
00f75371ed
|
|
@ -1,6 +1,7 @@
|
|||
import { defineAppConfig } from 'ice';
|
||||
import { defineAuthConfig } from '@ice/plugin-auth/esm/types';
|
||||
import { isWeb, isNode } from '@uni/env';
|
||||
import type { GetAppData } from 'ice';
|
||||
|
||||
if (process.env.ICE_CORE_ERROR_BOUNDARY === 'true') {
|
||||
console.error('__REMOVED__');
|
||||
|
|
@ -33,3 +34,14 @@ export default defineAppConfig({
|
|||
rootId: 'app',
|
||||
},
|
||||
});
|
||||
|
||||
export const getAppData: GetAppData = () => {
|
||||
return new Promise((resolve) => {
|
||||
resolve({
|
||||
title: 'gogogogo',
|
||||
auth: {
|
||||
admin: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
import { Meta, Title, Links, Main, Scripts } from 'ice';
|
||||
import { Meta, Title, Links, Main, Scripts, useAppData } from 'ice';
|
||||
import type { AppData } from 'ice';
|
||||
|
||||
function Document() {
|
||||
const appData = useAppData<AppData>();
|
||||
|
||||
return (
|
||||
<html>
|
||||
<head>
|
||||
|
|
@ -10,6 +13,11 @@ function Document() {
|
|||
<Meta />
|
||||
<Title />
|
||||
<Links />
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `console.log('${appData?.title}')`,
|
||||
}}
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<Main />
|
||||
|
|
|
|||
|
|
@ -1,19 +1,22 @@
|
|||
import { Suspense, lazy } from 'react';
|
||||
import { Link, useData, useConfig } from 'ice';
|
||||
import { Link, useData, useAppData, useConfig } from 'ice';
|
||||
// not recommended but works
|
||||
import { useAppContext } from '@ice/runtime';
|
||||
import { useRequest } from 'ahooks';
|
||||
import type { AppData } from 'ice';
|
||||
import styles from './index.module.css';
|
||||
|
||||
const Bar = lazy(() => import('../components/bar'));
|
||||
|
||||
export default function Home(props) {
|
||||
const appContext = useAppContext();
|
||||
const appData = useAppData<AppData>();
|
||||
const data = useData();
|
||||
const config = useConfig();
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
console.log('render Home', props);
|
||||
console.log('get AppData', appData);
|
||||
console.log('get AppContext', appContext);
|
||||
console.log('render Home', 'data', data, 'config', config);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
import { dataLoader } from '@ice/runtime';
|
||||
import { getAppData } from '@/app';
|
||||
|
||||
<%- loaders %>
|
||||
|
||||
loaders['__app'] = getAppData;
|
||||
|
||||
dataLoader.init(loaders);
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ export {
|
|||
|
||||
export {
|
||||
defineAppConfig,
|
||||
useAppData,
|
||||
useData,
|
||||
useConfig,
|
||||
Meta,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { AppConfig as DefaultAppConfig } from '@ice/runtime';
|
||||
import type { AppConfig as DefaultAppConfig, GetAppData, AppData } from '@ice/runtime';
|
||||
|
||||
<%- configTypes.imports -%>
|
||||
|
||||
|
|
@ -13,3 +13,8 @@ export type AppConfig = ExtendsAppConfig;
|
|||
<% } else { -%>
|
||||
export type AppConfig = DefaultAppConfig;
|
||||
<% } -%>
|
||||
|
||||
export {
|
||||
GetAppData,
|
||||
AppData
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import * as React from 'react';
|
||||
import type { AppExport, AppData, RequestContext } from './types.js';
|
||||
|
||||
const Context = React.createContext<AppData | undefined>(undefined);
|
||||
|
||||
Context.displayName = 'AppDataContext';
|
||||
|
||||
function useAppData <T = AppData>(): T {
|
||||
const value = React.useContext(Context);
|
||||
return value;
|
||||
}
|
||||
|
||||
const AppDataProvider = Context.Provider;
|
||||
|
||||
/**
|
||||
* Call the getData of app config.
|
||||
*/
|
||||
async function getAppData(appExport: AppExport, requestContext: RequestContext): Promise<AppData> {
|
||||
const hasGlobalLoader = typeof window !== 'undefined' && (window as any).__ICE_DATA_LOADER__;
|
||||
|
||||
if (hasGlobalLoader) {
|
||||
const load = (window as any).__ICE_DATA_LOADER__;
|
||||
return await load('__app');
|
||||
}
|
||||
|
||||
if (appExport?.getAppData) {
|
||||
return await appExport.getAppData(requestContext);
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
getAppData,
|
||||
useAppData,
|
||||
AppDataProvider,
|
||||
};
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import * as React from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useAppContext } from './AppContext.js';
|
||||
import { useAppData } from './AppData.js';
|
||||
import { getMeta, getTitle, getLinks, getScripts } from './routesConfig.js';
|
||||
import type { AppContext, RouteMatch, AssetsManifest } from './types.js';
|
||||
import getCurrentRoutePath from './utils/getCurrentRoutePath.js';
|
||||
|
|
@ -64,6 +65,7 @@ export function Links() {
|
|||
|
||||
export function Scripts() {
|
||||
const { routesData, routesConfig, matches, assetsManifest, documentOnly, routeModules, basename } = useAppContext();
|
||||
const appData = useAppData();
|
||||
|
||||
const routeScripts = getScripts(matches, routesConfig);
|
||||
const pageAssets = getPageAssets(matches, assetsManifest);
|
||||
|
|
@ -75,6 +77,7 @@ export function Scripts() {
|
|||
const routePath = getCurrentRoutePath(matches);
|
||||
|
||||
const appContext: AppContext = {
|
||||
appData,
|
||||
routesData,
|
||||
routesConfig,
|
||||
assetsManifest,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import Runtime from './runtime.js';
|
|||
import App from './App.js';
|
||||
import runClientApp from './runClientApp.js';
|
||||
import { useAppContext } from './AppContext.js';
|
||||
import { useAppData } from './AppData.js';
|
||||
import { useData, useConfig } from './RouteContext.js';
|
||||
import {
|
||||
Meta,
|
||||
|
|
@ -34,6 +35,7 @@ import type {
|
|||
AppProvider,
|
||||
RouteWrapper,
|
||||
RenderMode,
|
||||
GetAppData,
|
||||
} from './types.js';
|
||||
import dataLoader from './dataLoader.js';
|
||||
import getAppConfig, { defineAppConfig } from './appConfig.js';
|
||||
|
|
@ -45,6 +47,7 @@ export {
|
|||
App,
|
||||
runClientApp,
|
||||
useAppContext,
|
||||
useAppData,
|
||||
useData,
|
||||
useConfig,
|
||||
Meta,
|
||||
|
|
@ -77,4 +80,5 @@ export type {
|
|||
AppProvider,
|
||||
RouteWrapper,
|
||||
RenderMode,
|
||||
GetAppData,
|
||||
};
|
||||
|
|
@ -6,6 +6,7 @@ import { createHistorySingle } from './utils/history-single.js';
|
|||
import Runtime from './runtime.js';
|
||||
import App from './App.js';
|
||||
import { AppContextProvider } from './AppContext.js';
|
||||
import { AppDataProvider, getAppData } from './AppData.js';
|
||||
import type {
|
||||
AppContext, AppExport, RouteItem, AppRouterProps, RoutesData, RoutesConfig,
|
||||
RouteWrapperConfig, RuntimeModules, RouteMatch, RouteModules, AppConfig, DocumentComponent,
|
||||
|
|
@ -38,6 +39,7 @@ export default async function runClientApp(options: RunClientAppOptions) {
|
|||
} = options;
|
||||
const appContextFromServer: AppContext = (window as any).__ICE_APP_CONTEXT__ || {};
|
||||
let {
|
||||
appData,
|
||||
routesData,
|
||||
routesConfig,
|
||||
assetsManifest,
|
||||
|
|
@ -47,6 +49,10 @@ export default async function runClientApp(options: RunClientAppOptions) {
|
|||
|
||||
const requestContext = getRequestContext(window.location);
|
||||
|
||||
if (!appData) {
|
||||
appData = await getAppData(app, requestContext);
|
||||
}
|
||||
|
||||
const appConfig = getAppConfig(app);
|
||||
|
||||
const basename = basenameFromServer || defaultBasename;
|
||||
|
|
@ -68,6 +74,7 @@ export default async function runClientApp(options: RunClientAppOptions) {
|
|||
appExport: app,
|
||||
routes,
|
||||
appConfig,
|
||||
appData,
|
||||
routesData,
|
||||
routesConfig,
|
||||
assetsManifest,
|
||||
|
|
@ -172,6 +179,7 @@ function BrowserEntry({
|
|||
routesConfig: initialRoutesConfig,
|
||||
routeModules: initialRouteModules,
|
||||
basename,
|
||||
appData,
|
||||
} = appContext;
|
||||
|
||||
const [historyState, setHistoryState] = useState<HistoryState>({
|
||||
|
|
@ -225,12 +233,14 @@ function BrowserEntry({
|
|||
|
||||
return (
|
||||
<AppContextProvider value={appContext}>
|
||||
<App
|
||||
action={action}
|
||||
location={location}
|
||||
navigator={history}
|
||||
{...rest}
|
||||
/>
|
||||
<AppDataProvider value={appData}>
|
||||
<App
|
||||
action={action}
|
||||
location={location}
|
||||
navigator={history}
|
||||
{...rest}
|
||||
/>
|
||||
</AppDataProvider>
|
||||
</AppContextProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import type { Location } from 'history';
|
|||
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 } from './routes.js';
|
||||
|
|
@ -14,6 +15,7 @@ import { createStaticNavigator } from './server/navigator.js';
|
|||
import type { NodeWritablePiper } from './server/streamRender.js';
|
||||
import type {
|
||||
AppContext, RouteItem, ServerContext,
|
||||
AppData,
|
||||
AppExport, RuntimePlugin, CommonJsRuntime, AssetsManifest,
|
||||
RouteMatch,
|
||||
RequestContext,
|
||||
|
|
@ -132,6 +134,13 @@ async function doRender(serverContext: ServerContext, renderOptions: RenderOptio
|
|||
const location = getLocation(req.url);
|
||||
|
||||
const requestContext = getRequestContext(location, serverContext);
|
||||
|
||||
let appData;
|
||||
// don't need to execute getAppData in CSR
|
||||
if (!documentOnly) {
|
||||
appData = await getAppData(app, requestContext);
|
||||
}
|
||||
|
||||
const appConfig = getAppConfig(app);
|
||||
const matches = matchRoutes(routes, location, serverOnlyBasename || basename);
|
||||
const routePath = getCurrentRoutePath(matches);
|
||||
|
|
@ -156,6 +165,7 @@ async function doRender(serverContext: ServerContext, renderOptions: RenderOptio
|
|||
matches,
|
||||
location,
|
||||
appConfig,
|
||||
appData,
|
||||
routeModules,
|
||||
basename,
|
||||
routePath,
|
||||
|
|
@ -181,6 +191,7 @@ interface renderServerEntry {
|
|||
matches: RouteMatch[];
|
||||
location: Location;
|
||||
appConfig: AppConfig;
|
||||
appData: AppData;
|
||||
routeModules: RouteModules;
|
||||
routePath: string;
|
||||
basename?: string;
|
||||
|
|
@ -196,6 +207,7 @@ async function renderServerEntry(
|
|||
matches,
|
||||
location,
|
||||
appConfig,
|
||||
appData,
|
||||
renderOptions,
|
||||
routeModules,
|
||||
basename,
|
||||
|
|
@ -217,6 +229,7 @@ async function renderServerEntry(
|
|||
appExport,
|
||||
assetsManifest,
|
||||
appConfig,
|
||||
appData,
|
||||
routesData,
|
||||
routesConfig,
|
||||
matches,
|
||||
|
|
@ -248,9 +261,11 @@ async function renderServerEntry(
|
|||
|
||||
const element = (
|
||||
<AppContextProvider value={appContext}>
|
||||
<DocumentContextProvider value={documentContext}>
|
||||
<Document pagePath={routePath} />
|
||||
</DocumentContextProvider>
|
||||
<AppDataProvider value={appData}>
|
||||
<DocumentContextProvider value={documentContext}>
|
||||
<Document pagePath={routePath} />
|
||||
</DocumentContextProvider>
|
||||
</AppDataProvider>
|
||||
</AppContextProvider>
|
||||
);
|
||||
|
||||
|
|
@ -286,12 +301,14 @@ function renderDocument(
|
|||
} = options;
|
||||
|
||||
const routesData = null;
|
||||
const appData = null;
|
||||
const appConfig = getAppConfig(app);
|
||||
const routesConfig = getRoutesConfig(matches, {}, routeModules);
|
||||
|
||||
const appContext: AppContext = {
|
||||
assetsManifest,
|
||||
appConfig,
|
||||
appData,
|
||||
routesData,
|
||||
routesConfig,
|
||||
matches,
|
||||
|
|
|
|||
|
|
@ -31,8 +31,11 @@ export interface RouteConfig {
|
|||
export interface AppExport {
|
||||
default?: AppConfig;
|
||||
[key: string]: any;
|
||||
getAppData?: GetAppData;
|
||||
}
|
||||
|
||||
export type GetAppData = (ctx: RequestContext) => Promise<AppData> | AppData;
|
||||
|
||||
// app.getData & route.getData
|
||||
export type GetData = (ctx: RequestContext) => Promise<RouteData> | RouteData;
|
||||
export type GetServerData = (ctx: RequestContext) => Promise<RouteData> | RouteData;
|
||||
|
|
@ -60,6 +63,7 @@ export interface RoutesData {
|
|||
// useAppContext
|
||||
export interface AppContext {
|
||||
appConfig: AppConfig;
|
||||
appData: any;
|
||||
assetsManifest: AssetsManifest;
|
||||
routesData: RoutesData;
|
||||
routesConfig: RoutesConfig;
|
||||
|
|
|
|||
|
|
@ -64,7 +64,8 @@ const compilationPlugin = (options: Options): UnpluginOptions => {
|
|||
merge(programmaticOptions, compilationConfig);
|
||||
}
|
||||
|
||||
if (removeExportExprs && /(.*)pages(.*)\.(jsx?|tsx?|mjs)$/.test(id)) {
|
||||
// handle app.tsx and page entries only
|
||||
if (removeExportExprs && (/(.*)pages(.*)\.(jsx?|tsx?|mjs)$/.test(id) || /(.*)src\/app/.test(id))) {
|
||||
merge(programmaticOptions, {
|
||||
jsc: {
|
||||
experimental: {
|
||||
|
|
|
|||
|
|
@ -64,6 +64,38 @@ export default defineAppConfig({
|
|||
- 类型 `string`
|
||||
- 默认值 `/`
|
||||
|
||||
## 应用级数据
|
||||
|
||||
可以在应用入口定义并导出 `getAppData` 方法,来获取应用级数据。示例:
|
||||
|
||||
```js
|
||||
import type { GetAppData } from 'ice';
|
||||
|
||||
// ...
|
||||
|
||||
export const getAppData: GetAppData = () => {
|
||||
return new Promise((resolve) => {
|
||||
resolve({
|
||||
success: true,
|
||||
id: 34293
|
||||
});
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
在页面或其他组件中,可以通过 `useAppData` 方法获取页面级数据。示例:
|
||||
|
||||
```js
|
||||
import { useAppData } from 'ice';
|
||||
import type { AppData } from 'ice';
|
||||
|
||||
export default function Home(props) {
|
||||
const appData = useAppData<AppData>();
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## 运行时拓展
|
||||
|
||||
应用入口除了支持定义应用配置之外,同时也承担运行时扩展的能力,比如权限配置:
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ export async function getData() {
|
|||
}
|
||||
|
||||
// Server 端专用的数据请求
|
||||
export async function getStaticData() {
|
||||
export async function getServerData() {
|
||||
const data = await sendRequestInServer();
|
||||
|
||||
return data;
|
||||
|
|
|
|||
Loading…
Reference in New Issue