Skip to content

Commit 04c10a1

Browse files
SamTV12345claude
andcommitted
chore(docs): make prerendering React 19 compatible
react-ssr-prepass 1.6.0 (latest) reads ReactCurrentDispatcher from the React internals, which React 19 no longer exposes, so the docs prerender crashed. The docs framework only used prepass to await AsyncComponent.preload() before renderToString, and the loading-state render already kicks off preload() itself, so a Yarn patch on @patternfly/documentation-framework replaces prepass with fixpoint rendering on public React APIs: render, let the dynamic imports settle, and re-render until no loading placeholder remains. .gitignore now keeps .yarn/patches while ignoring the rest of .yarn. The same change should be upstreamed to the documentation-framework. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent fd72aa9 commit 04c10a1

4 files changed

Lines changed: 146 additions & 3 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,5 @@ lerna-debug.log
3131
.vscode
3232
# For vim
3333
*.swp
34-
.yarn
34+
.yarn/*
35+
!.yarn/patches
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
diff --git a/scripts/webpack/prerender.js b/scripts/webpack/prerender.js
2+
index 37306139a11cabc0c89e2eee637ea8af88693cd9..03f0c1d909a9bc516c5006cb4f8805c250e060bf 100644
3+
--- a/scripts/webpack/prerender.js
4+
+++ b/scripts/webpack/prerender.js
5+
@@ -1,10 +1,44 @@
6+
const React = require('react');
7+
const ReactDOMServer = require('react-dom/server');
8+
const { ServerLocation } = require('@reach/router');
9+
-const ssrPrepass = require('react-ssr-prepass');
10+
// react, react-dom, and @reach/router are all EXCLUDED from the ssr-bundle.js
11+
// The versions imported above are used instead, which allows us to use <ServerLocation>
12+
-// const ssrPrepass = require('react-ssr-prepass');
13+
+
14+
+// Let pending dynamic imports (AsyncComponent.preload) settle before re-rendering
15+
+const flushAsync = () => new Promise((resolve) => setImmediate(() => setImmediate(resolve)));
16+
+
17+
+// The loading state rendered by asyncComponentFactory while a page chunk loads
18+
+const LOADING_MARKER = 'style="height:100vh">Loading...';
19+
+
20+
+// react-ssr-prepass walked the element tree to await AsyncComponent.preload(), but it
21+
+// relies on React internals that no longer exist in React 19. AsyncComponent already
22+
+// kicks off preload() from its loading-state render, so rendering to a fixpoint with
23+
+// plain renderToString preloads the same chunks using only public React APIs.
24+
+let prerenderCount = 0;
25+
+
26+
+async function renderToStringWithPreload(element) {
27+
+ const maxPasses = 4;
28+
+ const start = Date.now();
29+
+ let passes = 1;
30+
+ let html = ReactDOMServer.renderToString(element);
31+
+ if (++prerenderCount % 25 === 0) {
32+
+ console.log(` [prerender #${prerenderCount}] heap=${Math.round(process.memoryUsage().heapUsed / 1048576)}MB`);
33+
+ }
34+
+
35+
+ for (; passes < maxPasses && html.includes(LOADING_MARKER); passes++) {
36+
+ // The first render has kicked off preload(); give the dynamic imports a chance
37+
+ // to settle, then render again until no loading placeholder is left.
38+
+ await (passes === 1 ? flushAsync() : new Promise((resolve) => setTimeout(resolve, 50)));
39+
+ html = ReactDOMServer.renderToString(element);
40+
+ }
41+
+
42+
+ const elapsed = Date.now() - start;
43+
+ if (elapsed > 5000 || html.includes(LOADING_MARKER)) {
44+
+ console.log(` rendered in ${passes} passes, ${elapsed}ms${html.includes(LOADING_MARKER) ? ' (loading placeholder left!)' : ''}`);
45+
+ }
46+
+
47+
+ return html;
48+
+}
49+
50+
// This function is effectively synchronous because it mutates global.setTimeout
51+
// Only allow one copy at a time to run
52+
@@ -44,12 +78,7 @@ async function prerender(url) {
53+
const WrappedApp = React.createElement(ServerLocation, { url },
54+
React.createElement(App)
55+
);
56+
- await ssrPrepass(WrappedApp, element => {
57+
- if (element.type.name === 'AsyncComponent') {
58+
- return element.type.preload();
59+
- }
60+
- });
61+
- const string = ReactDOMServer.renderToString(WrappedApp);
62+
+ const string = await renderToStringWithPreload(WrappedApp);
63+
64+
return string;
65+
}

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@
124124
]
125125
},
126126
"resolutions": {
127-
"dompurify": "3.4.13"
127+
"dompurify": "3.4.13",
128+
"@patternfly/documentation-framework": "patch:@patternfly/documentation-framework@npm%3A6.49.2#~/.yarn/patches/@patternfly-documentation-framework-npm-6.49.2-02e7a8d840.patch"
128129
}
129130
}

yarn.lock

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4963,7 +4963,7 @@ __metadata:
49634963
languageName: node
49644964
linkType: hard
49654965

4966-
"@patternfly/documentation-framework@npm:^6.40.0":
4966+
"@patternfly/documentation-framework@npm:6.49.2":
49674967
version: 6.49.2
49684968
resolution: "@patternfly/documentation-framework@npm:6.49.2"
49694969
dependencies:
@@ -5039,6 +5039,82 @@ __metadata:
50395039
languageName: node
50405040
linkType: hard
50415041

5042+
"@patternfly/documentation-framework@patch:@patternfly/documentation-framework@npm%3A6.49.2#~/.yarn/patches/@patternfly-documentation-framework-npm-6.49.2-02e7a8d840.patch":
5043+
version: 6.49.2
5044+
resolution: "@patternfly/documentation-framework@patch:@patternfly/documentation-framework@npm%3A6.49.2#~/.yarn/patches/@patternfly-documentation-framework-npm-6.49.2-02e7a8d840.patch::version=6.49.2&hash=e7d433"
5045+
dependencies:
5046+
"@babel/core": "npm:^7.29.0"
5047+
"@babel/preset-env": "npm:7.29.0"
5048+
"@babel/preset-react": "npm:^7.28.5"
5049+
"@mdx-js/util": "npm:1.6.22"
5050+
"@patternfly/ast-helpers": "npm:^1.26.2"
5051+
"@reach/router": "npm:@gatsbyjs/reach-router@1.3.9"
5052+
"@rspack/core": "npm:^1.7.7"
5053+
"@rspack/dev-server": "npm:^1.2.1"
5054+
autoprefixer: "npm:10.4.27"
5055+
babel-loader: "npm:^10.0.0"
5056+
camelcase-css: "npm:2.0.1"
5057+
chokidar: "npm:4.0.3"
5058+
codesandbox: "npm:2.2.3"
5059+
commander: "npm:4.1.1"
5060+
css-loader: "npm:6.11.0"
5061+
detab: "npm:2.0.4"
5062+
express: "npm:4.22.1"
5063+
file-loader: "npm:6.2.0"
5064+
file-saver: "npm:1.3.8"
5065+
fs-extra: "npm:9.1.0"
5066+
glob: "npm:12.0.0"
5067+
handlebars: "npm:4.7.8"
5068+
hast-to-hyperscript: "npm:9.0.1"
5069+
hast-util-to-text: "npm:2.0.1"
5070+
html-formatter: "npm:0.1.9"
5071+
html-webpack-plugin: "npm:5.6.6"
5072+
js-yaml: "npm:4.1.1"
5073+
mdast-util-to-hast: "npm:9.1.2"
5074+
mdurl: "npm:1.0.1"
5075+
null-loader: "npm:4.0.1"
5076+
parse-entities: "npm:2.0.0"
5077+
path-browserify: "npm:1.0.1"
5078+
postcss: "npm:^8.5.8"
5079+
postcss-loader: "npm:7.3.4"
5080+
process: "npm:^0.11.10"
5081+
puppeteer: "npm:^24.37.5"
5082+
puppeteer-cluster: "npm:^0.25.0"
5083+
react-docgen: "npm:6.0.4"
5084+
react-ssr-prepass: "npm:1.6.0"
5085+
remark-footnotes: "npm:1.0.0"
5086+
remark-frontmatter: "npm:2.0.0"
5087+
remark-mdx: "npm:2.0.0-next.8"
5088+
remark-mdxjs: "npm:2.0.0-next.8"
5089+
remark-parse: "npm:8.0.3"
5090+
remark-squeeze-paragraphs: "npm:4.0.0"
5091+
sharp: "npm:0.34.5"
5092+
style-to-object: "npm:0.4.4"
5093+
to-vfile: "npm:6.1.0"
5094+
typedoc: "npm:0.23.28"
5095+
typescript: "npm:4.9.5"
5096+
unified: "npm:9.2.2"
5097+
unist-util-remove: "npm:2.1.0"
5098+
unist-util-visit: "npm:2.0.3"
5099+
url-loader: "npm:4.1.1"
5100+
vfile-reporter: "npm:6.0.2"
5101+
webpack-bundle-analyzer: "npm:5.2.0"
5102+
webpack-dev-server: "npm:5.2.3"
5103+
webpack-merge: "npm:5.10.0"
5104+
peerDependencies:
5105+
"@patternfly/patternfly": ^6.6.0
5106+
"@patternfly/react-code-editor": ^6.6.0
5107+
"@patternfly/react-core": ^6.6.0
5108+
"@patternfly/react-icons": ^6.6.0
5109+
"@patternfly/react-table": ^6.6.0
5110+
react: ^17.0.0 || ^18.0.0
5111+
react-dom: ^17.0.0 || ^18.0.0
5112+
bin:
5113+
pf-docs-framework: scripts/cli/cli.js
5114+
checksum: 10c0/315007bd0c1b780c3f789027c8443267e30564fc4b6fc31f14d06dca1cbed8907b7b5bfa006c3149803365c58ee1380df09f5f10e0994426771bb39516418ef3
5115+
languageName: node
5116+
linkType: hard
5117+
50425118
"@patternfly/patternfly-a11y@npm:5.2.1":
50435119
version: 5.2.1
50445120
resolution: "@patternfly/patternfly-a11y@npm:5.2.1"

0 commit comments

Comments
 (0)