@remix-run/server-runtime
Version:
Server runtime for Remix
123 lines (115 loc) • 4.57 kB
JavaScript
/**
* @remix-run/server-runtime v2.16.8
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
;
Object.defineProperty(exports, '__esModule', { value: true });
var router = require('@remix-run/router');
var mode = require('./mode.js');
/**
* This thing probably warrants some explanation.
*
* The whole point here is to emulate componentDidCatch for server rendering and
* data loading. It can get tricky. React can do this on component boundaries
* but doesn't support it for server rendering or data loading. We know enough
* with nested routes to be able to emulate the behavior (because we know them
* statically before rendering.)
*
* Each route can export an `ErrorBoundary`.
*
* - When rendering throws an error, the nearest error boundary will render
* (normal react componentDidCatch). This will be the route's own boundary, but
* if none is provided, it will bubble up to the parents.
* - When data loading throws an error, the nearest error boundary will render
* - When performing an action, the nearest error boundary for the action's
* route tree will render (no redirect happens)
*
* During normal react rendering, we do nothing special, just normal
* componentDidCatch.
*
* For server rendering, we mutate `renderBoundaryRouteId` to know the last
* layout that has an error boundary that tried to render. This emulates which
* layout would catch a thrown error. If the rendering fails, we catch the error
* on the server, and go again a second time with the emulator holding on to the
* information it needs to render the same error boundary as a dynamically
* thrown render error.
*
* When data loading, server or client side, we use the emulator to likewise
* hang on to the error and re-render at the appropriate layout (where a thrown
* error would have been caught by cDC).
*
* When actions throw, it all works the same. There's an edge case to be aware
* of though. Actions normally are required to redirect, but in the case of
* errors, we render the action's route with the emulator holding on to the
* error. If during this render a parent route/loader throws we ignore that new
* error and render the action's original error as deeply as possible. In other
* words, we simply ignore the new error and use the action's error in place
* because it came first, and that just wouldn't be fair to let errors cut in
* line.
*/
function sanitizeError(error, serverMode) {
if (error instanceof Error && serverMode !== mode.ServerMode.Development) {
let sanitized = new Error("Unexpected Server Error");
sanitized.stack = undefined;
return sanitized;
}
return error;
}
function sanitizeErrors(errors, serverMode) {
return Object.entries(errors).reduce((acc, [routeId, error]) => {
return Object.assign(acc, {
[routeId]: sanitizeError(error, serverMode)
});
}, {});
}
// must be type alias due to inference issues on interfaces
// https://github.com/microsoft/TypeScript/issues/15300
function serializeError(error, serverMode) {
let sanitized = sanitizeError(error, serverMode);
return {
message: sanitized.message,
stack: sanitized.stack
};
}
function serializeErrors(errors, serverMode) {
if (!errors) return null;
let entries = Object.entries(errors);
let serialized = {};
for (let [key, val] of entries) {
// Hey you! If you change this, please change the corresponding logic in
// deserializeErrors in remix-react/errors.ts :)
if (router.isRouteErrorResponse(val)) {
serialized[key] = {
...val,
__type: "RouteErrorResponse"
};
} else if (val instanceof Error) {
let sanitized = sanitizeError(val, serverMode);
serialized[key] = {
message: sanitized.message,
stack: sanitized.stack,
__type: "Error",
// If this is a subclass (i.e., ReferenceError), send up the type so we
// can re-create the same type during hydration. This will only apply
// in dev mode since all production errors are sanitized to normal
// Error instances
...(sanitized.name !== "Error" ? {
__subType: sanitized.name
} : {})
};
} else {
serialized[key] = val;
}
}
return serialized;
}
exports.sanitizeError = sanitizeError;
exports.sanitizeErrors = sanitizeErrors;
exports.serializeError = serializeError;
exports.serializeErrors = serializeErrors;