@samitouri / QOS-React / commits / 0eaca37565

Add script to generate inline Fizz runtime (#25481)

* Move Fizz inline instructions to unified module Instead of a separate module per instruction, this exports all of them from a unified module. In the next step, I'll add a script to generate this new module. * Add script to generate inline Fizz runtime This adds a script to generate the inline Fizz runtime. Previously, the runtime source was in an inline comment, and a compiled version of the instructions were hardcoded as strings into the Fizz implementation, where they are injected into the HTML stream. I've moved the source for the instructions to a regular JavaScript module. A script compiles the instructions with Closure, then generates another module that exports the compiled instructions as strings. Then the Fizz runtime imports the instructions from the generated module. To build the instructions, run: yarn generate-inline-fizz-runtime In the next step, I'll add a CI check to verify that the generated files are up to date. * Check in CI if generated Fizz runtime is in sync The generated Fizz runtime is checked into source. In CI, we'll ensure it stays in sync by running the script and confirming nothing changed.

Andrew Clark committed Oct 14, 2022 at 21:00 UTC 0eaca37565b3aeb0d967bf403029a2d39d831ad4
14 files changed +369 -256
.circleci/config.yml
+16
@@ -367,6 +367,19 @@ jobs:
367 command: |
368 yarn extract-errors
369 git diff --quiet || (echo "Found unminified errors. Either update the error codes map or disable error minification for the affected build, if appropriate." && false)
370 +
371 + check_generated_fizz_runtime:
372 + docker: *docker
373 + environment: *environment
374 + steps:
375 + - checkout
376 + - attach_workspace: *attach_workspace
377 + - *restore_node_modules
378 + - run:
379 + name: Confirm generated inline Fizz runtime is up to date
380 + command: |
381 + yarn generate-inline-fizz-runtime
382 + git diff --quiet || (echo "There was a change to the Fizz runtime. Run `yarn generate-inline-fizz-runtime` and check in the result." && false)
383
384 yarn_test:
385 docker: *docker
@@ -494,6 +507,9 @@ workflows:
507 - sync_reconciler_forks:
508 requires:
509 - setup
510 + - check_generated_fizz_runtime:
511 + requires:
512 + - setup
513 - yarn_lint:
514 requires:
515 - setup
package.json
+2 -1
@@ -145,7 +145,8 @@
145 "download-build": "node ./scripts/release/download-experimental-build.js",
146 "download-build-for-head": "node ./scripts/release/download-experimental-build.js --commit=$(git rev-parse HEAD)",
147 "download-build-in-codesandbox-ci": "cd scripts/release && yarn install && cd ../../ && yarn download-build-for-head || yarn build-combined --type=node react/index react-dom/index react-dom/src/server react-dom/test-utils scheduler/index react/jsx-runtime react/jsx-dev-runtime",
148 - "check-release-dependencies": "node ./scripts/release/check-release-dependencies"
148 + "check-release-dependencies": "node ./scripts/release/check-release-dependencies",
149 + "generate-inline-fizz-runtime": "node ./scripts/rollup/generate-inline-fizz-runtime.js"
150 },
151 "resolutions": {
152 "react-is": "npm:react-is"
packages/react-dom-bindings/src/server/ReactDOMServerFormatConfig.js
+6 -4
@@ -74,10 +74,12 @@ export {
74 hoistResourcesToRoot,
75 } from './ReactDOMFloatServer';
76
77 -import completeSegmentFunction from './fizz-instruction-set/completeSegmentFunctionString';
78 -import completeBoundaryFunction from './fizz-instruction-set/completeBoundaryFunctionString';
79 -import styleInsertionFunction from './fizz-instruction-set/styleInsertionFunctionString';
80 -import clientRenderFunction from './fizz-instruction-set/clientRenderFunctionString';
77 +import {
78 + clientRenderBoundary as clientRenderFunction,
79 + completeBoundary as completeBoundaryFunction,
80 + completeBoundaryWithStyles as styleInsertionFunction,
81 + completeSegment as completeSegmentFunction,
82 +} from './fizz-instruction-set/ReactDOMFizzInstructionSetInlineCodeStrings';
83
84 import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals';
85 const ReactDOMCurrentDispatcher = ReactDOMSharedInternals.Dispatcher;
packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineClientRenderBoundary.js new
+5
@@ -0,0 +1,5 @@
1 +import {clientRenderBoundary} from './ReactDOMFizzInstructionSet';
2 +
3 +// This is a string so Closure's advanced compilation mode doesn't mangle it.
4 +// eslint-disable-next-line dot-notation
5 +window['$RX'] = clientRenderBoundary;
packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineCompleteBoundary.js new
+5
@@ -0,0 +1,5 @@
1 +import {completeBoundary} from './ReactDOMFizzInstructionSet';
2 +
3 +// This is a string so Closure's advanced compilation mode doesn't mangle it.
4 +// eslint-disable-next-line dot-notation
5 +window['$RC'] = completeBoundary;
packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineCompleteBoundaryWithStyles.js new
+7
@@ -0,0 +1,7 @@
1 +import {completeBoundaryWithStyles} from './ReactDOMFizzInstructionSet';
2 +
3 +// This is a string so Closure's advanced compilation mode doesn't mangle it.
4 +// eslint-disable-next-line dot-notation
5 +window['$RM'] = new Map();
6 +// eslint-disable-next-line dot-notation
7 +window['$RR'] = completeBoundaryWithStyles;
packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineCompleteSegment.js new
+5
@@ -0,0 +1,5 @@
1 +import {completeSegment} from './ReactDOMFizzInstructionSet';
2 +
3 +// This is a string so Closure's advanced compilation mode doesn't mangle it.
4 +// eslint-disable-next-line dot-notation
5 +window['$RS'] = completeSegment;
packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSet.js new
+224
@@ -0,0 +1,224 @@
1 +/* eslint-disable dot-notation */
2 +
3 +const COMMENT_NODE = 8;
4 +const SUSPENSE_START_DATA = '$';
5 +const SUSPENSE_END_DATA = '/$';
6 +const SUSPENSE_PENDING_START_DATA = '$?';
7 +const SUSPENSE_FALLBACK_START_DATA = '$!';
8 +const LOADED = 'l';
9 +const ERRORED = 'e';
10 +
11 +// TODO: Symbols that are referenced outside this module use dynamic accessor
12 +// notation instead of dot notation to prevent Closure's advanced compilation
13 +// mode from renaming. We could use extern files instead, but I couldn't get it
14 +// working. Closure converts it to a dot access anyway, though, so it's not an
15 +// urgent issue.
16 +
17 +export function clientRenderBoundary(
18 + suspenseBoundaryID,
19 + errorDigest,
20 + errorMsg,
21 + errorComponentStack,
22 +) {
23 + // Find the fallback's first element.
24 + const suspenseIdNode = document.getElementById(suspenseBoundaryID);
25 + if (!suspenseIdNode) {
26 + // The user must have already navigated away from this tree.
27 + // E.g. because the parent was hydrated.
28 + return;
29 + }
30 + // Find the boundary around the fallback. This is always the previous node.
31 + const suspenseNode = suspenseIdNode.previousSibling;
32 + // Tag it to be client rendered.
33 + suspenseNode.data = SUSPENSE_FALLBACK_START_DATA;
34 + // assign error metadata to first sibling
35 + const dataset = suspenseIdNode.dataset;
36 + if (errorDigest) dataset['dgst'] = errorDigest;
37 + if (errorMsg) dataset['msg'] = errorMsg;
38 + if (errorComponentStack) dataset['stck'] = errorComponentStack;
39 + // Tell React to retry it if the parent already hydrated.
40 + if (suspenseNode['_reactRetry']) {
41 + suspenseNode['_reactRetry']();
42 + }
43 +}
44 +
45 +export function completeBoundaryWithStyles(
46 + suspenseBoundaryID,
47 + contentID,
48 + styles,
49 +) {
50 + // TODO: In the non-inline version of the runtime, these don't need to be read
51 + // from the global scope.
52 + const completeBoundaryImpl = window['$RC'];
53 + const resourceMap = window['$RM'];
54 +
55 + const precedences = new Map();
56 + const thisDocument = document;
57 + let lastResource, node;
58 +
59 + // Seed the precedence list with existing resources
60 + const nodes = thisDocument.querySelectorAll('link[data-rprec]');
61 + for (let i = 0; (node = nodes[i++]); ) {
62 + precedences.set(node.dataset['rprec'], (lastResource = node));
63 + }
64 +
65 + let i = 0;
66 + const dependencies = [];
67 + let style, href, precedence, attr, loadingState, resourceEl;
68 +
69 + function setStatus(s) {
70 + this['s'] = s;
71 + }
72 +
73 + while ((style = styles[i++])) {
74 + let j = 0;
75 + href = style[j++];
76 + // We check if this resource is already in our resourceMap and reuse it if so.
77 + // If it is already loaded we don't return it as a depenendency since there is nothing
78 + // to wait for
79 + loadingState = resourceMap.get(href);
80 + if (loadingState) {
81 + if (loadingState['s'] !== 'l') {
82 + dependencies.push(loadingState);
83 + }
84 + continue;
85 + }
86 +
87 + // We construct our new resource element, looping over remaining attributes if any
88 + // setting them to the Element.
89 + resourceEl = thisDocument.createElement('link');
90 + resourceEl.href = href;
91 + resourceEl.rel = 'stylesheet';
92 + resourceEl.dataset['rprec'] = precedence = style[j++];
93 + while ((attr = style[j++])) {
94 + resourceEl.setAttribute(attr, style[j++]);
95 + }
96 +
97 + // We stash a pending promise in our map by href which will resolve or reject
98 + // when the underlying resource loads or errors. We add it to the dependencies
99 + // array to be returned.
100 + loadingState = resourceEl['_p'] = new Promise((re, rj) => {
101 + resourceEl.onload = re;
102 + resourceEl.onerror = rj;
103 + });
104 + loadingState.then(
105 + setStatus.bind(loadingState, LOADED),
106 + setStatus.bind(loadingState, ERRORED),
107 + );
108 + resourceMap.set(href, loadingState);
109 + dependencies.push(loadingState);
110 +
111 + // The prior style resource is the last one placed at a given
112 + // precedence or the last resource itself which may be null.
113 + // We grab this value and then update the last resource for this
114 + // precedence to be the inserted element, updating the lastResource
115 + // pointer if needed.
116 + const prior = precedences.get(precedence) || lastResource;
117 + if (prior === lastResource) {
118 + lastResource = resourceEl;
119 + }
120 + precedences.set(precedence, resourceEl);
121 +
122 + // Finally, we insert the newly constructed instance at an appropriate location
123 + // in the Document.
124 + if (prior) {
125 + prior.parentNode.insertBefore(resourceEl, prior.nextSibling);
126 + } else {
127 + const head = thisDocument.head;
128 + head.insertBefore(resourceEl, head.firstChild);
129 + }
130 + }
131 +
132 + Promise.all(dependencies).then(
133 + completeBoundaryImpl.bind(null, suspenseBoundaryID, contentID, ''),
134 + completeBoundaryImpl.bind(
135 + null,
136 + suspenseBoundaryID,
137 + contentID,
138 + 'Resource failed to load',
139 + ),
140 + );
141 +}
142 +
143 +export function completeBoundary(suspenseBoundaryID, contentID, errorDigest) {
144 + const contentNode = document.getElementById(contentID);
145 + // We'll detach the content node so that regardless of what happens next we don't leave in the tree.
146 + // This might also help by not causing recalcing each time we move a child from here to the target.
147 + contentNode.parentNode.removeChild(contentNode);
148 +
149 + // Find the fallback's first element.
150 + const suspenseIdNode = document.getElementById(suspenseBoundaryID);
151 + if (!suspenseIdNode) {
152 + // The user must have already navigated away from this tree.
153 + // E.g. because the parent was hydrated. That's fine there's nothing to do
154 + // but we have to make sure that we already deleted the container node.
155 + return;
156 + }
157 + // Find the boundary around the fallback. This is always the previous node.
158 + const suspenseNode = suspenseIdNode.previousSibling;
159 +
160 + if (!errorDigest) {
161 + // Clear all the existing children. This is complicated because
162 + // there can be embedded Suspense boundaries in the fallback.
163 + // This is similar to clearSuspenseBoundary in ReactDOMHostConfig.
164 + // TODO: We could avoid this if we never emitted suspense boundaries in fallback trees.
165 + // They never hydrate anyway. However, currently we support incrementally loading the fallback.
166 + const parentInstance = suspenseNode.parentNode;
167 + let node = suspenseNode.nextSibling;
168 + let depth = 0;
169 + do {
170 + if (node && node.nodeType === COMMENT_NODE) {
171 + const data = node.data;
172 + if (data === SUSPENSE_END_DATA) {
173 + if (depth === 0) {
174 + break;
175 + } else {
176 + depth--;
177 + }
178 + } else if (
179 + data === SUSPENSE_START_DATA ||
180 + data === SUSPENSE_PENDING_START_DATA ||
181 + data === SUSPENSE_FALLBACK_START_DATA
182 + ) {
183 + depth++;
184 + }
185 + }
186 +
187 + const nextNode = node.nextSibling;
188 + parentInstance.removeChild(node);
189 + node = nextNode;
190 + } while (node);
191 +
192 + const endOfBoundary = node;
193 +
194 + // Insert all the children from the contentNode between the start and end of suspense boundary.
195 + while (contentNode.firstChild) {
196 + parentInstance.insertBefore(contentNode.firstChild, endOfBoundary);
197 + }
198 +
199 + suspenseNode.data = SUSPENSE_START_DATA;
200 + } else {
201 + suspenseNode.data = SUSPENSE_FALLBACK_START_DATA;
202 + suspenseIdNode.setAttribute('data-dgst', errorDigest);
203 + }
204 +
205 + if (suspenseNode['_reactRetry']) {
206 + suspenseNode['_reactRetry']();
207 + }
208 +}
209 +
210 +export function completeSegment(containerID, placeholderID) {
211 + const segmentContainer = document.getElementById(containerID);
212 + const placeholderNode = document.getElementById(placeholderID);
213 + // We always expect both nodes to exist here because, while we might
214 + // have navigated away from the main tree, we still expect the detached
215 + // tree to exist.
216 + segmentContainer.parentNode.removeChild(segmentContainer);
217 + while (segmentContainer.firstChild) {
218 + placeholderNode.parentNode.insertBefore(
219 + segmentContainer.firstChild,
220 + placeholderNode,
221 + );
222 + }
223 + placeholderNode.parentNode.removeChild(placeholderNode);
224 +}
packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetInlineCodeStrings.js new
+11
@@ -0,0 +1,11 @@
1 +// This is a generated file. The source files are in react-dom-bindings/src/server/fizz-instruction-set.
2 +// The build script is at scripts/rollup/generate-inline-fizz-runtime.js.
3 +// Run `yarn generate-inline-fizz-runtime` to generate.
4 +export const clientRenderBoundary =
5 + '$RX=function(b,c,d,e){var a=document.getElementById(b);a&&(b=a.previousSibling,b.data="$!",a=a.dataset,c&&(a.dgst=c),d&&(a.msg=d),e&&(a.stck=e),b._reactRetry&&b._reactRetry())};';
6 +export const completeBoundary =
7 + '$RC=function(b,c,e){c=document.getElementById(c);c.parentNode.removeChild(c);var a=document.getElementById(b);if(a){b=a.previousSibling;if(e)b.data="$!",a.setAttribute("data-dgst",e);else{e=b.parentNode;a=b.nextSibling;var f=0;do{if(a&&8===a.nodeType){var d=a.data;if("/$"===d)if(0===f)break;else f--;else"$"!==d&&"$?"!==d&&"$!"!==d||f++}d=a.nextSibling;e.removeChild(a);a=d}while(a);for(;c.firstChild;)e.insertBefore(c.firstChild,a);b.data="$"}b._reactRetry&&b._reactRetry()}};';
8 +export const completeBoundaryWithStyles =
9 + '$RM=new Map;\n$RR=function(p,q,v){function r(l){this.s=l}for(var t=$RC,u=$RM,m=new Map,n=document,g,e,f=n.querySelectorAll("link[data-rprec]"),d=0;e=f[d++];)m.set(e.dataset.rprec,g=e);e=0;f=[];for(var c,h,b,a;c=v[e++];){var k=0;h=c[k++];if(b=u.get(h))"l"!==b.s&&f.push(b);else{a=n.createElement("link");a.href=h;a.rel="stylesheet";for(a.dataset.rprec=d=c[k++];b=c[k++];)a.setAttribute(b,c[k++]);b=a._p=new Promise(function(l,w){a.onload=l;a.onerror=w});b.then(r.bind(b,"l"),r.bind(b,"e"));u.set(h,\nb);f.push(b);c=m.get(d)||g;c===g&&(g=a);m.set(d,a);c?c.parentNode.insertBefore(a,c.nextSibling):(d=n.head,d.insertBefore(a,d.firstChild))}}Promise.all(f).then(t.bind(null,p,q,""),t.bind(null,p,q,"Resource failed to load"))};';
10 +export const completeSegment =
11 + '$RS=function(a,b){a=document.getElementById(a);b=document.getElementById(b);for(a.parentNode.removeChild(a);a.firstChild;)b.parentNode.insertBefore(a.firstChild,b);b.parentNode.removeChild(b)};';
packages/react-dom-bindings/src/server/fizz-instruction-set/clientRenderFunctionString.js deleted
-38
@@ -1,38 +0,0 @@
1 -// Instruction Set
2 -
3 -// The following code is the source scripts that we then minify and inline below,
4 -// with renamed function names that we hope don't collide:
5 -
6 -// const COMMENT_NODE = 8;
7 -// const SUSPENSE_START_DATA = '$';
8 -// const SUSPENSE_END_DATA = '/$';
9 -// const SUSPENSE_PENDING_START_DATA = '$?';
10 -// const SUSPENSE_FALLBACK_START_DATA = '$!';
11 -// const LOADED = 'l';
12 -// const ERRORED = 'e';
13 -
14 -// function clientRenderBoundary(suspenseBoundaryID, errorDigest, errorMsg, errorComponentStack) {
15 -// // Find the fallback's first element.
16 -// const suspenseIdNode = document.getElementById(suspenseBoundaryID);
17 -// if (!suspenseIdNode) {
18 -// // The user must have already navigated away from this tree.
19 -// // E.g. because the parent was hydrated.
20 -// return;
21 -// }
22 -// // Find the boundary around the fallback. This is always the previous node.
23 -// const suspenseNode = suspenseIdNode.previousSibling;
24 -// // Tag it to be client rendered.
25 -// suspenseNode.data = SUSPENSE_FALLBACK_START_DATA;
26 -// // assign error metadata to first sibling
27 -// let dataset = suspenseIdNode.dataset;
28 -// if (errorDigest) dataset.dgst = errorDigest;
29 -// if (errorMsg) dataset.msg = errorMsg;
30 -// if (errorComponentStack) dataset.stck = errorComponentStack;
31 -// // Tell React to retry it if the parent already hydrated.
32 -// if (suspenseNode._reactRetry) {
33 -// suspenseNode._reactRetry();
34 -// }
35 -// }
36 -
37 -// TODO: Generate this file with a build step.
38 -export default 'function $RX(b,c,d,e){var a=document.getElementById(b);a&&(b=a.previousSibling,b.data="$!",a=a.dataset,c&&(a.dgst=c),d&&(a.msg=d),e&&(a.stck=e),b._reactRetry&&b._reactRetry())}';
packages/react-dom-bindings/src/server/fizz-instruction-set/completeBoundaryFunctionString.js deleted
-82
@@ -1,82 +0,0 @@
1 -// Instruction Set
2 -
3 -// The following code is the source scripts that we then minify and inline below,
4 -// with renamed function names that we hope don't collide:
5 -
6 -// const COMMENT_NODE = 8;
7 -// const SUSPENSE_START_DATA = '$';
8 -// const SUSPENSE_END_DATA = '/$';
9 -// const SUSPENSE_PENDING_START_DATA = '$?';
10 -// const SUSPENSE_FALLBACK_START_DATA = '$!';
11 -// const LOADED = 'l';
12 -// const ERRORED = 'e';
13 -
14 -// function completeBoundary(suspenseBoundaryID, contentID, errorDigest) {
15 -// const contentNode = document.getElementById(contentID);
16 -// // We'll detach the content node so that regardless of what happens next we don't leave in the tree.
17 -// // This might also help by not causing recalcing each time we move a child from here to the target.
18 -// contentNode.parentNode.removeChild(contentNode);
19 -
20 -// // Find the fallback's first element.
21 -// const suspenseIdNode = document.getElementById(suspenseBoundaryID);
22 -// if (!suspenseIdNode) {
23 -// // The user must have already navigated away from this tree.
24 -// // E.g. because the parent was hydrated. That's fine there's nothing to do
25 -// // but we have to make sure that we already deleted the container node.
26 -// return;
27 -// }
28 -// // Find the boundary around the fallback. This is always the previous node.
29 -// const suspenseNode = suspenseIdNode.previousSibling;
30 -
31 -// if (!errorDigest) {
32 -// // Clear all the existing children. This is complicated because
33 -// // there can be embedded Suspense boundaries in the fallback.
34 -// // This is similar to clearSuspenseBoundary in ReactDOMHostConfig.
35 -// // TODO: We could avoid this if we never emitted suspense boundaries in fallback trees.
36 -// // They never hydrate anyway. However, currently we support incrementally loading the fallback.
37 -// const parentInstance = suspenseNode.parentNode;
38 -// let node = suspenseNode.nextSibling;
39 -// let depth = 0;
40 -// do {
41 -// if (node && node.nodeType === COMMENT_NODE) {
42 -// const data = node.data;
43 -// if (data === SUSPENSE_END_DATA) {
44 -// if (depth === 0) {
45 -// break;
46 -// } else {
47 -// depth--;
48 -// }
49 -// } else if (
50 -// data === SUSPENSE_START_DATA ||
51 -// data === SUSPENSE_PENDING_START_DATA ||
52 -// data === SUSPENSE_FALLBACK_START_DATA
53 -// ) {
54 -// depth++;
55 -// }
56 -// }
57 -
58 -// const nextNode = node.nextSibling;
59 -// parentInstance.removeChild(node);
60 -// node = nextNode;
61 -// } while (node);
62 -
63 -// const endOfBoundary = node;
64 -
65 -// // Insert all the children from the contentNode between the start and end of suspense boundary.
66 -// while (contentNode.firstChild) {
67 -// parentInstance.insertBefore(contentNode.firstChild, endOfBoundary);
68 -// }
69 -
70 -// suspenseNode.data = SUSPENSE_START_DATA;
71 -// } else {
72 -// suspenseNode.data = SUSPENSE_FALLBACK_START_DATA;
73 -// suspenseIdNode.setAttribute('data-dgst', errorDigest)
74 -// }
75 -
76 -// if (suspenseNode._reactRetry) {
77 -// suspenseNode._reactRetry();
78 -// }
79 -// }
80 -
81 -// TODO: Generate this file with a build step.
82 -export default 'function $RC(b,c,d){c=document.getElementById(c);c.parentNode.removeChild(c);var a=document.getElementById(b);if(a){b=a.previousSibling;if(d)b.data="$!",a.setAttribute("data-dgst",d);else{d=b.parentNode;a=b.nextSibling;var e=0;do{if(a&&a.nodeType===8){var h=a.data;if(h==="/$")if(0===e)break;else e--;else h!=="$"&&h!=="$?"&&h!=="$!"||e++}h=a.nextSibling;d.removeChild(a);a=h}while(a);for(;c.firstChild;)d.insertBefore(c.firstChild,a);b.data="$"}b._reactRetry&&b._reactRetry()}}';
packages/react-dom-bindings/src/server/fizz-instruction-set/completeSegmentFunctionString.js deleted
-31
@@ -1,31 +0,0 @@
1 -// Instruction Set
2 -
3 -// The following code is the source scripts that we then minify and inline below,
4 -// with renamed function names that we hope don't collide:
5 -
6 -// const COMMENT_NODE = 8;
7 -// const SUSPENSE_START_DATA = '$';
8 -// const SUSPENSE_END_DATA = '/$';
9 -// const SUSPENSE_PENDING_START_DATA = '$?';
10 -// const SUSPENSE_FALLBACK_START_DATA = '$!';
11 -// const LOADED = 'l';
12 -// const ERRORED = 'e';
13 -
14 -// function completeSegment(containerID, placeholderID) {
15 -// const segmentContainer = document.getElementById(containerID);
16 -// const placeholderNode = document.getElementById(placeholderID);
17 -// // We always expect both nodes to exist here because, while we might
18 -// // have navigated away from the main tree, we still expect the detached
19 -// // tree to exist.
20 -// segmentContainer.parentNode.removeChild(segmentContainer);
21 -// while (segmentContainer.firstChild) {
22 -// placeholderNode.parentNode.insertBefore(
23 -// segmentContainer.firstChild,
24 -// placeholderNode,
25 -// );
26 -// }
27 -// placeholderNode.parentNode.removeChild(placeholderNode);
28 -// }
29 -
30 -// TODO: Generate this file with a build step.
31 -export default 'function $RS(a,b){a=document.getElementById(a);b=document.getElementById(b);for(a.parentNode.removeChild(a);a.firstChild;)b.parentNode.insertBefore(a.firstChild,b);b.parentNode.removeChild(b)}';
packages/react-dom-bindings/src/server/fizz-instruction-set/styleInsertionFunctionString.js deleted
-100
@@ -1,100 +0,0 @@
1 -// Instruction Set
2 -
3 -// The following code is the source scripts that we then minify and inline below,
4 -// with renamed function names that we hope don't collide:
5 -
6 -// const COMMENT_NODE = 8;
7 -// const SUSPENSE_START_DATA = '$';
8 -// const SUSPENSE_END_DATA = '/$';
9 -// const SUSPENSE_PENDING_START_DATA = '$?';
10 -// const SUSPENSE_FALLBACK_START_DATA = '$!';
11 -// const LOADED = 'l';
12 -// const ERRORED = 'e';
13 -
14 -// resourceMap = new Map();
15 -// function completeBoundaryWithStyles(suspenseBoundaryID, contentID, styles) {
16 -// const precedences = new Map();
17 -// const thisDocument = document;
18 -// let lastResource, node;
19 -
20 -// // Seed the precedence list with existing resources
21 -// let nodes = thisDocument.querySelectorAll('link[data-rprec]');
22 -// for (let i = 0;node = nodes[i++];) {
23 -// precedences.set(node.dataset.rprec, lastResource = node);
24 -// }
25 -
26 -// let i = 0;
27 -// let dependencies = [];
28 -// let style, href, precedence, attr, loadingState, resourceEl;
29 -
30 -// function setStatus(s) {
31 -// this.s = s;
32 -// }
33 -
34 -// while (style = styles[i++]) {
35 -// let j = 0;
36 -// href = style[j++];
37 -// // We check if this resource is already in our resourceMap and reuse it if so.
38 -// // If it is already loaded we don't return it as a depenendency since there is nothing
39 -// // to wait for
40 -// loadingState = resourceMap.get(href);
41 -// if (loadingState) {
42 -// if (loadingState.s !== 'l') {
43 -// dependencies.push(loadingState);
44 -// }
45 -// continue;
46 -// }
47 -
48 -// // We construct our new resource element, looping over remaining attributes if any
49 -// // setting them to the Element.
50 -// resourceEl = thisDocument.createElement("link");
51 -// resourceEl.href = href;
52 -// resourceEl.rel = 'stylesheet';
53 -// resourceEl.dataset.rprec = precedence = style[j++];
54 -// while(attr = style[j++]) {
55 -// resourceEl.setAttribute(attr, style[j++]);
56 -// }
57 -
58 -// // We stash a pending promise in our map by href which will resolve or reject
59 -// // when the underlying resource loads or errors. We add it to the dependencies
60 -// // array to be returned.
61 -// loadingState = resourceEl._p = new Promise((re, rj) => {
62 -// resourceEl.onload = re;
63 -// resourceEl.onerror = rj;
64 -// })
65 -// loadingState.then(
66 -// setStatus.bind(loadingState, LOADED),
67 -// setStatus.bind(loadingState, ERRORED)
68 -// );
69 -// resourceMap.set(href, loadingState);
70 -// dependencies.push(loadingState);
71 -
72 -// // The prior style resource is the last one placed at a given
73 -// // precedence or the last resource itself which may be null.
74 -// // We grab this value and then update the last resource for this
75 -// // precedence to be the inserted element, updating the lastResource
76 -// // pointer if needed.
77 -// let prior = precedences.get(precedence) || lastResource;
78 -// if (prior === lastResource) {
79 -// lastResource = resourceEl
80 -// }
81 -// precedences.set(precedence, resourceEl)
82 -
83 -// // Finally, we insert the newly constructed instance at an appropriate location
84 -// // in the Document.
85 -// if (prior) {
86 -// prior.parentNode.insertBefore(resourceEl, prior.nextSibling);
87 -// } else {
88 -// let head = thisDocument.head;
89 -// head.insertBefore(resourceEl, head.firstChild);
90 -// }
91 -// }
92 -
93 -// Promise.all(dependencies).then(
94 -// completeBoundary.bind(null, suspenseBoundaryID, contentID, ''),
95 -// completeBoundary.bind(null, suspenseBoundaryID, contentID, "Resource failed to load")
96 -// );
97 -// }
98 -
99 -// TODO: Generate this file with a build step.
100 -export default '$RM=new Map;function $RR(p,q,t){function r(l){this.s=l}for(var m=new Map,n=document,g,e,f=n.querySelectorAll("link[data-rprec]"),d=0;e=f[d++];)m.set(e.dataset.rprec,g=e);e=0;f=[];for(var c,h,b,a;c=t[e++];){var k=0;h=c[k++];if(b=$RM.get(h))"l"!==b.s&&f.push(b);else{a=n.createElement("link");a.href=h;a.rel="stylesheet";for(a.dataset.rprec=d=c[k++];b=c[k++];)a.setAttribute(b,c[k++]);b=a._p=new Promise(function(l,u){a.onload=l;a.onerror=u});b.then(r.bind(b,"l"),r.bind(b,"e"));$RM.set(h,b);f.push(b);c=m.get(d)||g;c===g&&(g=a);m.set(d,a);c?c.parentNode.insertBefore(a,c.nextSibling):(d=n.head,d.insertBefore(a,d.firstChild))}}Promise.all(f).then($RC.bind(null,p,q,""),$RC.bind(null,p,q,"Resource failed to load"))}';
scripts/rollup/generate-inline-fizz-runtime.js new
+88
@@ -0,0 +1,88 @@
1 +'use strict';
2 +
3 +const fs = require('fs');
4 +const ClosureCompiler = require('google-closure-compiler').compiler;
5 +const prettier = require('prettier');
6 +
7 +const instructionDir =
8 + './packages/react-dom-bindings/src/server/fizz-instruction-set';
9 +
10 +// This is the name of the generated file that exports the inline instruction
11 +// set as strings.
12 +const inlineCodeStringsFilename =
13 + instructionDir + '/ReactDOMFizzInstructionSetInlineCodeStrings.js';
14 +
15 +const config = [
16 + {
17 + entry: 'ReactDOMFizzInlineClientRenderBoundary.js',
18 + exportName: 'clientRenderBoundary',
19 + },
20 + {
21 + entry: 'ReactDOMFizzInlineCompleteBoundary.js',
22 + exportName: 'completeBoundary',
23 + },
24 + {
25 + entry: 'ReactDOMFizzInlineCompleteBoundaryWithStyles.js',
26 + exportName: 'completeBoundaryWithStyles',
27 + },
28 + {
29 + entry: 'ReactDOMFizzInlineCompleteSegment.js',
30 + exportName: 'completeSegment',
31 + },
32 +];
33 +
34 +const prettierConfig = require('../../.prettierrc.js');
35 +
36 +async function main() {
37 + const exportStatements = await Promise.all(
38 + config.map(async ({entry, exportName}) => {
39 + const fullEntryPath = instructionDir + '/' + entry;
40 + const compiler = new ClosureCompiler({
41 + entry_point: fullEntryPath,
42 + js: [fullEntryPath, instructionDir + '/ReactDOMFizzInstructionSet.js'],
43 + compilation_level: 'ADVANCED',
44 + module_resolution: 'NODE',
45 + // This is necessary to prevent Closure from inlining a Promise polyfill
46 + rewrite_polyfills: false,
47 + });
48 +
49 + const code = await new Promise((resolve, reject) => {
50 + compiler.run((exitCode, stdOut, stdErr) => {
51 + if (exitCode !== 0) {
52 + reject(new Error(stdErr));
53 + } else {
54 + resolve(stdOut);
55 + }
56 + });
57 + });
58 +
59 + return `export const ${exportName} = ${JSON.stringify(code.trim())};`;
60 + })
61 + );
62 +
63 + let outputCode = [
64 + '// This is a generated file. The source files are in react-dom-bindings/src/server/fizz-instruction-set.',
65 + '// The build script is at scripts/rollup/generate-inline-fizz-runtime.js.',
66 + '// Run `yarn generate-inline-fizz-runtime` to generate.',
67 + ...exportStatements,
68 + ].join('\n');
69 +
70 + // This replaces "window.$globalVar" with "$globalVar". There's probably a
71 + // better way to do this with Closure, with externs or something, but I
72 + // couldn't figure it out. Good enough for now. This only affects the inline
73 + // Fizz runtime, and should break immediately if there were a mistake, so I'm
74 + // not too worried about it.
75 + outputCode = outputCode.replaceAll(
76 + /window\.(\$[A-z0-9_]*)/g,
77 + (_, variableName) => variableName
78 + );
79 +
80 + const prettyOutputCode = prettier.format(outputCode, prettierConfig);
81 +
82 + fs.writeFileSync(inlineCodeStringsFilename, prettyOutputCode, 'utf8');
83 +}
84 +
85 +main().catch(err => {
86 + console.error(err);
87 + process.exit(1);
88 +});