UNPKG

@shopify/react-server

Version:
155 lines (147 loc) 6.17 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var path = require('path'); var fs = require('fs'); var React = require('react'); var compose = require('koa-compose'); var server$1 = require('@shopify/react-html/server'); var reactHtml = require('@shopify/react-html'); var server = require('@shopify/react-network/server'); var server$2 = require('@shopify/react-effect/server'); var reactHydrate = require('@shopify/react-hydrate'); var reactAsync = require('@shopify/react-async'); var reactNetwork = require('@shopify/react-network'); var sewingKitKoa = require('@shopify/sewing-kit-koa'); var middleware = require('../quilt-data/middleware.js'); var logger = require('../logger/logger.js'); var fallbackErrorMarkup = require('./error/fallback-error-markup.js'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var React__default = /*#__PURE__*/_interopDefaultLegacy(React); var compose__default = /*#__PURE__*/_interopDefaultLegacy(compose); /** * Creates a Koa middleware for rendering an `@shopify/react-html` based React application defined by `render`. * @param render * @param options */ function createRender(render, options = {}) { const manifestPath = getManifestPath(process.cwd()); const { assetPrefix, assetName: assetNameInput = 'main', renderError, renderRawErrorMessage, htmlProps: htmlPropsInput = {} } = options; async function renderFunction(ctx) { const assetName = typeof assetNameInput === 'function' ? assetNameInput(ctx) : assetNameInput; const { scripts: additionalScripts = [], styles: additionalStyles = [], ...additionalHtmlProps } = typeof htmlPropsInput === 'function' ? htmlPropsInput(ctx) : htmlPropsInput; const logger$1 = logger.getLogger(ctx) || console; const assets = sewingKitKoa.getAssets(ctx); const networkManager = new server.NetworkManager({ headers: ctx.headers, cookies: ctx.request.headers.cookie || '' }); const htmlManager = new server$1.HtmlManager(); const asyncAssetManager = new reactAsync.AsyncAssetManager(); const hydrationManager = new reactHydrate.HydrationManager(); function Providers({ children }) { const [, Serialize] = reactHtml.useSerialized('quilt-data'); return /*#__PURE__*/React__default["default"].createElement(reactAsync.AsyncAssetContext.Provider, { value: asyncAssetManager }, /*#__PURE__*/React__default["default"].createElement(reactHydrate.HydrationContext.Provider, { value: hydrationManager }, /*#__PURE__*/React__default["default"].createElement(server.NetworkContext.Provider, { value: networkManager }, children, /*#__PURE__*/React__default["default"].createElement(Serialize, { data: () => ctx.state.quiltData })))); } try { const app = render(ctx); await server$2.extract(app, { decorate(element) { return /*#__PURE__*/React__default["default"].createElement(server$1.HtmlContext.Provider, { value: htmlManager }, /*#__PURE__*/React__default["default"].createElement(Providers, null, element)); }, afterEachPass({ renderDuration, resolveDuration, index, finished }) { const pass = `Pass number ${index} ${finished ? ' (this was the final pass)' : ''}`; const rendering = `Rendering took ${renderDuration}ms`; const resolving = `Resolving promises took ${resolveDuration}ms`; logger$1.log(pass); logger$1.log(rendering); logger$1.log(resolving); }, ...options }); server.applyToContext(ctx, networkManager); const immediateAsyncAssets = asyncAssetManager.used(reactAsync.AssetTiming.Immediate); const [styles, scripts] = await Promise.all([assets.styles({ name: assetName, asyncAssets: immediateAsyncAssets }), assets.scripts({ name: assetName, asyncAssets: immediateAsyncAssets })]); styles.push(...additionalStyles); scripts.push(...additionalScripts); const response = server$1.stream( /*#__PURE__*/React__default["default"].createElement(server$1.Html, Object.assign({}, additionalHtmlProps, { manager: htmlManager, styles: styles, scripts: scripts }), /*#__PURE__*/React__default["default"].createElement(Providers, null, app))); ctx.set(reactNetwork.Header.ContentType, 'text/html'); ctx.body = response; } catch (error) { const errorMessage = `React server-side rendering error:\n${error.stack || error.message}`; logger$1.log(errorMessage); ctx.status = reactNetwork.StatusCode.InternalServerError; ctx.state.quiltError = error; if (renderRawErrorMessage) { ctx.body = errorMessage; } else { if (renderError) { const [styles, scripts] = await Promise.all([assets.styles({ name: 'error' }), assets.scripts({ name: 'error' })]); const response = server$1.render( /*#__PURE__*/React__default["default"].createElement(server$1.Html, { manager: htmlManager, styles: styles, scripts: scripts }, renderError(ctx))); ctx.body = response; } else { ctx.body = fallbackErrorMarkup.fallbackErrorMarkup; ctx.set(reactNetwork.Header.ContentType, 'text/html'); } ctx.throw(reactNetwork.StatusCode.InternalServerError, error); } } } return compose__default["default"]([middleware.quiltDataMiddleware, sewingKitKoa.middleware({ assetPrefix, manifestPath }), renderFunction]); } function getManifestPath(root) { const gemFileExists = fs.existsSync(path.join(root, 'Gemfile')); if (!gemFileExists) { return; } // eslint-disable-next-line no-process-env return process.env.NODE_ENV === 'development' ? `tmp/sewing-kit/sewing-kit-manifest.json` : `public/bundles/sewing-kit-manifest.json`; } exports.createRender = createRender;