From 6ac6d3578b68ecb34f1236309c094de9e532bd87 Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Mon, 24 Jul 2023 14:18:02 +0800 Subject: [PATCH 1/6] feat: support API useDocumentData --- .changeset/olive-bikes-carry.md | 6 ++++++ examples/basic-project/src/app.tsx | 9 +++++++++ examples/basic-project/src/document.tsx | 6 +++++- packages/ice/src/constant.ts | 1 + packages/runtime/src/index.ts | 6 ++++++ packages/runtime/src/runServerApp.tsx | 16 ++++++++++++---- packages/runtime/src/types.ts | 2 ++ 7 files changed, 41 insertions(+), 5 deletions(-) create mode 100644 .changeset/olive-bikes-carry.md diff --git a/.changeset/olive-bikes-carry.md b/.changeset/olive-bikes-carry.md new file mode 100644 index 0000000000..80d87c52f1 --- /dev/null +++ b/.changeset/olive-bikes-carry.md @@ -0,0 +1,6 @@ +--- +'@ice/runtime': patch +'@ice/app': patch +--- + +feat: support API useDocumentData diff --git a/examples/basic-project/src/app.tsx b/examples/basic-project/src/app.tsx index d7e750ca67..9506892651 100644 --- a/examples/basic-project/src/app.tsx +++ b/examples/basic-project/src/app.tsx @@ -46,3 +46,12 @@ export const dataLoader = defineDataLoader(() => { export const runApp = (render) => { render(); }; +export const unstable_documentData = () => { + return new Promise((resolve) => { + setTimeout(() => { + resolve({ + data: 'documentData', + }); + }, 1000); + }); +}; diff --git a/examples/basic-project/src/document.tsx b/examples/basic-project/src/document.tsx index 5a3d61e38d..16570deffb 100644 --- a/examples/basic-project/src/document.tsx +++ b/examples/basic-project/src/document.tsx @@ -1,8 +1,12 @@ -import { Meta, Title, Links, Main, Scripts, useAppData } from 'ice'; +import { Meta, Title, Links, Main, Scripts, useAppData, useDocumentData } from 'ice'; import type { AppData } from '@/types'; function Document() { const appData = useAppData(); + // Get document data when fallback to document only. + const data = useDocumentData(); + + console.log('document data', data); return ( diff --git a/packages/ice/src/constant.ts b/packages/ice/src/constant.ts index 42158a8131..32d3bfa97a 100644 --- a/packages/ice/src/constant.ts +++ b/packages/ice/src/constant.ts @@ -67,6 +67,7 @@ export const RUNTIME_EXPORTS = [ 'defineServerDataLoader', 'defineStaticDataLoader', 'usePageLifecycle', + 'useDocumentData', ], alias: { usePublicAppContext: 'useAppContext', diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index 67c46b42b4..4c3882926c 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -80,6 +80,11 @@ function usePublicAppContext(): PublicAppContext { }; } +function useDocumentData() { + const context = useInternalAppContext(); + return context.documentData; +} + export { getAppConfig, defineAppConfig, @@ -92,6 +97,7 @@ export { */ useAppContext, usePublicAppContext, + useDocumentData, useAppData, useData, getAppData, diff --git a/packages/runtime/src/runServerApp.tsx b/packages/runtime/src/runServerApp.tsx index cc69ccb214..55ab106f08 100644 --- a/packages/runtime/src/runServerApp.tsx +++ b/packages/runtime/src/runServerApp.tsx @@ -262,6 +262,11 @@ async function doRender(serverContext: ServerContext, renderOptions: RenderOptio await Promise.all(runtimeModules.statics.map(m => runtime.loadModule(m)).filter(Boolean)); } + let documentData: any; + if (app?.documentData) { + documentData = await app.documentData(requestContext); + appContext.documentData = documentData; + } // don't need to execute getAppData in CSR if (!documentOnly) { try { @@ -273,13 +278,13 @@ async function doRender(serverContext: ServerContext, renderOptions: RenderOptio // HashRouter loads route modules by the CSR. if (appConfig?.router?.type === 'hash') { - return renderDocument({ matches: [], routes, renderOptions }); + return renderDocument({ matches: [], routes, renderOptions, documentData }); } const matches = matchRoutes(routes, location, finalBasename); const routePath = getCurrentRoutePath(matches); if (documentOnly) { - return renderDocument({ matches, routePath, routes, renderOptions }); + return renderDocument({ matches, routePath, routes, renderOptions, documentData }); } else if (!matches.length) { return handleNotFoundResponse(); } @@ -339,7 +344,7 @@ async function doRender(serverContext: ServerContext, renderOptions: RenderOptio throw err; } console.error('Warning: render server entry error, downgrade to csr.', err); - return renderDocument({ matches, routePath, renderOptions, routes, downgrade: true }); + return renderDocument({ matches, routePath, renderOptions, routes, downgrade: true, documentData }); } } @@ -399,7 +404,7 @@ async function renderServerEntry( const pipe = renderToNodeStream(element); const fallback = () => { - return renderDocument({ matches, routePath, renderOptions, routes, downgrade: true }); + return renderDocument({ matches, routePath, renderOptions, routes, downgrade: true, documentData: appContext.documentData }); }; return { @@ -414,6 +419,7 @@ interface RenderDocumentOptions { matches: RouteMatch[]; renderOptions: RenderOptions; routes: RouteItem[]; + documentData: any; routePath?: string; downgrade?: boolean; } @@ -428,6 +434,7 @@ function renderDocument(options: RenderDocumentOptions): Response { routePath, downgrade, routes, + documentData, }: RenderDocumentOptions = options; const { @@ -465,6 +472,7 @@ function renderDocument(options: RenderDocumentOptions): Response { basename, downgrade, serverData, + documentData, }; const documentContext = { diff --git a/packages/runtime/src/types.ts b/packages/runtime/src/types.ts index 6cc510d3f5..3b7e2704c4 100644 --- a/packages/runtime/src/types.ts +++ b/packages/runtime/src/types.ts @@ -38,6 +38,7 @@ export interface AppExport { default?: AppConfig; [key: string]: any; dataLoader?: DataLoaderConfig; + documentData: (requestContext: RequestContext) => Promise; } export type DataLoaderResult = (Promise | RouteData) | RouteData; @@ -99,6 +100,7 @@ export interface LoaderData { export interface AppContext { appConfig: AppConfig; appData: any; + documentData?: any; serverData?: any; assetsManifest?: AssetsManifest; loaderData?: LoadersData; From 3844a5c693f6a9805c99482f1a13e2ac09c73d93 Mon Sep 17 00:00:00 2001 From: ZeroLing Date: Thu, 24 Aug 2023 11:04:30 +0800 Subject: [PATCH 2/6] refactor: make document data unstable --- examples/basic-project/src/document.tsx | 4 ++-- packages/ice/src/constant.ts | 2 +- packages/runtime/src/index.ts | 6 ++++-- packages/runtime/src/types.ts | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/examples/basic-project/src/document.tsx b/examples/basic-project/src/document.tsx index 16570deffb..60db74b6be 100644 --- a/examples/basic-project/src/document.tsx +++ b/examples/basic-project/src/document.tsx @@ -1,10 +1,10 @@ -import { Meta, Title, Links, Main, Scripts, useAppData, useDocumentData } from 'ice'; +import { Meta, Title, Links, Main, Scripts, useAppData, unstable_useDocumentData } from 'ice'; import type { AppData } from '@/types'; function Document() { const appData = useAppData(); // Get document data when fallback to document only. - const data = useDocumentData(); + const data = unstable_useDocumentData(); console.log('document data', data); diff --git a/packages/ice/src/constant.ts b/packages/ice/src/constant.ts index 32d3bfa97a..65321bd026 100644 --- a/packages/ice/src/constant.ts +++ b/packages/ice/src/constant.ts @@ -67,7 +67,7 @@ export const RUNTIME_EXPORTS = [ 'defineServerDataLoader', 'defineStaticDataLoader', 'usePageLifecycle', - 'useDocumentData', + 'unstable_useDocumentData', ], alias: { usePublicAppContext: 'useAppContext', diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index 4c3882926c..b88f4ee646 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -82,9 +82,12 @@ function usePublicAppContext(): PublicAppContext { function useDocumentData() { const context = useInternalAppContext(); - return context.documentData; + return context.unstable_documentData; } +// @TODO: remove unstable prefix or refactor. +export const unstable_useDocumentData = useDocumentData; + export { getAppConfig, defineAppConfig, @@ -97,7 +100,6 @@ export { */ useAppContext, usePublicAppContext, - useDocumentData, useAppData, useData, getAppData, diff --git a/packages/runtime/src/types.ts b/packages/runtime/src/types.ts index 3b7e2704c4..391098fd4e 100644 --- a/packages/runtime/src/types.ts +++ b/packages/runtime/src/types.ts @@ -100,7 +100,7 @@ export interface LoaderData { export interface AppContext { appConfig: AppConfig; appData: any; - documentData?: any; + unstable_documentData?: any; serverData?: any; assetsManifest?: AssetsManifest; loaderData?: LoadersData; From 724eee91133c3aedb7e4ba1a4362a1e729273970 Mon Sep 17 00:00:00 2001 From: ZeroLing Date: Thu, 24 Aug 2023 11:50:04 +0800 Subject: [PATCH 3/6] refactor: document data loader --- examples/basic-project/src/app.tsx | 9 ------- examples/basic-project/src/document.tsx | 23 ++++++++++++++--- .../ice/templates/core/entry.server.ts.ejs | 5 ++-- packages/runtime/package.json | 1 + packages/runtime/src/index.ts | 2 +- packages/runtime/src/runServerApp.tsx | 25 +++++++++++++------ packages/runtime/src/types.ts | 3 +-- pnpm-lock.yaml | 2 ++ 8 files changed, 46 insertions(+), 24 deletions(-) diff --git a/examples/basic-project/src/app.tsx b/examples/basic-project/src/app.tsx index 9506892651..d7e750ca67 100644 --- a/examples/basic-project/src/app.tsx +++ b/examples/basic-project/src/app.tsx @@ -46,12 +46,3 @@ export const dataLoader = defineDataLoader(() => { export const runApp = (render) => { render(); }; -export const unstable_documentData = () => { - return new Promise((resolve) => { - setTimeout(() => { - resolve({ - data: 'documentData', - }); - }, 1000); - }); -}; diff --git a/examples/basic-project/src/document.tsx b/examples/basic-project/src/document.tsx index 60db74b6be..3399161771 100644 --- a/examples/basic-project/src/document.tsx +++ b/examples/basic-project/src/document.tsx @@ -1,12 +1,23 @@ -import { Meta, Title, Links, Main, Scripts, useAppData, unstable_useDocumentData } from 'ice'; +import { Meta, Title, Links, Main, Scripts, useAppData, defineDataLoader, unstable_useDocumentData } from 'ice'; import type { AppData } from '@/types'; +export const dataLoader = defineDataLoader(() => { + return new Promise((resolve) => { + setTimeout(() => { + resolve({ + title: 'documentData', + }); + // ATTENTION: This async call will pause rendering document. + }, 1000); + }); +}); + function Document() { const appData = useAppData(); // Get document data when fallback to document only. - const data = unstable_useDocumentData(); + const documentData = unstable_useDocumentData(); - console.log('document data', data); + console.log('document data', documentData); return ( @@ -25,6 +36,12 @@ function Document() {
+
+

Document Data

+ +
{JSON.stringify(documentData, null, 2)}
+
+
diff --git a/packages/ice/templates/core/entry.server.ts.ejs b/packages/ice/templates/core/entry.server.ts.ejs index 48b7a4eb0f..26f9376976 100644 --- a/packages/ice/templates/core/entry.server.ts.ejs +++ b/packages/ice/templates/core/entry.server.ts.ejs @@ -4,7 +4,7 @@ import * as runtime from '@ice/runtime/server'; import { commons, statics } from './runtimeModules'; <% }-%> import * as app from '@/app'; -import Document from '@/document'; +import * as Document from '@/document'; import type { RenderMode, DistType } from '@ice/runtime'; import type { RenderToPipeableStreamOptions } from 'react-dom/server'; // @ts-ignore @@ -85,7 +85,8 @@ function mergeOptions(options) { assetsManifest, createRoutes, runtimeModules, - Document, + documentDataLoader: Document.dataLoader, + Document: Document.default, basename: basename || getRouterBasename(), renderMode, routesConfig, diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 22c0e1ea88..52c19c6ed4 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -50,6 +50,7 @@ ], "dependencies": { "@ice/jsx-runtime": "^0.2.1", + "@ice/shared": "^1.0.1", "@remix-run/router": "1.7.2", "abortcontroller-polyfill": "1.7.5", "ejs": "^3.1.6", diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index b88f4ee646..a69da107a0 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -82,7 +82,7 @@ function usePublicAppContext(): PublicAppContext { function useDocumentData() { const context = useInternalAppContext(); - return context.unstable_documentData; + return context.documentData; } // @TODO: remove unstable prefix or refactor. diff --git a/packages/runtime/src/runServerApp.tsx b/packages/runtime/src/runServerApp.tsx index 55ab106f08..864c801572 100644 --- a/packages/runtime/src/runServerApp.tsx +++ b/packages/runtime/src/runServerApp.tsx @@ -1,9 +1,10 @@ import type { ServerResponse, IncomingMessage } from 'http'; import * as React from 'react'; +import type { RenderToPipeableStreamOptions } from 'react-dom/server'; import * as ReactDOMServer from 'react-dom/server'; -import { parsePath } from 'history'; import type { Location } from 'history'; -import type { RenderToPipeableStreamOptions } from 'react-dom/server'; +import { parsePath } from 'history'; +import { isFunction } from '@ice/shared'; import type { AppContext, RouteItem, ServerContext, AppExport, AssetsManifest, @@ -13,7 +14,7 @@ import type { DocumentComponent, RuntimeModules, AppData, - ServerAppRouterProps, + ServerAppRouterProps, DataLoaderConfig, } from './types.js'; import Runtime from './runtime.js'; import { AppContextProvider } from './AppContext.js'; @@ -31,11 +32,13 @@ import ServerRouter from './ServerRouter.js'; import { renderHTMLToJS } from './renderHTMLToJS.js'; import addLeadingSlash from './utils/addLeadingSlash.js'; + interface RenderOptions { app: AppExport; assetsManifest: AssetsManifest; createRoutes: (options: Pick) => RouteItem[]; runtimeModules: RuntimeModules; + documentDataLoader?: DataLoaderConfig; Document: DocumentComponent; documentOnly?: boolean; renderMode?: RenderMode; @@ -262,12 +265,20 @@ async function doRender(serverContext: ServerContext, renderOptions: RenderOptio await Promise.all(runtimeModules.statics.map(m => runtime.loadModule(m)).filter(Boolean)); } + // Execute document dataLoader. let documentData: any; - if (app?.documentData) { - documentData = await app.documentData(requestContext); - appContext.documentData = documentData; + if (renderOptions.documentDataLoader) { + const { loader } = renderOptions.documentDataLoader; + if (isFunction(loader)) { + documentData = await loader(requestContext); + // @TODO: document should have it's own context, not shared with app. + appContext.documentData = documentData; + } else { + console.warn('Document dataLoader only accepts function.'); + } } - // don't need to execute getAppData in CSR + + // Not to execute [getAppData] when CSR. if (!documentOnly) { try { appData = await getAppData(app, requestContext); diff --git a/packages/runtime/src/types.ts b/packages/runtime/src/types.ts index 391098fd4e..6ac6110b2e 100644 --- a/packages/runtime/src/types.ts +++ b/packages/runtime/src/types.ts @@ -38,7 +38,6 @@ export interface AppExport { default?: AppConfig; [key: string]: any; dataLoader?: DataLoaderConfig; - documentData: (requestContext: RequestContext) => Promise; } export type DataLoaderResult = (Promise | RouteData) | RouteData; @@ -100,7 +99,7 @@ export interface LoaderData { export interface AppContext { appConfig: AppConfig; appData: any; - unstable_documentData?: any; + documentData?: any; serverData?: any; assetsManifest?: AssetsManifest; loaderData?: LoadersData; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 49d3cd98a0..7b499c67e3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1582,6 +1582,7 @@ importers: packages/runtime: specifiers: '@ice/jsx-runtime': ^0.2.1 + '@ice/shared': ^1.0.1 '@remix-run/router': 1.7.2 '@remix-run/web-fetch': ^4.3.3 '@types/react': ^18.0.8 @@ -1599,6 +1600,7 @@ importers: source-map: ^0.7.4 dependencies: '@ice/jsx-runtime': link:../jsx-runtime + '@ice/shared': link:../shared '@remix-run/router': 1.7.2 abortcontroller-polyfill: 1.7.5 ejs: 3.1.8 From c6ad54ecdb28db69d4e4757dab80c17aa39b4d37 Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Wed, 30 Aug 2023 18:07:50 +0800 Subject: [PATCH 4/6] chore: lint --- examples/basic-project/src/document.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/basic-project/src/document.tsx b/examples/basic-project/src/document.tsx index 3399161771..79ee7f4ef1 100644 --- a/examples/basic-project/src/document.tsx +++ b/examples/basic-project/src/document.tsx @@ -1,3 +1,4 @@ +// eslint-disable-next-line import { Meta, Title, Links, Main, Scripts, useAppData, defineDataLoader, unstable_useDocumentData } from 'ice'; import type { AppData } from '@/types'; From 818b78aa97d67ff8a95ead723cfd2c8ffc69ab5b Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Wed, 30 Aug 2023 18:08:15 +0800 Subject: [PATCH 5/6] chore: lint --- packages/runtime/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index a69da107a0..f5ad7663c9 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -86,6 +86,7 @@ function useDocumentData() { } // @TODO: remove unstable prefix or refactor. +// eslint-disable-next-line export const unstable_useDocumentData = useDocumentData; export { From 26d70d9948646fddeadcdc8c5207a0e9f3755f18 Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Wed, 30 Aug 2023 18:08:58 +0800 Subject: [PATCH 6/6] chore: lint --- packages/runtime/src/runServerApp.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/runtime/src/runServerApp.tsx b/packages/runtime/src/runServerApp.tsx index 864c801572..d1e37256ca 100644 --- a/packages/runtime/src/runServerApp.tsx +++ b/packages/runtime/src/runServerApp.tsx @@ -415,7 +415,14 @@ async function renderServerEntry( const pipe = renderToNodeStream(element); const fallback = () => { - return renderDocument({ matches, routePath, renderOptions, routes, downgrade: true, documentData: appContext.documentData }); + return renderDocument({ + matches, + routePath, + renderOptions, + routes, + downgrade: true, + documentData: appContext.documentData, + }); }; return {