feat: pass server data to client

This commit is contained in:
shuilan.cj 2022-03-25 16:24:01 +08:00 committed by ClarkXia
parent 5a8bc3549d
commit aed4a7b4e8
5 changed files with 52 additions and 35 deletions

View File

@ -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,
});
}

View File

@ -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;

View File

@ -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 => {

View File

@ -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,

View File

@ -97,6 +97,11 @@ export interface AppContext {
initialData?: InitialData;
}
export interface AppData {
initialData?: InitialData;
pageData?: PageData;
}
export interface PageData {
pageConfig?: PageConfig;
initialData?: InitialData;