@shopify/react-server
Version:
Utilities for React server-side rendering
155 lines (147 loc) • 6.17 kB
JavaScript
;
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;