Skip to content

Commit

Permalink
fix: handle ssr off for subapps (#1854)
Browse files Browse the repository at this point in the history
  • Loading branch information
jchip authored Jun 25, 2021
1 parent 1f3f619 commit 94fa623
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 22 deletions.
12 changes: 8 additions & 4 deletions packages/xarc-react/src/node/react-lib-node.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
SubAppSSRData,
SubAppSSRResult,
SubAppFeatureResult,
envHooks
envHooks,
} from "@xarc/subapp";
// import { render } from "react-dom";
import { createElement, Component } from "react";
Expand All @@ -24,6 +24,10 @@ export class SSRReactLib implements ServerFrameworkLib {
await subapp._getModule();
}

if (subapp._module.loadError) {
return {};
}

const Comp = subapp._getExport<Component>()?.Component;

const featNames = Object.keys(subapp._features);
Expand All @@ -33,7 +37,7 @@ export class SSRReactLib implements ServerFrameworkLib {
const featIds = ["state-provider", "router-provider", "app-context-provider"];

for (const featId of featIds) {
const featName = featNames.find(x => subapp._features[x].id === featId);
const featName = featNames.find((x) => subapp._features[x].id === featId);
if (featName) {
const feat = subapp._features[featName];
let nextRes = feat.execute({ input: result, ssrData: data });
Expand Down Expand Up @@ -68,15 +72,15 @@ export class SSRReactLib implements ServerFrameworkLib {
if (!subapp._module) {
return {
content: `<h3>SubApp ${subapp.name} can't SSR sync because its module not yet loaded</h3>`,
props: undefined
props: undefined,
};
}

const content = renderToString(<prepResult.Component />);

return {
content,
props: prepResult.props
props: prepResult.props,
};
}
}
6 changes: 3 additions & 3 deletions packages/xarc-subapp/src/node/load-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ export function loadSubApp(_setupContext: any, { props: setupProps }): any {
subapp,
options: props,
request,
path: request.path
}
path: request.path,
},
});

return undefined;
}
},
};
}
25 changes: 19 additions & 6 deletions packages/xarc-subapp/src/node/render-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
startSubApp,
isSubAppReady,
subAppReady,
InitProps
InitProps,
} from "./index";

/**
Expand All @@ -26,6 +26,19 @@ export type RenderOptions = {

/** namespace to load the subapps */
namespace?: string;

/**
* Turn on/off server side rendering for the entire render, regardless if subapp
* wants ssr. Setting this flag to `true` will not make a subapp that sets its
* own `ssr` to `false` do SSR.
*/
ssr?: boolean;

/**
* If you only want to prepare data for when `ssr` is `true`, set this to `true`.
* This will affect all subapps in this render
*/
prepareOnly?: boolean;
};

