Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fizz] Fork Fizz instruction set for inline script and external runtime #25862

Merged
merged 1 commit into from
Jan 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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',
),
);
}
Original file line number Diff line number Diff line change
@@ -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',
),
);
}
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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.
Expand Down
Loading