Skip to content

Commit

Permalink
[Fizz] Duplicate completeBoundaryWithStyles to not reference globals
Browse files Browse the repository at this point in the history
  • Loading branch information
mofeiZ committed Dec 9, 2022
1 parent b14d7fa commit 00e5dad
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,12 @@
// and by rollup in jest unit tests
import {
clientRenderBoundary,
completeBoundaryWithStyles,
completeBoundaryWithStylesInlineLocals,
completeBoundary,
completeSegment,
} from './fizz-instruction-set/ReactDOMFizzInstructionSet';

if (!window.$RC) {
// TODO: Eventually remove, we currently need to set these globals for
// compatibility with ReactDOMFizzInstructionSet
window.$RC = completeBoundary;
window.$RM = new Map();
}
const resourceMap = new Map();

if (document.readyState === 'loading') {
if (document.body != null) {
Expand Down Expand Up @@ -91,7 +86,8 @@ function handleNode(node_ /*: Node */) {
node.remove();
} else if (dataset['rri'] != null) {
// Convert styles here, since its type is Array<Array<string>>
completeBoundaryWithStyles(
completeBoundaryWithStylesInlineLocals(
resourceMap,
dataset['bid'],
dataset['sid'],
JSON.parse(dataset['sty']),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {completeBoundaryWithStyles} from './ReactDOMFizzInstructionSet';
import {completeBoundaryWithStylesInlineGlobals} from './ReactDOMFizzInstructionSet';

// This is a string so Closure's advanced compilation mode doesn't mangle it.
// eslint-disable-next-line dot-notation
window['$RM'] = new Map();
// eslint-disable-next-line dot-notation
window['$RR'] = completeBoundaryWithStyles;
window['$RR'] = completeBoundaryWithStylesInlineGlobals;
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,121 @@ export function clientRenderBoundary(
}
}

export function completeBoundaryWithStyles(
// The following two functions should be almost identical. The only differences
// are the values used for `completeBoundaryImpl` and `resourceMap`.
// 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 a @inline compiler directive.
export function completeBoundaryWithStylesInlineLocals(
resourceMap,
suspenseBoundaryID,
contentID,
styles,
) {
const completeBoundaryImpl = completeBoundary;
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 completeBoundaryWithStylesInlineGlobals(
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;
Expand Down

0 comments on commit 00e5dad

Please sign in to comment.