-
Notifications
You must be signed in to change notification settings - Fork 46.5k
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
Implement basic stylesheet Resources for react-dom #25060
Changes from 11 commits
c8c4d22
eb34ba2
52fd944
4068cd4
64b95e5
3ec9468
29c5e8c
7b71ebe
6d94b15
4f5aa87
8e69ca1
3d56c0e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,7 @@ import {Children} from 'react'; | |
import { | ||
enableFilterEmptyStringAttributesDOM, | ||
enableCustomElementPropertySupport, | ||
enableFloat, | ||
} from 'shared/ReactFeatureFlags'; | ||
|
||
import type { | ||
|
@@ -242,6 +243,26 @@ export function getChildFormatContext( | |
return parentContext; | ||
} | ||
|
||
function isPreambleInsertion(type: string): boolean { | ||
switch (type) { | ||
case 'html': | ||
case 'head': { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
function isPostambleInsertion(type: string): boolean { | ||
switch (type) { | ||
case 'body': | ||
case 'html': { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
export type SuspenseBoundaryID = null | PrecomputedChunk; | ||
|
||
export const UNINITIALIZED_SUSPENSE_BOUNDARY_ID: SuspenseBoundaryID = null; | ||
|
@@ -1056,6 +1077,52 @@ function pushStartTextArea( | |
return null; | ||
} | ||
|
||
function pushLink( | ||
target: Array<Chunk | PrecomputedChunk>, | ||
props: Object, | ||
responseState: ResponseState, | ||
): ReactNodeList { | ||
const isStylesheet = props.rel === 'stylesheet'; | ||
target.push(startChunkForTag('link')); | ||
|
||
for (const propKey in props) { | ||
if (hasOwnProperty.call(props, propKey)) { | ||
const propValue = props[propKey]; | ||
if (propValue == null) { | ||
continue; | ||
} | ||
switch (propKey) { | ||
case 'children': | ||
case 'dangerouslySetInnerHTML': | ||
throw new Error( | ||
`${'link'} is a self-closing tag and must neither have \`children\` nor ` + | ||
'use `dangerouslySetInnerHTML`.', | ||
); | ||
case 'precedence': { | ||
if (isStylesheet) { | ||
if (propValue === true || typeof propValue === 'string') { | ||
pushAttribute(target, responseState, 'data-rprec', propValue); | ||
} else if (__DEV__) { | ||
throw new Error( | ||
`the "precedence" prop for links to stylehseets expects to receive a string but received something of type "${typeof propValue}" instead.`, | ||
); | ||
} | ||
break; | ||
} | ||
// intentionally fall through | ||
} | ||
// eslint-disable-next-line-no-fallthrough | ||
default: | ||
pushAttribute(target, responseState, propKey, propValue); | ||
break; | ||
} | ||
} | ||
} | ||
|
||
target.push(endOfStartTagSelfClosing); | ||
return null; | ||
} | ||
|
||
function pushSelfClosing( | ||
target: Array<Chunk | PrecomputedChunk>, | ||
props: Object, | ||
|
@@ -1189,6 +1256,39 @@ function pushStartTitle( | |
return children; | ||
} | ||
|
||
function pushStartHead( | ||
target: Array<Chunk | PrecomputedChunk>, | ||
preamble: ?Array<Chunk | PrecomputedChunk>, | ||
props: Object, | ||
tag: string, | ||
responseState: ResponseState, | ||
): ReactNodeList { | ||
// Preamble type is nullable for feature off cases but is guaranteed when feature is on | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I usually just make the type contain everything you'd expect to exist in the new common state. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was trying to avoid two extra array allocations for non dom (even though no one else is really using it yet I suppose). In the future I'd expect that preamble is not nullable for dom fizz server and is explicitly null for environments that do not support preamble but how would I type that? I guess I could export the preamble type but then I'd probably also need a create function for the preamble itself. Do you recommend doing it that way? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It'd just make it required. It's at most two per request, and we don't know if other renderers will end up using it yet. |
||
target = enableFloat && isPreambleInsertion(tag) ? (preamble: any) : target; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You don't need the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. brain broke |
||
|
||
return pushStartGenericElement(target, props, tag, responseState); | ||
} | ||
|
||
function pushStartHtml( | ||
target: Array<Chunk | PrecomputedChunk>, | ||
preamble: ?Array<Chunk | PrecomputedChunk>, | ||
props: Object, | ||
tag: string, | ||
formatContext: FormatContext, | ||
responseState: ResponseState, | ||
): ReactNodeList { | ||
// Preamble type is nullable for feature off cases but is guaranteed when feature is on | ||
target = enableFloat && isPreambleInsertion(tag) ? (preamble: any) : target; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here. |
||
|
||
if (formatContext.insertionMode === ROOT_HTML_MODE) { | ||
// If we're rendering the html tag and we're at the root (i.e. not in foreignObject) | ||
// then we also emit the DOCTYPE as part of the root content as a convenience for | ||
// rendering the whole document. | ||
target.push(DOCTYPE); | ||
} | ||
return pushStartGenericElement(target, props, tag, responseState); | ||
} | ||
|
||
function pushStartGenericElement( | ||
target: Array<Chunk | PrecomputedChunk>, | ||
props: Object, | ||
|
@@ -1405,6 +1505,7 @@ const DOCTYPE: PrecomputedChunk = stringToPrecomputedChunk('<!DOCTYPE html>'); | |
|
||
export function pushStartInstance( | ||
target: Array<Chunk | PrecomputedChunk>, | ||
preamble: ?Array<Chunk | PrecomputedChunk>, | ||
type: string, | ||
props: Object, | ||
responseState: ResponseState, | ||
|
@@ -1461,6 +1562,8 @@ export function pushStartInstance( | |
return pushStartMenuItem(target, props, responseState); | ||
case 'title': | ||
return pushStartTitle(target, props, responseState); | ||
case 'link': | ||
return pushLink(target, props, responseState); | ||
// Newline eating tags | ||
case 'listing': | ||
case 'pre': { | ||
|
@@ -1475,7 +1578,6 @@ export function pushStartInstance( | |
case 'hr': | ||
case 'img': | ||
case 'keygen': | ||
case 'link': | ||
case 'meta': | ||
case 'param': | ||
case 'source': | ||
|
@@ -1495,14 +1597,18 @@ export function pushStartInstance( | |
case 'missing-glyph': { | ||
return pushStartGenericElement(target, props, type, responseState); | ||
} | ||
// Preamble start tags | ||
case 'head': | ||
return pushStartHead(target, preamble, props, type, responseState); | ||
case 'html': { | ||
if (formatContext.insertionMode === ROOT_HTML_MODE) { | ||
// If we're rendering the html tag and we're at the root (i.e. not in foreignObject) | ||
// then we also emit the DOCTYPE as part of the root content as a convenience for | ||
// rendering the whole document. | ||
target.push(DOCTYPE); | ||
} | ||
return pushStartGenericElement(target, props, type, responseState); | ||
return pushStartHtml( | ||
target, | ||
preamble, | ||
props, | ||
type, | ||
formatContext, | ||
responseState, | ||
); | ||
} | ||
default: { | ||
if (type.indexOf('-') === -1 && typeof props.is !== 'string') { | ||
|
@@ -1521,6 +1627,7 @@ const endTag2 = stringToPrecomputedChunk('>'); | |
|
||
export function pushEndInstance( | ||
target: Array<Chunk | PrecomputedChunk>, | ||
postamble: ?Array<Chunk | PrecomputedChunk>, | ||
type: string, | ||
props: Object, | ||
): void { | ||
|
@@ -1546,6 +1653,13 @@ export function pushEndInstance( | |
// No close tag needed. | ||
break; | ||
} | ||
// Postamble end tags | ||
case 'body': | ||
case 'html': | ||
// Preamble type is nullable for feature off cases but is guaranteed when feature is on | ||
target = | ||
enableFloat && isPostambleInsertion(type) ? (postamble: any) : target; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here. |
||
// Intentional fallthrough | ||
default: { | ||
target.push(endTag1, stringToChunk(type), endTag2); | ||
} | ||
|
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.
small typo: "stylehseets" -> "stylesheets"