Skip to content

Commit fcec758

Browse files
huozhikodiakhq[bot]shuding
authored
Flush initial styled-jsx in gIP first in concurrent rendering (#36594)
* Use flushed effects to generate styled-jsx styles insted of gIP by default * ensure styles are flushed inside the default getInitialProps Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> Co-authored-by: Shu Ding <[email protected]>
1 parent b188fab commit fcec758

File tree

7 files changed

+45
-26
lines changed

7 files changed

+45
-26
lines changed

packages/next/pages/_document.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import React, { Component, ReactElement, ReactNode, useContext } from 'react'
2-
import { OPTIMIZED_FONT_PROVIDERS } from '../shared/lib/constants'
2+
import {
3+
OPTIMIZED_FONT_PROVIDERS,
4+
NEXT_BUILTIN_DOCUMENT,
5+
} from '../shared/lib/constants'
36
import type {
47
DocumentContext,
58
DocumentInitialProps,
69
DocumentProps,
10+
DocumentType,
711
} from '../shared/lib/utils'
812
import { BuildManifest, getPageFiles } from '../server/get-page-files'
913
import { cleanAmpPath } from '../server/utils'
@@ -309,7 +313,7 @@ export default class Document<P = {}> extends Component<DocumentProps & P> {
309313

310314
// Add a special property to the built-in `Document` component so later we can
311315
// identify if a user customized `Document` is used or not.
312-
;(Document as any).__next_internal_document =
316+
const InternalFunctionDocument: DocumentType =
313317
function InternalFunctionDocument() {
314318
return (
315319
<Html>
@@ -321,6 +325,7 @@ export default class Document<P = {}> extends Component<DocumentProps & P> {
321325
</Html>
322326
)
323327
}
328+
;(Document as any)[NEXT_BUILTIN_DOCUMENT] = InternalFunctionDocument
324329

325330
export function Html(
326331
props: React.DetailedHTMLProps<

packages/next/server/base-server.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { parse as parseQs } from 'querystring'
2727
import { format as formatUrl, parse as parseUrl } from 'url'
2828
import { getRedirectStatus } from '../lib/load-custom-routes'
2929
import {
30+
NEXT_BUILTIN_DOCUMENT,
3031
SERVERLESS_DIRECTORY,
3132
SERVER_DIRECTORY,
3233
STATIC_STATUS_PAGES,
@@ -1221,7 +1222,7 @@ export default abstract class Server<ServerOptions extends Options = Options> {
12211222
// When concurrent features is enabled, the built-in `Document`
12221223
// component also supports dynamic HTML.
12231224
(this.renderOpts.reactRoot &&
1224-
!!(components.Document as any)?.__next_internal_document)
1225+
NEXT_BUILTIN_DOCUMENT in components.Document)
12251226

12261227
// Disable dynamic HTML in cases that we know it won't be generated,
12271228
// so that we can continue generating a cache key when possible.

packages/next/server/render.tsx

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { DomainLocale } from './config'
66
import type {
77
AppType,
88
DocumentInitialProps,
9+
DocumentType,
910
DocumentProps,
1011
DocumentContext,
1112
NextComponentType,
@@ -35,6 +36,7 @@ import {
3536
UNSTABLE_REVALIDATE_RENAME_ERROR,
3637
} from '../lib/constants'
3738
import {
39+
NEXT_BUILTIN_DOCUMENT,
3840
SERVER_PROPS_ID,
3941
STATIC_PROPS_ID,
4042
STATIC_STATUS_PAGES,
@@ -769,6 +771,7 @@ export async function renderToHTML(
769771

770772
const { html, head } = await docCtx.renderPage({ enhanceApp })
771773
const styles = jsxStyleRegistry.styles({ nonce: options.nonce })
774+
jsxStyleRegistry.flush()
772775
return { html, head, styles }
773776
},
774777
}
@@ -1311,14 +1314,14 @@ export async function renderToHTML(
13111314
// 1. Using `Document.getInitialProps` in the Edge runtime.
13121315
// 2. Using the class component `Document` with concurrent features.
13131316

1314-
const builtinDocument = (Document as any).__next_internal_document as
1315-
| typeof Document
1316-
| undefined
1317+
const BuiltinFunctionalDocument: DocumentType | undefined = (
1318+
Document as any
1319+
)[NEXT_BUILTIN_DOCUMENT]
13171320

13181321
if (process.env.NEXT_RUNTIME === 'edge' && Document.getInitialProps) {
13191322
// In the Edge runtime, `Document.getInitialProps` isn't supported.
13201323
// We throw an error here if it's customized.
1321-
if (!builtinDocument) {
1324+
if (!BuiltinFunctionalDocument) {
13221325
throw new Error(
13231326
'`getInitialProps` in Document component is not supported with the Edge Runtime.'
13241327
)
@@ -1329,8 +1332,8 @@ export async function renderToHTML(
13291332
(isServerComponent || process.env.NEXT_RUNTIME === 'edge') &&
13301333
Document.getInitialProps
13311334
) {
1332-
if (builtinDocument) {
1333-
Document = builtinDocument
1335+
if (BuiltinFunctionalDocument) {
1336+
Document = BuiltinFunctionalDocument
13341337
} else {
13351338
throw new Error(
13361339
'`getInitialProps` in Document component is not supported with React Server Components.'
@@ -1433,6 +1436,7 @@ export async function renderToHTML(
14331436
}
14341437

14351438
if (!process.env.__NEXT_REACT_ROOT) {
1439+
// Enabling react legacy rendering mode: __NEXT_REACT_ROOT = false
14361440
if (Document.getInitialProps) {
14371441
const documentInitialPropsRes = await documentInitialProps()
14381442
if (documentInitialPropsRes === null) return null
@@ -1468,6 +1472,7 @@ export async function renderToHTML(
14681472
}
14691473
}
14701474
} else {
1475+
// Enabling react concurrent rendering mode: __NEXT_REACT_ROOT = true
14711476
let renderStream: ReadableStream<Uint8Array> & {
14721477
allReady?: Promise<void> | undefined
14731478
}
@@ -1534,13 +1539,11 @@ export async function renderToHTML(
15341539
// be streamed.
15351540
// Do not use `await` here.
15361541
generateStaticFlightDataIfNeeded()
1537-
15381542
return await continueFromInitialStream({
15391543
renderStream,
15401544
suffix,
15411545
dataStream: serverComponentsInlinedTransformStream?.readable,
1542-
generateStaticHTML:
1543-
generateStaticHTML || !process.env.__NEXT_REACT_ROOT,
1546+
generateStaticHTML,
15441547
flushEffectHandler,
15451548
})
15461549
}
@@ -1572,8 +1575,8 @@ export async function renderToHTML(
15721575

15731576
return <Document {...htmlProps} {...docProps} />
15741577
}
1575-
let styles
15761578

1579+
let styles
15771580
if (hasDocumentGetInitialProps) {
15781581
styles = docProps.styles
15791582
} else {

packages/next/shared/lib/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export const CLIENT_PUBLIC_FILES_PATH = 'public'
2525
export const CLIENT_STATIC_FILES_PATH = 'static'
2626
export const CLIENT_STATIC_FILES_RUNTIME = 'runtime'
2727
export const STRING_LITERAL_DROP_BUNDLE = '__NEXT_DROP_CLIENT_FILE__'
28+
export const NEXT_BUILTIN_DOCUMENT = '__NEXT_BUILTIN_DOCUMENT__'
2829

2930
// server/middleware-flight-manifest.js
3031
export const MIDDLEWARE_FLIGHT_MANIFEST = 'middleware-flight-manifest'
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export default function Page() {
2+
return (
3+
<div>
4+
<style jsx>{'p { color: red; }'}</style>
5+
<p>hello world</p>
6+
</div>
7+
)
8+
}

test/e2e/styled-jsx/index.test.ts

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,19 @@
1-
import { createNext } from 'e2e-utils'
1+
import path from 'path'
2+
import { createNext, FileRef } from 'e2e-utils'
23
import { NextInstance } from 'test/lib/next-modes/base'
34
import { renderViaHTTP } from 'next-test-utils'
45
import cheerio from 'cheerio'
56

7+
const appDir = path.join(__dirname, 'app')
8+
69
function runTest(packageManager?: string) {
710
describe(`styled-jsx${packageManager ? ' ' + packageManager : ''}`, () => {
811
let next: NextInstance
912

1013
beforeAll(async () => {
1114
next = await createNext({
1215
files: {
13-
'pages/index.js': `
14-
export default function Page() {
15-
return (
16-
<div>
17-
<style jsx>{'p { color: red; }'}</style>
18-
<p>hello world</p>
19-
</div>
20-
)
21-
}
22-
`,
16+
pages: new FileRef(path.join(appDir, 'pages')),
2317
},
2418
...(packageManager
2519
? {
@@ -33,7 +27,7 @@ function runTest(packageManager?: string) {
3327
it('should contain styled-jsx styles in html', async () => {
3428
const html = await renderViaHTTP(next.url, '/')
3529
const $ = cheerio.load(html)
36-
expect($('head').text()).toContain('color:red')
30+
expect($('html').text()).toMatch(/color:(\s)*red/)
3731
})
3832
})
3933
}

test/integration/react-18/test/concurrent.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,14 @@ export default (context, _render) => {
3232
})
3333
})
3434

35-
it('flushes styles as the page renders', async () => {
35+
it('flushes styled-jsx styles as the page renders', async () => {
36+
const html = await renderViaHTTP(
37+
context.appPort,
38+
'/use-flush-effect/styled-jsx'
39+
)
40+
const stylesOccurrence = html.match(/color:(\s)*blue/g) || []
41+
expect(stylesOccurrence.length).toBe(1)
42+
3643
await withBrowser('/use-flush-effect/styled-jsx', async (browser) => {
3744
await check(
3845
() => browser.waitForElementByCss('#__jsx-900f996af369fc74').text(),

0 commit comments

Comments
 (0)