[Fizz] Fork Fizz instruction set for inline script and external runtime (#25862)
~~[Fizz] Duplicate completeBoundaryWithStyles to not reference globals~~ ## Summary Follow-up / cleanup PR to #25437 - `completeBoundaryWithStylesInlineLocals` is used by the Fizz external runtime, which bundles together all Fizz instruction functions (and is able to reference / rename `completeBoundary` and `resourceMap` as locals). - `completeBoundaryWithStylesInlineGlobals` is used by the Fizz inline script writer, which sends Fizz instruction functions on an as-needed basis. This version needs to reference `completeBoundary($RC)` and `resourceMap($RM)` as globals. Ideally, Closure would take care of inlining a shared implementation, but I couldn't figure out a zero-overhead inline due to lack of an `@inline` compiler directive. It seems that Closure thinks that a shared `completeBoundaryWithStyles` is too large and will always keep it as a separate function. I've also tried currying / writing a higher order function (`getCompleteBoundaryWithStyles`) with no luck ## How did you test this change? - generated Fizz inline instructions should be unchanged - bundle size for unstable_external_runtime should be slightly smaller (due to lack of globals) - `ReactDOMFizzServer-test.js` and `ReactDOMFloat-test.js` should be unaffected
This commit is contained in:
parent
5379b6123f
commit
0b974418c9
|
@ -12,7 +12,7 @@ import {
|
|||
completeBoundaryWithStyles,
|
||||
completeBoundary,
|
||||
completeSegment,
|
||||
} from './fizz-instruction-set/ReactDOMFizzInstructionSet';
|
||||
} from './fizz-instruction-set/ReactDOMFizzInstructionSetExternalRuntime';
|
||||
|
||||
if (!window.$RC) {
|
||||
// TODO: Eventually remove, we currently need to set these globals for
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {clientRenderBoundary} from './ReactDOMFizzInstructionSet';
|
||||
import {clientRenderBoundary} from './ReactDOMFizzInstructionSetInlineSource';
|
||||
|
||||
// This is a string so Closure's advanced compilation mode doesn't mangle it.
|
||||
// eslint-disable-next-line dot-notation
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {completeBoundary} from './ReactDOMFizzInstructionSet';
|
||||
import {completeBoundary} from './ReactDOMFizzInstructionSetInlineSource';
|
||||
|
||||
// This is a string so Closure's advanced compilation mode doesn't mangle it.
|
||||
// eslint-disable-next-line dot-notation
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {completeBoundaryWithStyles} from './ReactDOMFizzInstructionSet';
|
||||
import {completeBoundaryWithStyles} from './ReactDOMFizzInstructionSetInlineSource';
|
||||
|
||||
// This is a string so Closure's advanced compilation mode doesn't mangle it.
|
||||
// eslint-disable-next-line dot-notation
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {completeSegment} from './ReactDOMFizzInstructionSet';
|
||||
import {completeSegment} from './ReactDOMFizzInstructionSetInlineSource';
|
||||
|
||||
// This is a string so Closure's advanced compilation mode doesn't mangle it.
|
||||
// eslint-disable-next-line dot-notation
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
/* eslint-disable dot-notation */
|
||||
|
||||
// Instruction set for the Fizz external runtime
|
||||
|
||||
import {
|
||||
clientRenderBoundary,
|
||||
completeBoundary,
|
||||
completeSegment,
|
||||
LOADED,
|
||||
ERRORED,
|
||||
} from './ReactDOMFizzInstructionSetShared';
|
||||
|
||||
export {clientRenderBoundary, completeBoundary, completeSegment};
|
||||
|
||||
const resourceMap = new Map();
|
||||
|
||||
// This function is almost identical to the version used by inline scripts
|
||||
// (ReactDOMFizzInstructionSetInlineSource), with the exception of how we read
|
||||
// completeBoundary and resourceMap
|
||||
export function completeBoundaryWithStyles(
|
||||
suspenseBoundaryID,
|
||||
contentID,
|
||||
styles,
|
||||
) {
|
||||
const precedences = new Map();
|
||||
const thisDocument = document;
|
||||
let lastResource, node;
|
||||
|
||||
// Seed the precedence list with existing resources
|
||||
const nodes = thisDocument.querySelectorAll(
|
||||
'link[data-precedence],style[data-precedence]',
|
||||
);
|
||||
for (let i = 0; (node = nodes[i++]); ) {
|
||||
precedences.set(node.dataset['precedence'], (lastResource = node));
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
const dependencies = [];
|
||||
let style, href, precedence, attr, loadingState, resourceEl;
|
||||
|
||||
function setStatus(s) {
|
||||
this['s'] = s;
|
||||
}
|
||||
|
||||
while ((style = styles[i++])) {
|
||||
let j = 0;
|
||||
href = style[j++];
|
||||
// We check if this resource is already in our resourceMap and reuse it if so.
|
||||
// If it is already loaded we don't return it as a depenendency since there is nothing
|
||||
// to wait for
|
||||
loadingState = resourceMap.get(href);
|
||||
if (loadingState) {
|
||||
if (loadingState['s'] !== 'l') {
|
||||
dependencies.push(loadingState);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// We construct our new resource element, looping over remaining attributes if any
|
||||
// setting them to the Element.
|
||||
resourceEl = thisDocument.createElement('link');
|
||||
resourceEl.href = href;
|
||||
resourceEl.rel = 'stylesheet';
|
||||
resourceEl.dataset['precedence'] = precedence = style[j++];
|
||||
while ((attr = style[j++])) {
|
||||
resourceEl.setAttribute(attr, style[j++]);
|
||||
}
|
||||
|
||||
// We stash a pending promise in our map by href which will resolve or reject
|
||||
// when the underlying resource loads or errors. We add it to the dependencies
|
||||
// array to be returned.
|
||||
loadingState = resourceEl['_p'] = new Promise((re, rj) => {
|
||||
resourceEl.onload = re;
|
||||
resourceEl.onerror = rj;
|
||||
});
|
||||
loadingState.then(
|
||||
setStatus.bind(loadingState, LOADED),
|
||||
setStatus.bind(loadingState, ERRORED),
|
||||
);
|
||||
resourceMap.set(href, loadingState);
|
||||
dependencies.push(loadingState);
|
||||
|
||||
// The prior style resource is the last one placed at a given
|
||||
// precedence or the last resource itself which may be null.
|
||||
// We grab this value and then update the last resource for this
|
||||
// precedence to be the inserted element, updating the lastResource
|
||||
// pointer if needed.
|
||||
const prior = precedences.get(precedence) || lastResource;
|
||||
if (prior === lastResource) {
|
||||
lastResource = resourceEl;
|
||||
}
|
||||
precedences.set(precedence, resourceEl);
|
||||
|
||||
// Finally, we insert the newly constructed instance at an appropriate location
|
||||
// in the Document.
|
||||
if (prior) {
|
||||
prior.parentNode.insertBefore(resourceEl, prior.nextSibling);
|
||||
} else {
|
||||
const head = thisDocument.head;
|
||||
head.insertBefore(resourceEl, head.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
Promise.all(dependencies).then(
|
||||
completeBoundary.bind(null, suspenseBoundaryID, contentID, ''),
|
||||
completeBoundary.bind(
|
||||
null,
|
||||
suspenseBoundaryID,
|
||||
contentID,
|
||||
'Resource failed to load',
|
||||
),
|
||||
);
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
/* eslint-disable dot-notation */
|
||||
|
||||
// Instruction set for Fizz inline scripts.
|
||||
// DO NOT DIRECTLY IMPORT THIS FILE. This is the source for the compiled and
|
||||
// minified code in ReactDOMFizzInstructionSetInlineCodeStrings.
|
||||
|
||||
import {
|
||||
clientRenderBoundary,
|
||||
completeBoundary,
|
||||
completeSegment,
|
||||
LOADED,
|
||||
ERRORED,
|
||||
} from './ReactDOMFizzInstructionSetShared';
|
||||
|
||||
export {clientRenderBoundary, completeBoundary, completeSegment};
|
||||
|
||||
// This function is almost identical to the version used by the external
|
||||
// runtime (ReactDOMFizzInstructionSetExternalRuntime), with the exception of
|
||||
// how we read completeBoundaryImpl and resourceMap
|
||||
export function completeBoundaryWithStyles(
|
||||
suspenseBoundaryID,
|
||||
contentID,
|
||||
styles,
|
||||
) {
|
||||
const completeBoundaryImpl = window['$RC'];
|
||||
const resourceMap = window['$RM'];
|
||||
|
||||
const precedences = new Map();
|
||||
const thisDocument = document;
|
||||
let lastResource, node;
|
||||
|
||||
// Seed the precedence list with existing resources
|
||||
const nodes = thisDocument.querySelectorAll(
|
||||
'link[data-precedence],style[data-precedence]',
|
||||
);
|
||||
for (let i = 0; (node = nodes[i++]); ) {
|
||||
precedences.set(node.dataset['precedence'], (lastResource = node));
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
const dependencies = [];
|
||||
let style, href, precedence, attr, loadingState, resourceEl;
|
||||
|
||||
function setStatus(s) {
|
||||
this['s'] = s;
|
||||
}
|
||||
|
||||
while ((style = styles[i++])) {
|
||||
let j = 0;
|
||||
href = style[j++];
|
||||
// We check if this resource is already in our resourceMap and reuse it if so.
|
||||
// If it is already loaded we don't return it as a depenendency since there is nothing
|
||||
// to wait for
|
||||
loadingState = resourceMap.get(href);
|
||||
if (loadingState) {
|
||||
if (loadingState['s'] !== 'l') {
|
||||
dependencies.push(loadingState);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// We construct our new resource element, looping over remaining attributes if any
|
||||
// setting them to the Element.
|
||||
resourceEl = thisDocument.createElement('link');
|
||||
resourceEl.href = href;
|
||||
resourceEl.rel = 'stylesheet';
|
||||
resourceEl.dataset['precedence'] = precedence = style[j++];
|
||||
while ((attr = style[j++])) {
|
||||
resourceEl.setAttribute(attr, style[j++]);
|
||||
}
|
||||
|
||||
// We stash a pending promise in our map by href which will resolve or reject
|
||||
// when the underlying resource loads or errors. We add it to the dependencies
|
||||
// array to be returned.
|
||||
loadingState = resourceEl['_p'] = new Promise((re, rj) => {
|
||||
resourceEl.onload = re;
|
||||
resourceEl.onerror = rj;
|
||||
});
|
||||
loadingState.then(
|
||||
setStatus.bind(loadingState, LOADED),
|
||||
setStatus.bind(loadingState, ERRORED),
|
||||
);
|
||||
resourceMap.set(href, loadingState);
|
||||
dependencies.push(loadingState);
|
||||
|
||||
// The prior style resource is the last one placed at a given
|
||||
// precedence or the last resource itself which may be null.
|
||||
// We grab this value and then update the last resource for this
|
||||
// precedence to be the inserted element, updating the lastResource
|
||||
// pointer if needed.
|
||||
const prior = precedences.get(precedence) || lastResource;
|
||||
if (prior === lastResource) {
|
||||
lastResource = resourceEl;
|
||||
}
|
||||
precedences.set(precedence, resourceEl);
|
||||
|
||||
// Finally, we insert the newly constructed instance at an appropriate location
|
||||
// in the Document.
|
||||
if (prior) {
|
||||
prior.parentNode.insertBefore(resourceEl, prior.nextSibling);
|
||||
} else {
|
||||
const head = thisDocument.head;
|
||||
head.insertBefore(resourceEl, head.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
Promise.all(dependencies).then(
|
||||
completeBoundaryImpl.bind(null, suspenseBoundaryID, contentID, ''),
|
||||
completeBoundaryImpl.bind(
|
||||
null,
|
||||
suspenseBoundaryID,
|
||||
contentID,
|
||||
'Resource failed to load',
|
||||
),
|
||||
);
|
||||
}
|
|
@ -1,12 +1,15 @@
|
|||
/* eslint-disable dot-notation */
|
||||
|
||||
const COMMENT_NODE = 8;
|
||||
const SUSPENSE_START_DATA = '$';
|
||||
const SUSPENSE_END_DATA = '/$';
|
||||
const SUSPENSE_PENDING_START_DATA = '$?';
|
||||
const SUSPENSE_FALLBACK_START_DATA = '$!';
|
||||
const LOADED = 'l';
|
||||
const ERRORED = 'e';
|
||||
// Shared implementation and constants between the inline script and external
|
||||
// runtime instruction sets.
|
||||
|
||||
export const COMMENT_NODE = 8;
|
||||
export const SUSPENSE_START_DATA = '$';
|
||||
export const SUSPENSE_END_DATA = '/$';
|
||||
export const SUSPENSE_PENDING_START_DATA = '$?';
|
||||
export const SUSPENSE_FALLBACK_START_DATA = '$!';
|
||||
export const LOADED = 'l';
|
||||
export const ERRORED = 'e';
|
||||
|
||||
// TODO: Symbols that are referenced outside this module use dynamic accessor
|
||||
// notation instead of dot notation to prevent Closure's advanced compilation
|
||||
|
@ -42,106 +45,6 @@ export function clientRenderBoundary(
|
|||
}
|
||||
}
|
||||
|
||||
export function completeBoundaryWithStyles(
|
||||
suspenseBoundaryID,
|
||||
contentID,
|
||||
styles,
|
||||
) {
|
||||
// TODO: In the non-inline version of the runtime, these don't need to be read
|
||||
// from the global scope.
|
||||
const completeBoundaryImpl = window['$RC'];
|
||||
const resourceMap = window['$RM'];
|
||||
|
||||
const precedences = new Map();
|
||||
const thisDocument = document;
|
||||
let lastResource, node;
|
||||
|
||||
// Seed the precedence list with existing resources
|
||||
const nodes = thisDocument.querySelectorAll(
|
||||
'link[data-precedence],style[data-precedence]',
|
||||
);
|
||||
for (let i = 0; (node = nodes[i++]); ) {
|
||||
precedences.set(node.dataset['precedence'], (lastResource = node));
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
const dependencies = [];
|
||||
let style, href, precedence, attr, loadingState, resourceEl;
|
||||
|
||||
function setStatus(s) {
|
||||
this['s'] = s;
|
||||
}
|
||||
|
||||
while ((style = styles[i++])) {
|
||||
let j = 0;
|
||||
href = style[j++];
|
||||
// We check if this resource is already in our resourceMap and reuse it if so.
|
||||
// If it is already loaded we don't return it as a depenendency since there is nothing
|
||||
// to wait for
|
||||
loadingState = resourceMap.get(href);
|
||||
if (loadingState) {
|
||||
if (loadingState['s'] !== 'l') {
|
||||
dependencies.push(loadingState);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// We construct our new resource element, looping over remaining attributes if any
|
||||
// setting them to the Element.
|
||||
resourceEl = thisDocument.createElement('link');
|
||||
resourceEl.href = href;
|
||||
resourceEl.rel = 'stylesheet';
|
||||
resourceEl.dataset['precedence'] = precedence = style[j++];
|
||||
while ((attr = style[j++])) {
|
||||
resourceEl.setAttribute(attr, style[j++]);
|
||||
}
|
||||
|
||||
// We stash a pending promise in our map by href which will resolve or reject
|
||||
// when the underlying resource loads or errors. We add it to the dependencies
|
||||
// array to be returned.
|
||||
loadingState = resourceEl['_p'] = new Promise((re, rj) => {
|
||||
resourceEl.onload = re;
|
||||
resourceEl.onerror = rj;
|
||||
});
|
||||
loadingState.then(
|
||||
setStatus.bind(loadingState, LOADED),
|
||||
setStatus.bind(loadingState, ERRORED),
|
||||
);
|
||||
resourceMap.set(href, loadingState);
|
||||
dependencies.push(loadingState);
|
||||
|
||||
// The prior style resource is the last one placed at a given
|
||||
// precedence or the last resource itself which may be null.
|
||||
// We grab this value and then update the last resource for this
|
||||
// precedence to be the inserted element, updating the lastResource
|
||||
// pointer if needed.
|
||||
const prior = precedences.get(precedence) || lastResource;
|
||||
if (prior === lastResource) {
|
||||
lastResource = resourceEl;
|
||||
}
|
||||
precedences.set(precedence, resourceEl);
|
||||
|
||||
// Finally, we insert the newly constructed instance at an appropriate location
|
||||
// in the Document.
|
||||
if (prior) {
|
||||
prior.parentNode.insertBefore(resourceEl, prior.nextSibling);
|
||||
} else {
|
||||
const head = thisDocument.head;
|
||||
head.insertBefore(resourceEl, head.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
Promise.all(dependencies).then(
|
||||
completeBoundaryImpl.bind(null, suspenseBoundaryID, contentID, ''),
|
||||
completeBoundaryImpl.bind(
|
||||
null,
|
||||
suspenseBoundaryID,
|
||||
contentID,
|
||||
'Resource failed to load',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function completeBoundary(suspenseBoundaryID, contentID, errorDigest) {
|
||||
const contentNode = document.getElementById(contentID);
|
||||
// We'll detach the content node so that regardless of what happens next we don't leave in the tree.
|
|
@ -39,7 +39,11 @@ async function main() {
|
|||
const fullEntryPath = instructionDir + '/' + entry;
|
||||
const compiler = new ClosureCompiler({
|
||||
entry_point: fullEntryPath,
|
||||
js: [fullEntryPath, instructionDir + '/ReactDOMFizzInstructionSet.js'],
|
||||
js: [
|
||||
fullEntryPath,
|
||||
instructionDir + '/ReactDOMFizzInstructionSetInlineSource.js',
|
||||
instructionDir + '/ReactDOMFizzInstructionSetShared.js',
|
||||
],
|
||||
compilation_level: 'ADVANCED',
|
||||
module_resolution: 'NODE',
|
||||
// This is necessary to prevent Closure from inlining a Promise polyfill
|
||||
|
|
Loading…
Reference in New Issue