Skip to content

Commit

Permalink
Integrate Server-Side ReactQuery Support (Non-Breaking Change) (#724)
Browse files Browse the repository at this point in the history
* Add react-query support, with opt-in via an HOC

Co-authored-by: Ben Chypak <[email protected]>
Co-authored-by: Ben Chypak <[email protected]>
  • Loading branch information
3 people authored Sep 21, 2022
1 parent f6de3aa commit f3c16a4
Show file tree
Hide file tree
Showing 20 changed files with 1,451 additions and 12,108 deletions.
10,364 changes: 477 additions & 9,887 deletions package-lock.json

Large diffs are not rendered by default.

1,933 changes: 88 additions & 1,845 deletions packages/commerce-sdk-react/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/pwa-kit-dev/src/configs/webpack/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ const baseConfig = (target) => {
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
alias: {
'babel-runtime': findInProjectThenSDK('babel-runtime'),
'@tanstack/react-query': findInProjectThenSDK('@tanstack/react-query'),
'@loadable/component': findInProjectThenSDK('@loadable/component'),
'@loadable/server': findInProjectThenSDK('@loadable/server'),
'@loadable/webpack-plugin': findInProjectThenSDK(
Expand Down
1 change: 1 addition & 0 deletions packages/pwa-kit-react-sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
## v2.3.0-dev (Aug 25, 2022)
- Support `react-query` server-side data fetching. [#724](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/724)
## v2.2.0 (Aug 25, 2022)
## v2.1.0 (Jul 05, 2022)
- Remove console logs from route component. [#651](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/651)
Expand Down
708 changes: 416 additions & 292 deletions packages/pwa-kit-react-sdk/package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions packages/pwa-kit-react-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@loadable/babel-plugin": "^5.13.2",
"@loadable/server": "^5.15.0",
"@loadable/webpack-plugin": "^5.15.0",
"@tanstack/react-query": "^4.0.10",
"cross-env": "^5.2.0",
"event-emitter": "^0.3.5",
"glob": "7.1.1",
Expand All @@ -51,6 +52,7 @@
"mkdirp": "^1.0.4",
"prop-types": "^15.6.0",
"pwa-kit-runtime": "^2.3.0-dev",
"react-ssr-prepass": "^1.5.0",
"react-uid": "^2.2.0",
"serialize-javascript": "^6.0.0",
"svg-sprite-loader": "^6.0.11",
Expand Down
3 changes: 2 additions & 1 deletion packages/pwa-kit-react-sdk/src/ssr/browser/main.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import ReactDOM from 'react-dom'
import {BrowserRouter as Router} from 'react-router-dom'
import DeviceContext from '../universal/device-context'
import App from '../universal/components/_app'
import AppConfig from '../universal/components/_app-config'
import {getAppConfig} from '../universal/compatibility'
import Switch from '../universal/components/switch'
import {getRoutes, routeComponent} from '../universal/components/route-component'
import {loadableReady} from '@loadable/component'
Expand All @@ -34,6 +34,7 @@ export const registerServiceWorker = (url) => {

/* istanbul ignore next */
export const start = () => {
const AppConfig = getAppConfig()
const rootEl = document.getElementsByClassName('react-target')[0]
const data = JSON.parse(document.getElementById('mobify-data').innerHTML)

Expand Down
155 changes: 76 additions & 79 deletions packages/pwa-kit-react-sdk/src/ssr/server/react-rendering.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ import Document from '../universal/components/_document'
import App from '../universal/components/_app'
import Throw404 from '../universal/components/throw-404'

import AppConfig from '../universal/components/_app-config'
import {getAppConfig} from '../universal/compatibility'
import Switch from '../universal/components/switch'
import {getRoutes, routeComponent} from '../universal/components/route-component'
import * as errors from '../universal/errors'
import {detectDeviceType, isRemote} from 'pwa-kit-runtime/utils/ssr-server'
import {proxyConfigs} from 'pwa-kit-runtime/utils/ssr-shared'
import {getConfig} from 'pwa-kit-runtime/utils/ssr-config'

import sprite from 'svg-sprite-loader/runtime/sprite.build'
import PropTypes from 'prop-types'

const CWD = process.cwd()
const BUNDLES_PATH = path.resolve(CWD, 'build/loadable-stats.json')
Expand Down Expand Up @@ -72,52 +72,6 @@ const logAndFormatError = (err) => {
}
}

const initAppState = async ({App, component, match, route, req, res, location}) => {
if (component === Throw404) {
// Don't init if there was no match
return {
error: new errors.HTTPNotFound('Not found'),
appState: {}
}
}

const {params} = match

const components = [App, route.component]
const promises = components.map((c) =>
c.getProps
? c.getProps({
req,
res,
params,
location
})
: Promise.resolve({})
)
let returnVal = {}

try {
const [appProps, pageProps] = await Promise.all(promises)
const appState = {
appProps,
pageProps,
__STATE_MANAGEMENT_LIBRARY: AppConfig.freeze(res.locals)
}

returnVal = {
error: undefined,
appState: appState
}
} catch (error) {
returnVal = {
error: error || new Error(),
appState: {}
}
}

return returnVal
}

/**
* This is the main react-rendering function for SSR. It is an Express handler.
*
Expand All @@ -129,11 +83,10 @@ const initAppState = async ({App, component, match, route, req, res, location})
* @return {Promise}
*/
export const render = async (req, res, next) => {
const AppConfig = getAppConfig()
// Get the application config which should have been stored at this point.
const config = getConfig()

// AppConfig.restore *must* come before using getRoutes() or routeComponent()
// to inject arguments into the wrapped component's getProps methods.
AppConfig.restore(res.locals)

const routes = getRoutes(res.locals)
Expand Down Expand Up @@ -162,30 +115,60 @@ export const render = async (req, res, next) => {
const component = await route.component.getComponent()

// Step 3 - Init the app state
const {appState, error: appStateError} = await initAppState({
App: WrappedApp,
component,
match,
route,
const deviceType = detectDeviceType(req)
const props = {
error: null,
appState: {},
routerContext: {},
req,
res,
location
})

// Step 4 - Render the App
let renderResult
const args = {
App: WrappedApp,
appState,
appStateError: appStateError && logAndFormatError(appStateError),
routes,
req,
res,
location,
config
deviceType
}
let appJSX = <OuterApp {...props} />

let appState, appStateError

if (component === Throw404) {
appState = {}
appStateError = new errors.HTTPNotFound('Not found')
} else {
const ret = await AppConfig.initAppState({
App: WrappedApp,
component,
match,
route,
req,
res,
location,
appJSX
})
appState = {
...ret.appState,
__STATE_MANAGEMENT_LIBRARY: AppConfig.freeze(res.locals)
}
appStateError = ret.error
}

appJSX = React.cloneElement(appJSX, {error: appStateError, appState})

// Step 4 - Render the App
let renderResult
try {
renderResult = renderApp(args)
renderResult = renderApp({
App: WrappedApp,
appState,
appStateError: appStateError && logAndFormatError(appStateError),
routes,
req,
res,
location,
config,
appJSX,
deviceType
})
} catch (e) {
// This is an unrecoverable error.
// (errors handled by the AppErrorBoundary are considered recoverable)
Expand All @@ -208,10 +191,9 @@ export const render = async (req, res, next) => {
}
}

const renderAppHtml = (req, res, error, appData) => {
const {App, appState, routes, routerContext, location, extractor, deviceType} = appData

let appJSX = (
const OuterApp = ({res, error, App, appState, routes, routerContext, location, deviceType}) => {
const AppConfig = getAppConfig()
return (
<Router location={location} context={routerContext}>
<DeviceContext.Provider value={{type: deviceType}}>
<AppConfig locals={res.locals}>
Expand All @@ -220,33 +202,48 @@ const renderAppHtml = (req, res, error, appData) => {
</DeviceContext.Provider>
</Router>
)
}

appJSX = extractor.collectChunks(appJSX)
return ReactDOMServer.renderToString(appJSX)
OuterApp.propTypes = {
res: PropTypes.object,
error: PropTypes.object,
App: PropTypes.elementType,
appState: PropTypes.object,
routes: PropTypes.array,
routerContext: PropTypes.object,
location: PropTypes.object,
deviceType: PropTypes.string
}

const renderToString = (jsx, extractor) =>
ReactDOMServer.renderToString(extractor.collectChunks(jsx))

const renderApp = (args) => {
const {req, res, appStateError, App, appState, location, routes, config} = args
const deviceType = detectDeviceType(req)
const {req, res, appStateError, appJSX, appState, config, deviceType} = args
const extractor = new ChunkExtractor({statsFile: BUNDLES_PATH, publicPath: getAssetUrl()})
const routerContext = {}
const appData = {App, appState, location, routes, routerContext, deviceType, extractor}

const ssrOnly = 'mobify_server_only' in req.query || '__server_only' in req.query
const prettyPrint = 'mobify_pretty' in req.query || '__pretty_print' in req.query
const indent = prettyPrint ? 8 : 0

let routerContext
let appHtml
let renderError
// It's important that we render the App before extracting the script elements,
// otherwise it won't return the correct chunks.

try {
appHtml = renderAppHtml(req, res, appStateError, appData)
routerContext = {}
appHtml = renderToString(React.cloneElement(appJSX, {routerContext}), extractor)
} catch (e) {
// This will catch errors thrown from the app and pass the error
// to the AppErrorBoundary component, and renders the error page.
routerContext = {}
renderError = logAndFormatError(e)
appHtml = renderAppHtml(req, res, renderError, appData)
appHtml = renderToString(
React.cloneElement(appJSX, {routerContext, error: renderError}),
extractor
)
}

// Setting type: 'application/json' stops the browser from executing the code.
Expand Down
Loading

0 comments on commit f3c16a4

Please sign in to comment.