/**
Expand All @@ -44,7 +57,7 @@ export class PageRenderer {
nonce,
prodAssetData,
devAssetData,
templateInserts: { head = {}, body = {} } = {}
templateInserts: { head = {}, body = {} } = {},
} = options;

const { charSet = "UTF-8" } = options;
Expand All @@ -66,7 +79,7 @@ ${head.end}
</head>
<body>
${body.begin}
${subApps.map(sa => TokenInvoke(loadSubApp, sa))}
${subApps.map((sa) => TokenInvoke(loadSubApp, sa))}
${body.beforeStart}
${TokenInvoke(startSubApp)}
${body.afterStart}
Expand All @@ -80,7 +93,7 @@ ${body.end}

_getSSRSubAppNames() {
const { subApps } = this._options;
return subApps.map(s => s.ssr && s.name).filter(x => x);
return subApps.map((s) => s.ssr && s.name).filter((x) => x);
}

/**
Expand All @@ -89,11 +102,11 @@ ${body.end}
* @returns Promise<RenderContext>
*/
async render(options: RenderOptions): Promise<RenderContext> {
if (!isSubAppReady()) {
if (options.ssr !== false && !isSubAppReady()) {
const ssrNames = this._getSSRSubAppNames();
// make sure the subapps this render depends on are ready
const readyNames = await subAppReady(ssrNames);
const badNames = ssrNames.filter(sn => !readyNames.includes(sn));
const badNames = ssrNames.filter((sn) => !readyNames.includes(sn));

if (badNames.length > 0) {
throw new Error(
Expand Down
43 changes: 39 additions & 4 deletions packages/xarc-subapp/src/node/server-render-pipeline.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
/* eslint-disable max-statements */
/* eslint-disable max-statements, no-console */

import _ from "lodash";
import { SubAppRenderPipeline } from "../subapp/subapp-render-pipeline";
import {
SubAppSSRData,
SubAppFeatureResult,
LoadSubAppOptions,
SubAppMountInfo
SubAppMountInfo,
} from "../subapp/types";
import { ServerFrameworkLib } from "./types";
import { safeStringifyJson } from "./utils";
// global name to store client subapp runtime, ie: window.xarcV1
// V1: version 1.
const xarc = "window.xarcV2";

function cleanStack(err) {
const stacks = err.stack.split("\n");
return stacks
.filter(
(l) =>
!l.includes("(internal/") &&
!l.includes("/isomorphic-loader/") &&
!l.includes("/pirates/lib/index")
)
.map((l) => l.replace(process.cwd(), "."))
.join("\n");
}

/**
* The server side rendering pipeline for a subapp
*
Expand Down Expand Up @@ -42,7 +56,7 @@ export class SubAppServerRenderPipeline implements SubAppRenderPipeline {

constructor(data: SubAppSSRData) {
const { context, options } = data;
this.options = options;
this.options = { ...options, ..._.pick(context.options, ["ssr", "prepareOnly"]) };
this.ssrData = data;
this.outputSpot = context.output.reserve();
this.framework = data.subapp._frameworkFactory();
Expand All @@ -56,7 +70,28 @@ export class SubAppServerRenderPipeline implements SubAppRenderPipeline {
/** start to run through all the subapp's features to prepare data for calling renderToString */
startPrepare(): void {
this.startTime = Date.now();
this.preparePromise = this.framework.prepareSSR(this.ssrData).then(result => {
this.preparePromise = this.framework.prepareSSR(this.ssrData).then((result) => {
const { subapp } = this.ssrData;
if (subapp._module?.loadError && !subapp._module.warned) {
subapp._module.warned = true;
console.error(
`Failed to getModule for subapp ${subapp.name}:`,
cleanStack(subapp._module.loadError)
);
console.error(` Originating stack:`, cleanStack(subapp._module.captureErr));
if (this.options.ssr === false || this.options.prepareOnly) {
console.error(` Things may be OK due to one of these settings: ssr: ${this.options.ssr}, prepareOnly: ${this.options.prepareOnly}
- Will continue to prepare your subapps to send to the browser
- If you know that your subapp module doesn't support SSR, then you can ignore these errors.
- You will *not* see SSR content.
`);
} else {
console.error(
`If you know your subapp doesn't support SSR, then set its ssr flag to false.`
);
}
}

return (this.prepResult = result);
});
}
Expand Down
22 changes: 17 additions & 5 deletions packages/xarc-subapp/src/subapp/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint-disable max-statements */

import { SubAppDef, SubAppOptions, SubAppFeatureFactory, SubApp } from "./types";
import { envHooks } from "./envhooks";
export * from "./types";
Expand Down Expand Up @@ -35,15 +37,16 @@ let id = 1;
*
* @returns Promise resolving to module loaded
*/
function _getModule(): Promise<any> {
async function _getModule(): Promise<any> {
/* eslint-disable no-invalid-this */
const container = envHooks.getContainer();
const subapp = container.get(this.name);

const getMod = typeof subapp.getModule === "function" ? subapp.getModule() : subapp.getModule;

if (getMod.then) {
return getMod.then(mod => {
try {
const mod = await getMod;
// get subapp def from container again in case subapp was re-declared by a reloaded module
// while getModule was in flight.
const subappB = container.get(this.name);
Expand All @@ -57,7 +60,16 @@ function _getModule(): Promise<any> {
container.updateReady();

return mod;
});
} catch (err) {
const mod = {
loadError: err,
captureErr: new Error(`load subapp ${this.name} module failed`),
warned: false,
};
const subappB = container.get(this.name);
subappB._module = mod;
return mod;
}
} else {
//
// allow specifying subapp statically (not using dynamic import)
Expand All @@ -68,7 +80,7 @@ function _getModule(): Promise<any> {
subappB._module = mod;
loadFeatures(this, subappB._getExport<unknown>()?.wantFeatures);
container.updateReady();
return Promise.resolve(mod);
return mod;
}
}

Expand Down Expand Up @@ -107,7 +119,7 @@ export function __declareSubApp(opts: SubAppOptions, override?: Partial<SubAppDe
_mount: noop,
_unmount: noop,
_renderPipelines: [],
_getExport
_getExport,
},
override,
opts
Expand Down

0 comments on commit 94fa623

Please sign in to comment.