From eae5d77d1136cb158eb88bce981bc94ffc8dab9b Mon Sep 17 00:00:00 2001 From: Vincent Marta Date: Fri, 23 Sep 2022 11:35:36 -0700 Subject: [PATCH 01/10] 1st attempt at porting PR #713 --- .../src/ssr/browser/main.jsx | 28 ++++++------ .../src/ssr/server/react-rendering.js | 31 +++++++++---- .../components/with-react-query/index.js | 14 +++++- .../{device-context.js => contexts.js} | 8 ++-- .../src/ssr/universal/hooks.js | 44 +++++++++++++++++++ .../app/pages/home.tsx | 11 +++++ 6 files changed, 110 insertions(+), 26 deletions(-) rename packages/pwa-kit-react-sdk/src/ssr/universal/{device-context.js => contexts.js} (51%) create mode 100644 packages/pwa-kit-react-sdk/src/ssr/universal/hooks.js diff --git a/packages/pwa-kit-react-sdk/src/ssr/browser/main.jsx b/packages/pwa-kit-react-sdk/src/ssr/browser/main.jsx index 3d5ec96a5e..6277c68ca1 100644 --- a/packages/pwa-kit-react-sdk/src/ssr/browser/main.jsx +++ b/packages/pwa-kit-react-sdk/src/ssr/browser/main.jsx @@ -8,7 +8,7 @@ import React from 'react' import ReactDOM from 'react-dom' import {BrowserRouter as Router} from 'react-router-dom' -import DeviceContext from '../universal/device-context' +import {DeviceContext, ServerContext} from '../universal/contexts' import App from '../universal/components/_app' import {getAppConfig} from '../universal/compatibility' import Switch from '../universal/components/switch' @@ -74,18 +74,20 @@ export const start = () => { .then(() => new Promise((resolve) => loadableReady(resolve))) .then(() => { ReactDOM.hydrate( - - - - - - - , + + + + + + + + + , rootEl, () => { window.__HYDRATING__ = false diff --git a/packages/pwa-kit-react-sdk/src/ssr/server/react-rendering.js b/packages/pwa-kit-react-sdk/src/ssr/server/react-rendering.js index a5f2ca2fee..cbe9ced494 100644 --- a/packages/pwa-kit-react-sdk/src/ssr/server/react-rendering.js +++ b/packages/pwa-kit-react-sdk/src/ssr/server/react-rendering.js @@ -18,7 +18,7 @@ import {StaticRouter as Router, matchPath} from 'react-router-dom' import serialize from 'serialize-javascript' import {getAssetUrl} from '../universal/utils' -import DeviceContext from '../universal/device-context' +import {DeviceContext, ServerContext} from '../universal/contexts' import Document from '../universal/components/_document' import App from '../universal/components/_app' @@ -191,20 +191,33 @@ export const render = async (req, res, next) => { } } -const OuterApp = ({res, error, App, appState, routes, routerContext, location, deviceType}) => { +const OuterApp = ({ + req, + res, + error, + App, + appState, + routes, + routerContext, + location, + deviceType +}) => { const AppConfig = getAppConfig() return ( - - - - - - - + + + + + + + + + ) } OuterApp.propTypes = { + req: PropTypes.object, res: PropTypes.object, error: PropTypes.object, App: PropTypes.elementType, diff --git a/packages/pwa-kit-react-sdk/src/ssr/universal/components/with-react-query/index.js b/packages/pwa-kit-react-sdk/src/ssr/universal/components/with-react-query/index.js index 8d992d13a5..6118815946 100644 --- a/packages/pwa-kit-react-sdk/src/ssr/universal/components/with-react-query/index.js +++ b/packages/pwa-kit-react-sdk/src/ssr/universal/components/with-react-query/index.js @@ -9,10 +9,22 @@ import {FetchStrategy} from '../fetch-strategy' import React from 'react' import {dehydrate, Hydrate, QueryClient, QueryClientProvider} from '@tanstack/react-query' import ssrPrepass from 'react-ssr-prepass' +import {IsPrePassContext} from '../../contexts' const isServerSide = typeof window === 'undefined' const STATE_KEY = '__reactQuery' +const WithPrepassContext = (Component) => { + const WrappedComponent = (props) => { + return ( + + + + ) + } + return WrappedComponent +} + export const withReactQuery = (Wrapped) => { /* istanbul ignore next */ const wrappedComponentName = Wrapped.displayName || Wrapped.name @@ -39,7 +51,7 @@ export const withReactQuery = (Wrapped) => { const queryClient = (res.locals.__queryClient = res.locals.__queryClient || new QueryClient()) - await ssrPrepass(appJSX) + await ssrPrepass(WithPrepassContext(appJSX)) const queryCache = queryClient.getQueryCache() const queries = queryCache.getAll().filter((q) => q.options.enabled !== false) diff --git a/packages/pwa-kit-react-sdk/src/ssr/universal/device-context.js b/packages/pwa-kit-react-sdk/src/ssr/universal/contexts.js similarity index 51% rename from packages/pwa-kit-react-sdk/src/ssr/universal/device-context.js rename to packages/pwa-kit-react-sdk/src/ssr/universal/contexts.js index 80f96309dd..3859008380 100644 --- a/packages/pwa-kit-react-sdk/src/ssr/universal/device-context.js +++ b/packages/pwa-kit-react-sdk/src/ssr/universal/contexts.js @@ -1,9 +1,11 @@ /* - * Copyright (c) 2021, salesforce.com, inc. + * Copyright (c) 2022, Salesforce, Inc. * All rights reserved. * SPDX-License-Identifier: BSD-3-Clause * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ - import React from 'react' -export default React.createContext() + +export const DeviceContext = React.createContext() +export const ServerContext = React.createContext() +export const IsPrePassContext = React.createContext(false) diff --git a/packages/pwa-kit-react-sdk/src/ssr/universal/hooks.js b/packages/pwa-kit-react-sdk/src/ssr/universal/hooks.js new file mode 100644 index 0000000000..131ec01672 --- /dev/null +++ b/packages/pwa-kit-react-sdk/src/ssr/universal/hooks.js @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2022, Salesforce, Inc. + * All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import {useContext} from 'react' +import {IsPrePassContext, ServerContext} from './contexts' + +/** + * Server context + * @typedef {Object} ServerContext + * @property {Object} req - Request object + * @property {Object} res - Response object + */ + +/** + * @callback serverCtxCallback + * @param {ServerContext} serverContext + */ + +/** + * Execute the given function within the server context of a server-rendered page + * + * @param {serverCtxCallback} fn - Function will be called when the server has received the request but the response is not sent yet + * @example + * useServerContext(({req, res}) => { res.status(404) }) + */ +export const useServerContext = (fn) => { + const serverContext = useContext(ServerContext) + + const isOnServer = Boolean(serverContext.req) + const isPrePass = useIsPrePass() + + if (isOnServer && !isPrePass) { + fn(serverContext) + } +} + +/** + * @private + */ +export const useIsPrePass = () => useContext(IsPrePassContext) diff --git a/packages/template-typescript-minimal/app/pages/home.tsx b/packages/template-typescript-minimal/app/pages/home.tsx index cbdeffdba7..7b3e041f12 100644 --- a/packages/template-typescript-minimal/app/pages/home.tsx +++ b/packages/template-typescript-minimal/app/pages/home.tsx @@ -10,6 +10,8 @@ import {useQuery} from '@tanstack/react-query' import HelloTS from '../components/hello-typescript' import HelloJS from '../components/hello-javascript' +import {useServerContext} from 'pwa-kit-react-sdk/ssr/universal/hooks' + interface Props { value: number } @@ -82,6 +84,8 @@ h1 { ` const Home = ({value}: Props) => { + // TODO: why is this log only once? I thought twice. + console.log('--- FOO') const [counter, setCounter] = useState(0) useEffect(() => { @@ -101,6 +105,13 @@ const Home = ({value}: Props) => { }) ) + useServerContext(({req, res}) => { + console.log('--- useServerContext') + if (query.error) { + res.status(404) + } + }) + return (