ilc-adapter-react
Version:
A single spa plugin for React apps
136 lines (135 loc) • 7.35 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.IlcAdapterReact = exports.SingleSpaContext = void 0;
exports.default = ilcAdapterReact;
const react_1 = __importDefault(require("react"));
const client_1 = require("react-dom/client");
const errors_1 = require("./errors");
const AdapterErrorBoundary_1 = __importDefault(require("./AdapterErrorBoundary"));
// React context that gives any react component the single-spa props
exports.SingleSpaContext = react_1.default.createContext({});
class IlcAdapterReact {
constructor(userOpts) {
this.domElements = {};
this.reactRoots = {};
this.bootstrap = (props) => __awaiter(this, void 0, void 0, function* () {
if ('rootComponent' in this.userOpts) {
// This is a class or stateless function component
this.rootComponent = this.userOpts.rootComponent;
}
else if (this.userOpts.loadRootComponent) {
// They passed a promise that resolves with the react component. Wait for it to resolve before mounting
this.rootComponent = yield this.userOpts.loadRootComponent(props);
}
else if (!this.rootComponent) {
throw new Error(`ilc-adapter-react: must be passed opts.rootComponent or opts.loadRootComponent`);
}
});
this.mount = (props) => __awaiter(this, void 0, void 0, function* () {
if (!this.rootComponent) {
throw new errors_1.IlcAdapterError(`ilc-adapter-react: Looks like "mount" was called before completion of the "bootstrap"`);
}
const domElement = this.chooseDomElementGetter(props)();
if (!domElement) {
throw new errors_1.IlcAdapterError(`ilc-adapter-react: domElementGetter function for application '${props.name}' did not return a valid dom element. Please pass a valid domElement or domElementGetter via opts or props`);
}
const elementToRender = this.getElementToRender(this.rootComponent, props);
let reactRoot = this.reactRoots[props.name];
reactRoot = this.reactDomRender(elementToRender, domElement, !!reactRoot, reactRoot, props);
this.reactRoots[props.name] = reactRoot;
this.domElements[props.name] = domElement;
return reactRoot;
});
this.unmount = (props) => __awaiter(this, void 0, void 0, function* () {
const reactRoot = this.reactRoots[props.name];
if (reactRoot) {
reactRoot.unmount();
delete this.reactRoots[props.name];
delete this.domElements[props.name];
}
});
this.update = (props) => __awaiter(this, void 0, void 0, function* () {
if (!this.rootComponent) {
throw new errors_1.IlcAdapterError(`ilc-adapter-react: Looks like "update" was called before completion of the "bootstrap"`);
}
const domElement = this.domElements[props.name];
if (domElement === undefined) {
throw new errors_1.IlcAdapterError(`ilc-adapter-react: Looks like "update" was called before "mount" or after "unmount"`);
}
const elementToRender = this.getElementToRender(this.rootComponent, props);
const reactRoot = this.reactRoots[props.name];
if (reactRoot === undefined) {
throw new errors_1.IlcAdapterError(`ilc-adapter-react: Looks like "update" was called before "mount" or after "unmount". Root node is not defined`);
}
return this.reactDomRender(elementToRender, domElement, true, reactRoot, props);
});
if (typeof userOpts !== 'object') {
throw new Error(`ilc-adapter-react requires a configuration object`);
}
this.userOpts = Object.assign(Object.assign({}, userOpts), { parcelCanUpdate: true });
if (this.userOpts.parcelCanUpdate === false) {
this.update = undefined;
}
}
chooseDomElementGetter(props) {
if ('domElement' in props) {
return () => props.domElement;
}
else if (props.domElementGetter) {
if (typeof props.domElementGetter !== 'function') {
throw new Error(`ilc-adapter-react: the domElementGetter for react application '${props.name}' is not a function`);
}
return props.domElementGetter;
}
throw new errors_1.IlcAdapterError(`ilc-adapter-react: Unable to identify DOM node to app/parcel mount`);
}
getElementToRender(component, props) {
const rootComponentElement = react_1.default.createElement(component, props);
const errorBoundary = this.userOpts.errorBoundary
? (caughtError, caughtErrorInfo) => this.userOpts.errorBoundary(caughtError, caughtErrorInfo, props)
: undefined;
const errorHandler = props.errorHandler
? props.errorHandler
: (error) => {
console.error(`ilc-adapter-react: app or parcel "${props.name}" have thrown an uncaught error:`, error);
};
return (react_1.default.createElement(exports.SingleSpaContext.Provider, { value: props },
react_1.default.createElement(AdapterErrorBoundary_1.default, { onError: errorHandler, errorBoundary: errorBoundary }, rootComponentElement)));
}
reactDomRender(elementToRender, domElement, forceRender = false, reactRoot, props) {
if (!forceRender && domElement.childElementCount > 0) {
//We're likely rendering app after SSR
const { onRecoverableError } = this.userOpts;
const onRecoverableErrorWrapped = onRecoverableError
? (error, errorInfo) => onRecoverableError(error, errorInfo, props)
: undefined;
return (0, client_1.hydrateRoot)(domElement, elementToRender, { onRecoverableError: onRecoverableErrorWrapped });
}
// default to this if 'renderType' is null or doesn't match the other options
if (!reactRoot) {
const root = (0, client_1.createRoot)(domElement);
root.render(elementToRender);
return root;
}
else {
reactRoot.render(elementToRender);
return reactRoot;
}
}
}
exports.IlcAdapterReact = IlcAdapterReact;
function ilcAdapterReact(userOpts) {
return new IlcAdapterReact(userOpts);
}