-
Notifications
You must be signed in to change notification settings - Fork 47.4k
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
Add script to generate inline Fizz runtime #25481
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
5 changes: 5 additions & 0 deletions
5
...ct-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineClientRenderBoundary.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import {clientRenderBoundary} from './ReactDOMFizzInstructionSet'; | ||
|
||
// This is a string so Closure's advanced compilation mode doesn't mangle it. | ||
// eslint-disable-next-line dot-notation | ||
window['$RX'] = clientRenderBoundary; |
5 changes: 5 additions & 0 deletions
5
.../react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineCompleteBoundary.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import {completeBoundary} from './ReactDOMFizzInstructionSet'; | ||
|
||
// This is a string so Closure's advanced compilation mode doesn't mangle it. | ||
// eslint-disable-next-line dot-notation | ||
window['$RC'] = completeBoundary; |
7 changes: 7 additions & 0 deletions
7
...-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineCompleteBoundaryWithStyles.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import {completeBoundaryWithStyles} 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; |
5 changes: 5 additions & 0 deletions
5
...s/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInlineCompleteSegment.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import {completeSegment} from './ReactDOMFizzInstructionSet'; | ||
|
||
// This is a string so Closure's advanced compilation mode doesn't mangle it. | ||
// eslint-disable-next-line dot-notation | ||
window['$RS'] = completeSegment; |
224 changes: 224 additions & 0 deletions
224
packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSet.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,224 @@ | ||
/* 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'; | ||
|
||
// TODO: Symbols that are referenced outside this module use dynamic accessor | ||
// notation instead of dot notation to prevent Closure's advanced compilation | ||
// mode from renaming. We could use extern files instead, but I couldn't get it | ||
// working. Closure converts it to a dot access anyway, though, so it's not an | ||
// urgent issue. | ||
|
||
export function clientRenderBoundary( | ||
suspenseBoundaryID, | ||
errorDigest, | ||
errorMsg, | ||
errorComponentStack, | ||
) { | ||
// Find the fallback's first element. | ||
const suspenseIdNode = document.getElementById(suspenseBoundaryID); | ||
if (!suspenseIdNode) { | ||
// The user must have already navigated away from this tree. | ||
// E.g. because the parent was hydrated. | ||
return; | ||
} | ||
// Find the boundary around the fallback. This is always the previous node. | ||
const suspenseNode = suspenseIdNode.previousSibling; | ||
// Tag it to be client rendered. | ||
suspenseNode.data = SUSPENSE_FALLBACK_START_DATA; | ||
// assign error metadata to first sibling | ||
const dataset = suspenseIdNode.dataset; | ||
if (errorDigest) dataset['dgst'] = errorDigest; | ||
if (errorMsg) dataset['msg'] = errorMsg; | ||
if (errorComponentStack) dataset['stck'] = errorComponentStack; | ||
// Tell React to retry it if the parent already hydrated. | ||
if (suspenseNode['_reactRetry']) { | ||
suspenseNode['_reactRetry'](); | ||
} | ||
} | ||
|
||
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-rprec]'); | ||
for (let i = 0; (node = nodes[i++]); ) { | ||
precedences.set(node.dataset['rprec'], (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['rprec'] = 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. | ||
// This might also help by not causing recalcing each time we move a child from here to the target. | ||
contentNode.parentNode.removeChild(contentNode); | ||
|
||
// Find the fallback's first element. | ||
const suspenseIdNode = document.getElementById(suspenseBoundaryID); | ||
if (!suspenseIdNode) { | ||
// The user must have already navigated away from this tree. | ||
// E.g. because the parent was hydrated. That's fine there's nothing to do | ||
// but we have to make sure that we already deleted the container node. | ||
return; | ||
} | ||
// Find the boundary around the fallback. This is always the previous node. | ||
const suspenseNode = suspenseIdNode.previousSibling; | ||
|
||
if (!errorDigest) { | ||
// Clear all the existing children. This is complicated because | ||
// there can be embedded Suspense boundaries in the fallback. | ||
// This is similar to clearSuspenseBoundary in ReactDOMHostConfig. | ||
// TODO: We could avoid this if we never emitted suspense boundaries in fallback trees. | ||
// They never hydrate anyway. However, currently we support incrementally loading the fallback. | ||
const parentInstance = suspenseNode.parentNode; | ||
let node = suspenseNode.nextSibling; | ||
let depth = 0; | ||
do { | ||
if (node && node.nodeType === COMMENT_NODE) { | ||
const data = node.data; | ||
if (data === SUSPENSE_END_DATA) { | ||
if (depth === 0) { | ||
break; | ||
} else { | ||
depth--; | ||
} | ||
} else if ( | ||
data === SUSPENSE_START_DATA || | ||
data === SUSPENSE_PENDING_START_DATA || | ||
data === SUSPENSE_FALLBACK_START_DATA | ||
) { | ||
depth++; | ||
} | ||
} | ||
|
||
const nextNode = node.nextSibling; | ||
parentInstance.removeChild(node); | ||
node = nextNode; | ||
} while (node); | ||
|
||
const endOfBoundary = node; | ||
|
||
// Insert all the children from the contentNode between the start and end of suspense boundary. | ||
while (contentNode.firstChild) { | ||
parentInstance.insertBefore(contentNode.firstChild, endOfBoundary); | ||
} | ||
|
||
suspenseNode.data = SUSPENSE_START_DATA; | ||
} else { | ||
suspenseNode.data = SUSPENSE_FALLBACK_START_DATA; | ||
suspenseIdNode.setAttribute('data-dgst', errorDigest); | ||
} | ||
|
||
if (suspenseNode['_reactRetry']) { | ||
suspenseNode['_reactRetry'](); | ||
} | ||
} | ||
|
||
export function completeSegment(containerID, placeholderID) { | ||
const segmentContainer = document.getElementById(containerID); | ||
const placeholderNode = document.getElementById(placeholderID); | ||
// We always expect both nodes to exist here because, while we might | ||
// have navigated away from the main tree, we still expect the detached | ||
// tree to exist. | ||
segmentContainer.parentNode.removeChild(segmentContainer); | ||
while (segmentContainer.firstChild) { | ||
placeholderNode.parentNode.insertBefore( | ||
segmentContainer.firstChild, | ||
placeholderNode, | ||
); | ||
} | ||
placeholderNode.parentNode.removeChild(placeholderNode); | ||
} |
11 changes: 11 additions & 0 deletions
11
...m-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetInlineCodeStrings.js
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
38 changes: 0 additions & 38 deletions
38
packages/react-dom-bindings/src/server/fizz-instruction-set/clientRenderFunctionString.js
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I don’t believe we’re using the “advanced” GCC mode.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are in the script I just added 😆