mirror of https://github.com/alibaba/ice.git
feat: pass server data to client
This commit is contained in:
parent
5a8bc3549d
commit
aed4a7b4e8
|
|
@ -6,13 +6,12 @@ import assetsManifest from './assets-manifest.json';
|
|||
import routes from './routes';
|
||||
|
||||
export async function render(requestContext, documentOnly = false) {
|
||||
return await runServerApp({
|
||||
return await runServerApp(Document, {
|
||||
requestContext,
|
||||
runtimeModules,
|
||||
appConfig,
|
||||
routes,
|
||||
assetsManifest,
|
||||
Document,
|
||||
documentOnly,
|
||||
});
|
||||
}
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
import * as React from 'react';
|
||||
import type { PageConfig } from './types';
|
||||
import type { PageConfig, AppData } from './types';
|
||||
|
||||
interface DocumentContext {
|
||||
html?: string;
|
||||
entryAssets?: string[];
|
||||
pageAssets?: string[];
|
||||
pageConfig?: PageConfig;
|
||||
appData?: AppData;
|
||||
}
|
||||
|
||||
const Context = React.createContext<DocumentContext>(null);
|
||||
|
|
@ -61,7 +62,7 @@ export function Links() {
|
|||
}
|
||||
|
||||
export function Scripts() {
|
||||
const { pageConfig = {}, pageAssets, entryAssets } = useDocumentContext();
|
||||
const { pageConfig = {}, pageAssets, entryAssets, appData } = useDocumentContext();
|
||||
const { links: customLinks = [], scripts: customScripts = [] } = pageConfig;
|
||||
|
||||
const scripts = pageAssets.concat(entryAssets).filter(path => path.indexOf('.js') > -1);
|
||||
|
|
@ -72,6 +73,7 @@ export function Scripts() {
|
|||
|
||||
return (
|
||||
<>
|
||||
<script dangerouslySetInnerHTML={{ __html: `window.__ICE_APP_DATA__=${JSON.stringify(appData)}` }} />
|
||||
{
|
||||
blockScripts.map(script => {
|
||||
const { block, ...props } = script;
|
||||
|
|
|
|||
|
|
@ -14,31 +14,34 @@ export default async function runBrowserApp(
|
|||
) {
|
||||
const matches = matchRoutes(routes, window.location);
|
||||
const routeModules = await loadRouteModules(matches.map(match => match.route as RouteItem));
|
||||
const pageData = await loadPageData(matches, routeModules, {});
|
||||
|
||||
const { href, origin, pathname, search } = window.location;
|
||||
const path = href.replace(origin, '');
|
||||
const query = Object.fromEntries(createSearchParams(search));
|
||||
const initialContext: InitialContext = {
|
||||
pathname,
|
||||
path,
|
||||
query,
|
||||
};
|
||||
|
||||
const appData = (window as any).__ICE_APP_DATA__ || {};
|
||||
let { initialData, pageData } = appData;
|
||||
|
||||
if (!initialData && appConfig?.app?.getInitialData) {
|
||||
initialData = await appConfig.app.getInitialData(initialContext);
|
||||
}
|
||||
|
||||
if (!pageData) {
|
||||
pageData = await loadPageData(matches, routeModules, initialContext);
|
||||
}
|
||||
|
||||
const appContext: AppContext = {
|
||||
routes,
|
||||
appConfig,
|
||||
initialData: null,
|
||||
routeModules,
|
||||
initialData,
|
||||
pageData,
|
||||
};
|
||||
// ssr enabled and the server has returned data
|
||||
if ((window as any).__ICE_APP_DATA__) {
|
||||
appContext.initialData = (window as any).__ICE_APP_DATA__;
|
||||
// context.pageInitialProps = (window as any).__ICE_PAGE_PROPS__;
|
||||
} else if (appConfig?.app?.getInitialData) {
|
||||
const { href, origin, pathname, search } = window.location;
|
||||
const path = href.replace(origin, '');
|
||||
const query = Object.fromEntries(createSearchParams(search));
|
||||
const ssrError = (window as any).__ICE_SSR_ERROR__;
|
||||
const initialContext: InitialContext = {
|
||||
pathname,
|
||||
path,
|
||||
query,
|
||||
ssrError,
|
||||
};
|
||||
appContext.initialData = await appConfig.app.getInitialData(initialContext);
|
||||
}
|
||||
|
||||
const runtime = new Runtime(appContext);
|
||||
runtimeModules.forEach(m => {
|
||||
|
|
|
|||
|
|
@ -16,24 +16,23 @@ interface RunServerAppOptions {
|
|||
routes: RouteItem[];
|
||||
documentOnly: boolean;
|
||||
runtimeModules: (RuntimePlugin | CommonJsRuntime)[];
|
||||
Document: React.ComponentType<any>;
|
||||
assetsManifest: AssetsManifest;
|
||||
}
|
||||
|
||||
export default async function runServerApp(options: RunServerAppOptions): Promise<string> {
|
||||
async function runServerApp(Document: React.ComponentType<any>, options: RunServerAppOptions): Promise<string> {
|
||||
const {
|
||||
requestContext,
|
||||
appConfig,
|
||||
assetsManifest,
|
||||
documentOnly,
|
||||
requestContext,
|
||||
runtimeModules,
|
||||
routes,
|
||||
Document,
|
||||
documentOnly,
|
||||
assetsManifest,
|
||||
} = options;
|
||||
|
||||
const { req } = requestContext;
|
||||
const { url } = req;
|
||||
// ref: https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/server.tsx
|
||||
const locationProps = parsePath(req.url);
|
||||
const locationProps = parsePath(url);
|
||||
|
||||
const location: Location = {
|
||||
pathname: locationProps.pathname || '/',
|
||||
|
|
@ -45,13 +44,12 @@ export default async function runServerApp(options: RunServerAppOptions): Promis
|
|||
|
||||
const matches = matchRoutes(routes, location);
|
||||
const routeModules = await loadRouteModules(matches.map(match => match.route as RouteItem));
|
||||
const pageData = await loadPageData(matches, routeModules, requestContext);
|
||||
|
||||
const initialContext: InitialContext = {
|
||||
...requestContext,
|
||||
pathname: location.pathname,
|
||||
query: Object.fromEntries(createSearchParams(location.search)),
|
||||
path: req.url,
|
||||
path: url,
|
||||
};
|
||||
|
||||
let initialData;
|
||||
|
|
@ -59,6 +57,8 @@ export default async function runServerApp(options: RunServerAppOptions): Promis
|
|||
initialData = await appConfig.app.getInitialData(initialContext);
|
||||
}
|
||||
|
||||
const pageData = await loadPageData(matches, routeModules, initialContext);
|
||||
|
||||
const appContext: AppContext = {
|
||||
matches,
|
||||
routes,
|
||||
|
|
@ -79,18 +79,20 @@ export default async function runServerApp(options: RunServerAppOptions): Promis
|
|||
runtime.loadModule(m);
|
||||
});
|
||||
|
||||
const html = render(runtime, location, Document, documentOnly);
|
||||
const html = render(Document, runtime, location, documentOnly);
|
||||
return html;
|
||||
}
|
||||
|
||||
export default runServerApp;
|
||||
|
||||
async function render(
|
||||
Document,
|
||||
runtime: Runtime,
|
||||
location: Location,
|
||||
Document,
|
||||
documentOnly: boolean,
|
||||
) {
|
||||
const appContext = runtime.getAppContext();
|
||||
const { matches, pageData = {}, assetsManifest } = appContext;
|
||||
const { matches, initialData, pageData, assetsManifest } = appContext;
|
||||
|
||||
let html = '';
|
||||
|
||||
|
|
@ -103,7 +105,13 @@ async function render(
|
|||
const pageAssets = getPageAssets(matches, assetsManifest);
|
||||
const entryAssets = getEntryAssets(assetsManifest);
|
||||
|
||||
const appData = {
|
||||
initialData,
|
||||
pageData,
|
||||
};
|
||||
|
||||
const documentContext = {
|
||||
appData,
|
||||
pageConfig,
|
||||
pageAssets,
|
||||
entryAssets,
|
||||
|
|
|
|||
|
|
@ -97,6 +97,11 @@ export interface AppContext {
|
|||
initialData?: InitialData;
|
||||
}
|
||||
|
||||
export interface AppData {
|
||||
initialData?: InitialData;
|
||||
pageData?: PageData;
|
||||
}
|
||||
|
||||
export interface PageData {
|
||||
pageConfig?: PageConfig;
|
||||
initialData?: InitialData;
|
||||
|
|
|
|||
Loading…
Reference in New Issue