Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
Size: Mime:
import React from 'react'
import { renderToString } from 'react-dom/server'
import { ServerStyleSheet } from 'view-container/styles/styled-components'
import { StaticRouter } from 'react-router-dom'
import Helmet from 'react-helmet'
import serialize from 'serialize-javascript'
import { getDataFromTree, ApolloProvider } from 'react-apollo'
import { useStaticRendering, Provider } from 'xmobx/mobx-react'
import { requireClient } from './requireClient'
import { respondRedirect, respondHyperText, respondError } from '../responders'
import { cacheIfNeeded } from '../../middleware/cacheMiddleware'
import { ServerHTML } from '../HTML'
import { Request, Response, ReactRouterContext } from '../typings'

/**
 * @see https://github.com/mobxjs/mobx-react
 * > "To avoid leaking memory when server side rendering"
 */
useStaticRendering(true)

function renderFinalString(html: JSX.Element) {
  // @note this was renderToStaticMarkup
  const appString = renderToString(html)

  if (process.env.IS_SSR_TEST) {
    const stringScript = `<script>window.__SSR_STRING__=${serialize(
      appString
    )}</script>`
    const output = appString + stringScript
    return output
  } else {
    return appString
  }
}

export async function renderUsingServerSideRendering(
  req: Request,
  res: Response,
  nonce: string
) {
  /**
   * would scope these higher, but req is scoped
   * @todo probably should have a class
   */
  const onError = apolloRenderingError => {
    console.error('[EXCEPTION_ALERT] [ssr] APOLLO ERROR', apolloRenderingError)
    respondError(res, apolloRenderingError)
  }

  console.log('[SSR] RENDERING_SSR_FOR_ROUTE:')

  /**
   * injected client dependencies
   * this is set in development poc & dist/index
   */
  const { App, client, OmniStore } = requireClient()

  /**
   * Create a context for <StaticRouter>,
   * which will allow us to query for the results of the render.
   */
  const reactRouterContext: ReactRouterContext = {}

  /**
   * Create the job context for our provider,
   * this grants us the ability to track the resolved jobs
   * so we can send it back to the client.
   */
  const store = OmniStore.create()

  /**
   * @description Declare our React application.
   * @see https://www.apollographql.com/docs/react/recipes/server-side-rendering.html
   */
  const appView = (
    <StaticRouter location={req.url} context={reactRouterContext}>
      <ApolloProvider client={client}>
        <Provider store={store}>
          <App />
        </Provider>
      </ApolloProvider>
    </StaticRouter>
  )

  function renderReactToString(initialState: Object) {
    const sheet = new ServerStyleSheet()
    const styledView = sheet.collectStyles(appView)
    const appString = renderToString(styledView)
    const styledTags = sheet.getStyleElement()

    /**
     * @see https://github.com/styled-components/styled-components/issues/378
     * @todo https://github.com/nfl/react-helmet/issues/216
     */
    const htmlView = (
      <ServerHTML
        isCached={req.SHOULD_CACHE_SSR}
        reactAppString={appString}
        nonce={nonce}
        helmet={Helmet.rewind()}
        styledTags={styledTags}
        apolloState={initialState}
        storeState={store}
        routerState={reactRouterContext}
      />
    )

    return renderFinalString(htmlView)
  }

  const onBootstrapped = () => {
    /**
     * @todo https://www.apollographql.com/docs/react/features/server-side-rendering.html#server-initialization
     */
    const initialState = client.extract()

    console.time('_render_app_to_string')
    const html = renderReactToString(initialState)
    console.timeEnd('_render_app_to_string')

    cacheIfNeeded(req, res, html)

    // @NOTE done here because it may be set in server html as a side effect
    const { url, status } = reactRouterContext

    /**
     * Check if the router context contains a redirect, if so we need to set
     * the specific status and redirect header and end the res.
     */
    if (url) {
      respondRedirect(res, url)
    } else {
      respondHyperText(res, status, html)
    }
  }

  const encasedOnBootstrap = () => {
    try {
      onBootstrapped()
    } catch (renderReactToStringException) {
      console.log('[ssr] TOSTRING ERROR', renderReactToStringException)
      respondError(res, renderReactToStringException)
    }
  }

  try {
    console.time('get_data_from_tree')
    getDataFromTree(appView)
      .then(encasedOnBootstrap)
      .catch(onError)
    console.timeEnd('get_data_from_tree')
  } catch (bootstrapperException) {
    console.log('[ssr] BOOTSTRAPPER ERROR', bootstrapperException)
    respondError(res, bootstrapperException)
  }
}