@shopify/react-server
Version:
Utilities for React server-side rendering
134 lines (118 loc) • 3.94 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var path = require('path');
var webpack = require('webpack');
var VirtualModulesPlugin = require('webpack-virtual-modules');
var shared = require('./shared.js');
var utilities = require('./error/utilities.js');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var VirtualModulesPlugin__default = /*#__PURE__*/_interopDefaultLegacy(VirtualModulesPlugin);
/**
* A webpack plugin that generates default server and client entrypoints if none are present.
* @param config
* @returns a customized webpack plugin
*/
class ReactServerPlugin {
constructor({
host,
port,
assetPrefix,
basePath = '.',
proxy = false
} = {}) {
this.options = {
basePath,
host,
port,
assetPrefix,
proxy
};
}
apply(compiler) {
const modules = this.modules(compiler);
const virtualModules = new VirtualModulesPlugin__default["default"](modules);
virtualModules.apply(compiler);
const ignorePaths = Object.keys(modules).concat(compiler.options.watchOptions.ignored || []);
const watchIgnore = new webpack.WatchIgnorePlugin({
paths: ignorePaths
});
watchIgnore.apply(compiler);
}
modules(compiler) {
const {
basePath
} = this.options;
const modules = {};
if (shared.noSourceExists(shared.Entrypoint.Client, this.options, compiler)) {
const file = path.join(basePath, `${shared.Entrypoint.Client}.js`);
modules[file] = clientSource();
}
if (shared.noSourceExists(shared.Entrypoint.Server, this.options, compiler)) {
const file = path.join(basePath, `${shared.Entrypoint.Server}.js`);
modules[file] = serverSource(this.options, compiler);
}
if (utilities.errorSSRComponentExists(this.options, compiler)) {
const file = path.join(basePath, `${shared.Entrypoint.Error}.entry.client.js`);
modules[file] = utilities.errorClientSource();
}
return modules;
}
}
function serverSource(options, compiler) {
const {
port,
host,
assetPrefix,
proxy
} = options;
return `
${shared.HEADER}
import React from 'react';
import {createServer} from '@shopify/react-server';
import App from 'index';
${utilities.errorSSRComponentExists(options, compiler) ? "import Error from 'error';" : ''}
process.on('uncaughtException', logError);
process.on('unhandledRejection', logError);
function logError(error) {
const errorLog = \`\${error.stack || error.message || 'No stack trace was present'}\`;
console.log(\`React Server failed to start.\n\${errorLog}\`);
process.exit(1);
}
const render = (ctx) => {
return React.createElement(App, {
url: ctx.request.URL,
data: ctx.state.quiltData,
});
}
const app = createServer({
port: ${port},
ip: ${JSON.stringify(host)},
assetPrefix: ${JSON.stringify(assetPrefix)},
proxy: ${proxy},
render,
${utilities.errorSSRComponentExists(options, compiler) ? `renderError: (ctx) => {
return React.createElement(Error, {
url: ctx.request.url,
data: ctx.state.quiltData,
error: ctx.state.quiltError,
});
}` : ''}
});
export default app;
`;
}
function clientSource() {
return `
${shared.HEADER}
import React from 'react';
import ReactDOM from 'react-dom/client';
import {showPage, getSerialized} from '@shopify/react-html';
import App from 'index';
const appContainer = document.getElementById('app');
const data = getSerialized('quilt-data');
const url = new URL(window.location.href);
ReactDOM.hydrateRoot(appContainer, React.createElement(App, {data, url}));
showPage();
`;
}
exports.ReactServerPlugin = ReactServerPlugin;