Skip to content

Commit bb57fa7

Browse files
authored
[Fizz] Share code between inline and external runtime (facebook#33066)
Stacked on facebook#33065. The runtime is about to be a lot more complicated so we need to start sharing some more code. The problem with sharing code is that we want the inline runtime to as much as possible be isolated in its scope using only a few global variables to refer across runtimes. A problem with Closure Compiler is that it refuses to inline functions if they have closures inside of them. Which makes sense because of how VMs work it can cause memory leaks. However, in our cases this doesn't matter and code size matters more. So we can't use many clever tricks. So this just favors writing the source in the inline form. Then we add an extra compiler pass to turn those global variables into local variables in the external runtime.
1 parent e9db3cc commit bb57fa7

12 files changed

+190
-291
lines changed

packages/react-dom-bindings/src/server/ReactDOMServerExternalRuntime.js

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,7 @@
77

88
// Imports are resolved statically by the closure compiler in release bundles
99
// and by rollup in jest unit tests
10-
import {
11-
clientRenderBoundary,
12-
completeBoundaryWithStyles,
13-
completeBoundary,
14-
completeSegment,
15-
} from './fizz-instruction-set/ReactDOMFizzInstructionSetExternalRuntime';
10+
import './fizz-instruction-set/ReactDOMFizzInstructionSetExternalRuntime';
1611

1712
if (document.body != null) {
1813
if (document.readyState === 'loading') {
@@ -82,7 +77,7 @@ function handleNode(node_: Node) {
8277
const node = (node_: HTMLElement);
8378
const dataset = node.dataset;
8479
if (dataset['rxi'] != null) {
85-
clientRenderBoundary(
80+
window['$RX'](
8681
dataset['bid'],
8782
dataset['dgst'],
8883
dataset['msg'],
@@ -92,17 +87,13 @@ function handleNode(node_: Node) {
9287
node.remove();
9388
} else if (dataset['rri'] != null) {
9489
// Convert styles here, since its type is Array<Array<string>>
95-
completeBoundaryWithStyles(
96-
dataset['bid'],
97-
dataset['sid'],
98-
JSON.parse(dataset['sty']),
99-
);
90+
window['$RR'](dataset['bid'], dataset['sid'], JSON.parse(dataset['sty']));
10091
node.remove();
10192
} else if (dataset['rci'] != null) {
102-
completeBoundary(dataset['bid'], dataset['sid']);
93+
window['$RC'](dataset['bid'], dataset['sid']);
10394
node.remove();
10495
} else if (dataset['rsi'] != null) {
105-
completeSegment(dataset['sid'], dataset['pid']);
96+
window['$RS'](dataset['sid'], dataset['pid']);
10697
node.remove();
10798
}
10899
}

packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineClientRenderBoundary.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {clientRenderBoundary} from './ReactDOMFizzInstructionSetInlineSource';
1+
import {clientRenderBoundary} from './ReactDOMFizzInstructionSetShared';
22

33
// This is a string so Closure's advanced compilation mode doesn't mangle it.
44
// eslint-disable-next-line dot-notation

packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineCompleteBoundary.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {completeBoundary} from './ReactDOMFizzInstructionSetInlineSource';
1+
import {completeBoundary} from './ReactDOMFizzInstructionSetShared';
22

33
// This is a string so Closure's advanced compilation mode doesn't mangle it.
44
// eslint-disable-next-line dot-notation

packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineCompleteBoundaryWithStyles.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {completeBoundaryWithStyles} from './ReactDOMFizzInstructionSetInlineSource';
1+
import {completeBoundaryWithStyles} from './ReactDOMFizzInstructionSetShared';
22

33
// This is a string so Closure's advanced compilation mode doesn't mangle it.
44
// eslint-disable-next-line dot-notation

packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineCompleteSegment.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {completeSegment} from './ReactDOMFizzInstructionSetInlineSource';
1+
import {completeSegment} from './ReactDOMFizzInstructionSetShared';
22

33
// This is a string so Closure's advanced compilation mode doesn't mangle it.
44
// eslint-disable-next-line dot-notation

packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetExternalRuntime.js

Lines changed: 8 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -5,138 +5,17 @@
55
import {
66
clientRenderBoundary,
77
completeBoundary,
8+
completeBoundaryWithStyles,
89
completeSegment,
910
listenToFormSubmissionsForReplaying,
1011
} from './ReactDOMFizzInstructionSetShared';
1112

12-
export {clientRenderBoundary, completeBoundary, completeSegment};
13-
14-
const resourceMap = new Map();
15-
16-
// This function is almost identical to the version used by inline scripts
17-
// (ReactDOMFizzInstructionSetInlineSource), with the exception of how we read
18-
// completeBoundary and resourceMap
19-
export function completeBoundaryWithStyles(
20-
suspenseBoundaryID,
21-
contentID,
22-
stylesheetDescriptors,
23-
) {
24-
const precedences = new Map();
25-
const thisDocument = document;
26-
let lastResource, node;
27-
28-
// Seed the precedence list with existing resources and collect hoistable style tags
29-
const nodes = thisDocument.querySelectorAll(
30-
'link[data-precedence],style[data-precedence]',
31-
);
32-
const styleTagsToHoist = [];
33-
for (let i = 0; (node = nodes[i++]); ) {
34-
if (node.getAttribute('media') === 'not all') {
35-
styleTagsToHoist.push(node);
36-
} else {
37-
if (node.tagName === 'LINK') {
38-
resourceMap.set(node.getAttribute('href'), node);
39-
}
40-
precedences.set(node.dataset['precedence'], (lastResource = node));
41-
}
42-
}
43-
44-
let i = 0;
45-
const dependencies = [];
46-
let href, precedence, attr, loadingState, resourceEl, media;
47-
48-
function cleanupWith(cb) {
49-
this['_p'] = null;
50-
cb();
51-
}
52-
53-
// Sheets Mode
54-
let sheetMode = true;
55-
while (true) {
56-
if (sheetMode) {
57-
// Sheet Mode iterates over the stylesheet arguments and constructs them if new or checks them for
58-
// dependency if they already existed
59-
const stylesheetDescriptor = stylesheetDescriptors[i++];
60-
if (!stylesheetDescriptor) {
61-
// enter <style> Mode
62-
sheetMode = false;
63-
i = 0;
64-
continue;
65-
}
66-
67-
let avoidInsert = false;
68-
let j = 0;
69-
href = stylesheetDescriptor[j++];
70-
71-
if ((resourceEl = resourceMap.get(href))) {
72-
// We have an already inserted stylesheet.
73-
loadingState = resourceEl['_p'];
74-
avoidInsert = true;
75-
} else {
76-
// We haven't already processed this href so we need to construct a stylesheet and hoist it
77-
// We construct it here and attach a loadingState. We also check whether it matches
78-
// media before we include it in the dependency array.
79-
resourceEl = thisDocument.createElement('link');
80-
resourceEl.href = href;
81-
resourceEl.rel = 'stylesheet';
82-
resourceEl.dataset['precedence'] = precedence =
83-
stylesheetDescriptor[j++];
84-
while ((attr = stylesheetDescriptor[j++])) {
85-
resourceEl.setAttribute(attr, stylesheetDescriptor[j++]);
86-
}
87-
loadingState = resourceEl['_p'] = new Promise((resolve, reject) => {
88-
resourceEl.onload = cleanupWith.bind(resourceEl, resolve);
89-
resourceEl.onerror = cleanupWith.bind(resourceEl, reject);
90-
});
91-
// Save this resource element so we can bailout if it is used again
92-
resourceMap.set(href, resourceEl);
93-
}
94-
media = resourceEl.getAttribute('media');
95-
if (loadingState && (!media || window['matchMedia'](media).matches)) {
96-
dependencies.push(loadingState);
97-
}
98-
if (avoidInsert) {
99-
// We have a link that is already in the document. We don't want to fall through to the insert path
100-
continue;
101-
}
102-
} else {
103-
// <style> mode iterates over not-yet-hoisted <style> tags with data-precedence and hoists them.
104-
resourceEl = styleTagsToHoist[i++];
105-
if (!resourceEl) {
106-
// we are done with all style tags
107-
break;
108-
}
109-
110-
precedence = resourceEl.getAttribute('data-precedence');
111-
resourceEl.removeAttribute('media');
112-
}
113-
114-
// resourceEl is either a newly constructed <link rel="stylesheet" ...> or a <style> tag requiring hoisting
115-
const prior = precedences.get(precedence) || lastResource;
116-
if (prior === lastResource) {
117-
lastResource = resourceEl;
118-
}
119-
precedences.set(precedence, resourceEl);
120-
121-
// Finally, we insert the newly constructed instance at an appropriate location
122-
// in the Document.
123-
if (prior) {
124-
prior.parentNode.insertBefore(resourceEl, prior.nextSibling);
125-
} else {
126-
const head = thisDocument.head;
127-
head.insertBefore(resourceEl, head.firstChild);
128-
}
129-
}
130-
131-
Promise.all(dependencies).then(
132-
completeBoundary.bind(null, suspenseBoundaryID, contentID, ''),
133-
completeBoundary.bind(
134-
null,
135-
suspenseBoundaryID,
136-
contentID,
137-
'Resource failed to load',
138-
),
139-
);
140-
}
13+
// This is a string so Closure's advanced compilation mode doesn't mangle it.
14+
// These will be renamed to local references by the external-runtime-plugin.
15+
window['$RM'] = new Map();
16+
window['$RX'] = clientRenderBoundary;
17+
window['$RC'] = completeBoundary;
18+
window['$RR'] = completeBoundaryWithStyles;
19+
window['$RS'] = completeSegment;
14120

14221
listenToFormSubmissionsForReplaying();

packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetInlineCodeStrings.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetInlineSource.js

Lines changed: 0 additions & 142 deletions
This file was deleted.

0 commit comments

Comments
 (0)