From d1f002077171c418ff95dec091e4decc6281d0da Mon Sep 17 00:00:00 2001 From: Josh Story Date: Fri, 9 Jun 2023 11:15:32 -0700 Subject: [PATCH 1/8] This just adds a test case that will be useful in a later patch to implement chunk preloading. I add it now because I am using it to check for flaws in the later change I make to the webpack config and aliasing --- .../app/_components/junk-drawer/augment.ts | 3 + .../app/_components/junk-drawer/getValue.ts | 6 + .../app/_components/junk-drawer/thingOne.ts | 6 + .../app/_components/junk-drawer/thingTwo.ts | 6 + .../app/account/ClientShared.tsx | 13 + .../app-dir/chunk-loading/app/account/page.js | 22 + .../app/account/styles.module.css | 3 + .../chunk-loading/app/feed/ClientShared.tsx | 14 + .../app-dir/chunk-loading/app/feed/page.js | 13 + test/e2e/app-dir/chunk-loading/app/layout.tsx | 21 + test/e2e/app-dir/chunk-loading/app/page.tsx | 12 + .../components/DynamicShared.tsx | 15 + .../chunk-loading/components/LazyShared.tsx | 10 + .../chunk-loading/components/SuperShared.tsx | 537 ++++++++++++++++++ .../chunk-loading/components/links.tsx | 20 + test/e2e/app-dir/chunk-loading/next.config.js | 1 + .../app-dir/chunk-loading/pages/page/about.js | 19 + 17 files changed, 721 insertions(+) create mode 100644 test/e2e/app-dir/chunk-loading/app/_components/junk-drawer/augment.ts create mode 100644 test/e2e/app-dir/chunk-loading/app/_components/junk-drawer/getValue.ts create mode 100644 test/e2e/app-dir/chunk-loading/app/_components/junk-drawer/thingOne.ts create mode 100644 test/e2e/app-dir/chunk-loading/app/_components/junk-drawer/thingTwo.ts create mode 100644 test/e2e/app-dir/chunk-loading/app/account/ClientShared.tsx create mode 100644 test/e2e/app-dir/chunk-loading/app/account/page.js create mode 100644 test/e2e/app-dir/chunk-loading/app/account/styles.module.css create mode 100644 test/e2e/app-dir/chunk-loading/app/feed/ClientShared.tsx create mode 100644 test/e2e/app-dir/chunk-loading/app/feed/page.js create mode 100644 test/e2e/app-dir/chunk-loading/app/layout.tsx create mode 100644 test/e2e/app-dir/chunk-loading/app/page.tsx create mode 100644 test/e2e/app-dir/chunk-loading/components/DynamicShared.tsx create mode 100644 test/e2e/app-dir/chunk-loading/components/LazyShared.tsx create mode 100644 test/e2e/app-dir/chunk-loading/components/SuperShared.tsx create mode 100644 test/e2e/app-dir/chunk-loading/components/links.tsx create mode 100644 test/e2e/app-dir/chunk-loading/next.config.js create mode 100644 test/e2e/app-dir/chunk-loading/pages/page/about.js diff --git a/test/e2e/app-dir/chunk-loading/app/_components/junk-drawer/augment.ts b/test/e2e/app-dir/chunk-loading/app/_components/junk-drawer/augment.ts new file mode 100644 index 0000000000000..2508eb61f6a05 --- /dev/null +++ b/test/e2e/app-dir/chunk-loading/app/_components/junk-drawer/augment.ts @@ -0,0 +1,3 @@ +export function augment(input) { + return input.slice(6) + input.slice(0, 6) +} diff --git a/test/e2e/app-dir/chunk-loading/app/_components/junk-drawer/getValue.ts b/test/e2e/app-dir/chunk-loading/app/_components/junk-drawer/getValue.ts new file mode 100644 index 0000000000000..40f30be71cfa4 --- /dev/null +++ b/test/e2e/app-dir/chunk-loading/app/_components/junk-drawer/getValue.ts @@ -0,0 +1,6 @@ +import { augment } from './augment' + +export function getValue(seed) { + let value = seed > 'asdfasd' ? 'll9' + seed : seed + 'aasdf' + return augment(value) +} diff --git a/test/e2e/app-dir/chunk-loading/app/_components/junk-drawer/thingOne.ts b/test/e2e/app-dir/chunk-loading/app/_components/junk-drawer/thingOne.ts new file mode 100644 index 0000000000000..95dc4844ad075 --- /dev/null +++ b/test/e2e/app-dir/chunk-loading/app/_components/junk-drawer/thingOne.ts @@ -0,0 +1,6 @@ +import { augment } from './augment' + +export function thingOne() { + let x = ((Math.random() * 100000) | 0).toString(16) + console.log('thingOne', augment(x)) +} diff --git a/test/e2e/app-dir/chunk-loading/app/_components/junk-drawer/thingTwo.ts b/test/e2e/app-dir/chunk-loading/app/_components/junk-drawer/thingTwo.ts new file mode 100644 index 0000000000000..e223c750d60e0 --- /dev/null +++ b/test/e2e/app-dir/chunk-loading/app/_components/junk-drawer/thingTwo.ts @@ -0,0 +1,6 @@ +import { getValue } from './getValue' + +export function thingTwo() { + let x = ((Math.random() * 100000) | 0).toString(16) + console.log('thingTwo', getValue(x)) +} diff --git a/test/e2e/app-dir/chunk-loading/app/account/ClientShared.tsx b/test/e2e/app-dir/chunk-loading/app/account/ClientShared.tsx new file mode 100644 index 0000000000000..6dce596785255 --- /dev/null +++ b/test/e2e/app-dir/chunk-loading/app/account/ClientShared.tsx @@ -0,0 +1,13 @@ +'use client' + +import { SuperShared } from '../../components/SuperShared' +import { LazyShared } from '../../components/LazyShared' +import 'client-only' + +export function ClientShared() { + return +} + +export function ClientDynamicShared() { + return +} diff --git a/test/e2e/app-dir/chunk-loading/app/account/page.js b/test/e2e/app-dir/chunk-loading/app/account/page.js new file mode 100644 index 0000000000000..7e6ef0be86e83 --- /dev/null +++ b/test/e2e/app-dir/chunk-loading/app/account/page.js @@ -0,0 +1,22 @@ +import { ClientShared, ClientDynamicShared } from './ClientShared' +import 'server-only' + +import styles from './styles.module.css' + +export default function AccountPage() { + return ( +
+

Account Page

+

+ Welcome to your account page. Here you can increment your account + counter +

+
+ +
+
+ +
+
+ ) +} diff --git a/test/e2e/app-dir/chunk-loading/app/account/styles.module.css b/test/e2e/app-dir/chunk-loading/app/account/styles.module.css new file mode 100644 index 0000000000000..944ede8a50609 --- /dev/null +++ b/test/e2e/app-dir/chunk-loading/app/account/styles.module.css @@ -0,0 +1,3 @@ +.blue { + background-color: lightblue; +} diff --git a/test/e2e/app-dir/chunk-loading/app/feed/ClientShared.tsx b/test/e2e/app-dir/chunk-loading/app/feed/ClientShared.tsx new file mode 100644 index 0000000000000..4ae9af45a7fb8 --- /dev/null +++ b/test/e2e/app-dir/chunk-loading/app/feed/ClientShared.tsx @@ -0,0 +1,14 @@ +'use client' + +import { useReducer } from 'react' + +import { LazyShared } from '../../components/LazyShared' + +export function ClientDynamicShared() { + let [shouldload, load] = useReducer(() => true, false) + if (shouldload) { + return + } else { + return + } +} diff --git a/test/e2e/app-dir/chunk-loading/app/feed/page.js b/test/e2e/app-dir/chunk-loading/app/feed/page.js new file mode 100644 index 0000000000000..2469ba5baab34 --- /dev/null +++ b/test/e2e/app-dir/chunk-loading/app/feed/page.js @@ -0,0 +1,13 @@ +import { ClientDynamicShared } from './ClientShared' + +export default function FeedPage() { + return ( +
+

Feed Page

+

Welcome to your feed page.

+
+ +
+
+ ) +} diff --git a/test/e2e/app-dir/chunk-loading/app/layout.tsx b/test/e2e/app-dir/chunk-loading/app/layout.tsx new file mode 100644 index 0000000000000..d245f9218b312 --- /dev/null +++ b/test/e2e/app-dir/chunk-loading/app/layout.tsx @@ -0,0 +1,21 @@ +import { Links } from '../components/links' + +export const metadata = { + title: 'Next.js', + description: 'Generated by Next.js', +} + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + + {children} + + + + ) +} diff --git a/test/e2e/app-dir/chunk-loading/app/page.tsx b/test/e2e/app-dir/chunk-loading/app/page.tsx new file mode 100644 index 0000000000000..8266002b1ea15 --- /dev/null +++ b/test/e2e/app-dir/chunk-loading/app/page.tsx @@ -0,0 +1,12 @@ +export default function Page() { + return ( +
+

Chunk-Loading

+

+ This fixture is a testbed for chunk loading. It has a variety of + sub-pages that combine client components as well as dynamic imports. It + should demonstrate optimal chunk loading characteristics with no errors +

+
+ ) +} diff --git a/test/e2e/app-dir/chunk-loading/components/DynamicShared.tsx b/test/e2e/app-dir/chunk-loading/components/DynamicShared.tsx new file mode 100644 index 0000000000000..e98cab626a66d --- /dev/null +++ b/test/e2e/app-dir/chunk-loading/components/DynamicShared.tsx @@ -0,0 +1,15 @@ +import dynamic from 'next/dynamic' + +const DynamicSuperShared = dynamic( + async () => { + const module = await import('./SuperShared') + return module.SuperShared + }, + { + loading: () =>
loading...
, + } +) + +export function DynamicShared() { + return +} diff --git a/test/e2e/app-dir/chunk-loading/components/LazyShared.tsx b/test/e2e/app-dir/chunk-loading/components/LazyShared.tsx new file mode 100644 index 0000000000000..e5a6bf932e69d --- /dev/null +++ b/test/e2e/app-dir/chunk-loading/components/LazyShared.tsx @@ -0,0 +1,10 @@ +import { lazy } from 'react' + +const LazySuperShared = lazy(async () => { + const module = await import('./SuperShared') + return { default: module.SuperShared } +}) + +export function LazyShared() { + return +} diff --git a/test/e2e/app-dir/chunk-loading/components/SuperShared.tsx b/test/e2e/app-dir/chunk-loading/components/SuperShared.tsx new file mode 100644 index 0000000000000..3b1f5c9fa740b --- /dev/null +++ b/test/e2e/app-dir/chunk-loading/components/SuperShared.tsx @@ -0,0 +1,537 @@ +import { thingOne } from '../app/_components/junk-drawer/thingOne' +import { thingTwo } from '../app/_components/junk-drawer/thingTwo' + +export function SuperShared({ from }: { from: 'flight' | 'fizz' | 'dynamic' }) { + const phrase = + from === 'flight' + ? 'loaded on the server (RSC)' + : from === 'fizz' + ? 'loaded on the client (Fizz/Fiber)' + : from === 'dynamic' + ? 'loaded dynamically' + : 'not configured to say where it was loaded' + + if (typeof globalThis.UNKNOWN_GLOBAL_BINDING === 'boolean') { + thingOne() + let foo = + 'I need to use up significant bytes to make this file be split into its own chunk' + foo = 'to do that I am going to add a lot of big string such as this one' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + foo = + 'I should just alternately repeat this and another string over and over again' + foo = + 'This is the other repeating string. It should be about the same length' + console.log('foo', foo) + thingTwo() + } + return ( +

+ this component is part of the server graph and the client graph. It is + also going to be dynamically imported by webpack. This particular instance + was {phrase}. +

+ ) +} diff --git a/test/e2e/app-dir/chunk-loading/components/links.tsx b/test/e2e/app-dir/chunk-loading/components/links.tsx new file mode 100644 index 0000000000000..2ea2e1ca87fab --- /dev/null +++ b/test/e2e/app-dir/chunk-loading/components/links.tsx @@ -0,0 +1,20 @@ +import Link from 'next/link' + +export function Links() { + return ( +
    +
  • + Feed +
  • +
  • + Account +
  • +
  • + about +
  • +
  • + Home +
  • +
+ ) +} diff --git a/test/e2e/app-dir/chunk-loading/next.config.js b/test/e2e/app-dir/chunk-loading/next.config.js new file mode 100644 index 0000000000000..4ba52ba2c8df6 --- /dev/null +++ b/test/e2e/app-dir/chunk-loading/next.config.js @@ -0,0 +1 @@ +module.exports = {} diff --git a/test/e2e/app-dir/chunk-loading/pages/page/about.js b/test/e2e/app-dir/chunk-loading/pages/page/about.js new file mode 100644 index 0000000000000..a77795c930428 --- /dev/null +++ b/test/e2e/app-dir/chunk-loading/pages/page/about.js @@ -0,0 +1,19 @@ +import { useReducer } from 'react' +import { Links } from '../../components/links' + +import { DynamicShared } from '../../components/DynamicShared' + +export default function About() { + let [shouldload, load] = useReducer(() => true, false) + return ( + <> +
About
+ {shouldload ? ( + + ) : ( + + )} + + + ) +} From 9b2bdd7fbf8e9d2684a6cbdeeb7b757b0e316926 Mon Sep 17 00:00:00 2001 From: Josh Story Date: Mon, 18 Sep 2023 14:36:15 -0700 Subject: [PATCH 2/8] update task to vendor react-server turbopack bindings --- packages/next/taskfile.js | 73 +++++++++++++++++++++++++++++++++------ 1 file changed, 62 insertions(+), 11 deletions(-) diff --git a/packages/next/taskfile.js b/packages/next/taskfile.js index 80d6dbaeeaa34..dd6858d28958b 100644 --- a/packages/next/taskfile.js +++ b/packages/next/taskfile.js @@ -1798,32 +1798,83 @@ export async function copy_vendor_react(task_) { // react-server-dom-webpack // Currently, this `next` and `experimental` channels are always in sync so // we can use the same version for both. - const reactServerDomDir = dirname( + const reactServerDomWebpackDir = dirname( relative( __dirname, require.resolve(`react-server-dom-webpack${packageSuffix}/package.json`) ) ) yield task - .source(join(reactServerDomDir, 'LICENSE')) + .source(join(reactServerDomWebpackDir, 'LICENSE')) .target(`src/compiled/react-server-dom-webpack${packageSuffix}`) yield task - .source(join(reactServerDomDir, '{package.json,*.js,cjs/**/*.js}')) + .source(join(reactServerDomWebpackDir, '{package.json,*.js,cjs/**/*.js}')) // eslint-disable-next-line require-yield .run({ every: true }, function* (file) { - const source = file.data.toString() // We replace the module/chunk loading code with our own implementation in Next.js. - // NOTE: We don't alias react and react-dom here since they could change while bundling, - // let bundling picking logic controlled by webpack. - file.data = source - .replace(/__webpack_chunk_load__/g, 'globalThis.__next_chunk_load__') - .replace(/__webpack_require__/g, 'globalThis.__next_require__') - - if (file.base === 'package.json') { + // NOTE: We only replace module/chunk loading for server builds because the server + // bundles have unique constraints like a runtime bundle. For browser builds this + // package will be bundled alongside user code and we don't need to introduce the extra + // indirection + if ( + (file.base.startsWith('react-server-dom-webpack-client') && + !file.base.startsWith('react-server-dom-webpack-client.browser')) || + (file.base.startsWith('react-server-dom-webpack-server') && + !file.base.startsWith('react-server-dom-webpack-server.browser')) + ) { + const source = file.data.toString() + file.data = source.replace( + /__webpack_require__/g, + 'globalThis.__next_require__' + ) + } else if (file.base === 'package.json') { file.data = overridePackageName(file.data) } }) .target(`src/compiled/react-server-dom-webpack${packageSuffix}`) + + // react-server-dom-turbopack + // Currently, this `next` and `experimental` channels are always in sync so + // we can use the same version for both. + const reactServerDomTurbopackDir = dirname( + relative( + __dirname, + require.resolve( + `react-server-dom-turbopack${packageSuffix}/package.json` + ) + ) + ) + yield task + .source(join(reactServerDomTurbopackDir, 'LICENSE')) + .target(`src/compiled/react-server-dom-turbopack${packageSuffix}`) + yield task + .source( + join(reactServerDomTurbopackDir, '{package.json,*.js,cjs/**/*.js}') + ) + // eslint-disable-next-line require-yield + .run({ every: true }, function* (file) { + // We replace the module loading code with our own implementation in Next.js. + // NOTE: We only replace module loading for server builds because the server + // bundles have unique constraints like a runtime bundle. For browser builds this + // package will be bundled alongside user code and we don't need to introduce the extra + // indirection + if ( + (file.base.startsWith('react-server-dom-turbopack-client') && + !file.base.startsWith( + 'react-server-dom-turbopack-client.browser' + )) || + (file.base.startsWith('react-server-dom-turbopack-server') && + !file.base.startsWith('react-server-dom-turbopack-server.browser')) + ) { + const source = file.data.toString() + file.data = source + .replace(/__turbopack_load__/g, 'globalThis.__next_chunk_load__') + .replace(/__turbopack_require__/g, 'globalThis.__next_require__') + } else if (file.base === 'package.json') { + file.data = overridePackageName(file.data) + } + }) + .target(`src/compiled/react-server-dom-turbopack${packageSuffix}`) } // As taskr transpiles async functions into generators, to reuse the same logic From 7a2fd0b48d48807001d3ebdb0d3c2f47b1ec7ac1 Mon Sep 17 00:00:00 2001 From: Josh Story Date: Wed, 27 Sep 2023 10:18:59 -0700 Subject: [PATCH 3/8] Updates react packages to 18.3.0-canary-f81c0f1ed-20230927 adds react-server-dom-turbopack as a dependency --- package.json | 18 +- ...t-dom-server-legacy.browser.development.js | 1328 +++--- ...om-server-legacy.browser.production.min.js | 286 +- ...eact-dom-server-legacy.node.development.js | 1328 +++--- ...t-dom-server-legacy.node.production.min.js | 311 +- ...t-dom-server-rendering-stub.development.js | 50 +- ...om-server-rendering-stub.production.min.js | 14 +- .../react-dom-server.browser.development.js | 1325 +++--- ...react-dom-server.browser.production.min.js | 268 +- .../cjs/react-dom-server.edge.development.js | 1325 +++--- .../react-dom-server.edge.production.min.js | 343 +- .../cjs/react-dom-server.node.development.js | 1325 +++--- .../react-dom-server.node.production.min.js | 321 +- .../react-dom-unstable_testing.development.js | 101 +- ...act-dom-unstable_testing.production.min.js | 24 +- .../cjs/react-dom.development.js | 101 +- .../cjs/react-dom.production.min.js | 24 +- .../cjs/react-dom.profiling.min.js | 124 +- .../react-dom.shared-subset.development.js | 48 +- .../react-dom.shared-subset.production.min.js | 11 +- .../react-dom-experimental/package.json | 4 +- ...t-dom-server-legacy.browser.development.js | 1282 +++--- ...om-server-legacy.browser.production.min.js | 267 +- ...eact-dom-server-legacy.node.development.js | 1282 +++--- ...t-dom-server-legacy.node.production.min.js | 265 +- ...t-dom-server-rendering-stub.development.js | 50 +- ...om-server-rendering-stub.production.min.js | 14 +- .../react-dom-server.browser.development.js | 1277 +++--- ...react-dom-server.browser.production.min.js | 240 +- .../cjs/react-dom-server.edge.development.js | 1277 +++--- .../react-dom-server.edge.production.min.js | 254 +- .../cjs/react-dom-server.node.development.js | 1277 +++--- .../react-dom-server.node.production.min.js | 247 +- .../react-dom/cjs/react-dom.development.js | 98 +- .../react-dom/cjs/react-dom.production.min.js | 172 +- .../react-dom/cjs/react-dom.profiling.min.js | 40 +- .../react-dom.shared-subset.development.js | 48 +- .../react-dom.shared-subset.production.min.js | 11 +- .../next/src/compiled/react-dom/package.json | 4 +- .../cjs/react.development.js | 6 +- .../cjs/react.production.min.js | 2 +- .../cjs/react.shared-subset.development.js | 6 +- .../cjs/react.shared-subset.production.min.js | 2 +- .../LICENSE | 21 + ...-dom-webpack-client.browser.development.js | 2244 ++++++++++ ...m-webpack-client.browser.production.min.js | 39 + ...ver-dom-webpack-client.edge.development.js | 2447 +++++++++++ ...-dom-webpack-client.edge.production.min.js | 44 + ...ver-dom-webpack-client.node.development.js | 2417 +++++++++++ ...-dom-webpack-client.node.production.min.js | 43 + ...bpack-client.node.unbundled.development.js | 2370 ++++++++++ ...ck-client.node.unbundled.production.min.js | 41 + .../react-server-dom-webpack-node-register.js | 15 + .../cjs/react-server-dom-webpack-plugin.js | 25 + ...-dom-webpack-server.browser.development.js | 3620 ++++++++++++++++ ...m-webpack-server.browser.production.min.js | 78 + ...ver-dom-webpack-server.edge.development.js | 3613 ++++++++++++++++ ...-dom-webpack-server.edge.production.min.js | 79 + ...ver-dom-webpack-server.node.development.js | 3805 +++++++++++++++++ ...-dom-webpack-server.node.production.min.js | 83 + ...bpack-server.node.unbundled.development.js | 3732 ++++++++++++++++ ...ck-server.node.unbundled.production.min.js | 80 + .../client.browser.js | 7 + .../client.edge.js | 7 + .../client.js | 3 + .../client.node.js | 7 + .../client.node.unbundled.js | 7 + .../index.js | 12 + .../node-register.js | 3 + .../package.json | 54 + .../plugin.js | 3 + .../server.browser.js | 7 + .../server.edge.js | 7 + .../server.js | 6 + .../server.node.js | 7 + .../server.node.unbundled.js | 7 + .../react-server-dom-turbopack/LICENSE | 21 + ...om-turbopack-client.browser.development.js | 2043 +++++++++ ...turbopack-client.browser.production.min.js | 36 + ...r-dom-turbopack-client.edge.development.js | 2265 ++++++++++ ...om-turbopack-client.edge.production.min.js | 40 + ...r-dom-turbopack-client.node.development.js | 2235 ++++++++++ ...om-turbopack-client.node.production.min.js | 39 + ...opack-client.node.unbundled.development.js | 2190 ++++++++++ ...ck-client.node.unbundled.production.min.js | 37 + ...eact-server-dom-turbopack-node-register.js | 15 + ...om-turbopack-server.browser.development.js | 3390 +++++++++++++++ ...turbopack-server.browser.production.min.js | 73 + ...r-dom-turbopack-server.edge.development.js | 3402 +++++++++++++++ ...om-turbopack-server.edge.production.min.js | 74 + ...r-dom-turbopack-server.node.development.js | 3593 ++++++++++++++++ ...om-turbopack-server.node.production.min.js | 78 + ...opack-server.node.unbundled.development.js | 3522 +++++++++++++++ ...ck-server.node.unbundled.production.min.js | 75 + .../client.browser.js | 7 + .../react-server-dom-turbopack/client.edge.js | 7 + .../react-server-dom-turbopack/client.js | 3 + .../react-server-dom-turbopack/client.node.js | 7 + .../client.node.unbundled.js | 7 + .../react-server-dom-turbopack/index.js | 12 + .../node-register.js | 3 + .../react-server-dom-turbopack/package.json | 54 + .../server.browser.js | 7 + .../react-server-dom-turbopack/server.edge.js | 7 + .../react-server-dom-turbopack/server.js | 6 + .../react-server-dom-turbopack/server.node.js | 7 + .../server.node.unbundled.js | 7 + ...-dom-webpack-client.browser.development.js | 143 +- ...m-webpack-client.browser.production.min.js | 47 +- ...ver-dom-webpack-client.edge.development.js | 159 +- ...-dom-webpack-client.edge.production.min.js | 58 +- ...ver-dom-webpack-client.node.development.js | 161 +- ...-dom-webpack-client.node.production.min.js | 60 +- ...bpack-client.node.unbundled.development.js | 119 +- ...ck-client.node.unbundled.production.min.js | 59 +- .../cjs/react-server-dom-webpack-plugin.js | 22 +- ...-dom-webpack-server.browser.development.js | 130 +- ...m-webpack-server.browser.production.min.js | 113 +- ...ver-dom-webpack-server.edge.development.js | 109 +- ...-dom-webpack-server.edge.production.min.js | 97 +- ...ver-dom-webpack-server.node.development.js | 109 +- ...-dom-webpack-server.node.production.min.js | 67 +- ...bpack-server.node.unbundled.development.js | 57 +- ...ck-server.node.unbundled.production.min.js | 60 +- .../package.json | 4 +- ...-dom-webpack-client.browser.development.js | 143 +- ...m-webpack-client.browser.production.min.js | 49 +- ...ver-dom-webpack-client.edge.development.js | 159 +- ...-dom-webpack-client.edge.production.min.js | 54 +- ...ver-dom-webpack-client.node.development.js | 161 +- ...-dom-webpack-client.node.production.min.js | 59 +- ...bpack-client.node.unbundled.development.js | 119 +- ...ck-client.node.unbundled.production.min.js | 55 +- .../cjs/react-server-dom-webpack-plugin.js | 22 +- ...-dom-webpack-server.browser.development.js | 130 +- ...m-webpack-server.browser.production.min.js | 63 +- ...ver-dom-webpack-server.edge.development.js | 109 +- ...-dom-webpack-server.edge.production.min.js | 97 +- ...ver-dom-webpack-server.node.development.js | 109 +- ...-dom-webpack-server.node.production.min.js | 95 +- ...bpack-server.node.unbundled.development.js | 57 +- ...ck-server.node.unbundled.production.min.js | 94 +- .../react-server-dom-webpack/package.json | 4 +- .../compiled/react/cjs/react.development.js | 6 +- .../react/cjs/react.production.min.js | 2 +- .../cjs/react.shared-subset.development.js | 6 +- .../cjs/react.shared-subset.production.min.js | 2 +- pnpm-lock.yaml | 100 +- 148 files changed, 59792 insertions(+), 8472 deletions(-) create mode 100644 packages/next/src/compiled/react-server-dom-turbopack-experimental/LICENSE create mode 100644 packages/next/src/compiled/react-server-dom-turbopack-experimental/cjs/react-server-dom-webpack-client.browser.development.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack-experimental/cjs/react-server-dom-webpack-client.browser.production.min.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack-experimental/cjs/react-server-dom-webpack-client.edge.development.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack-experimental/cjs/react-server-dom-webpack-client.edge.production.min.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack-experimental/cjs/react-server-dom-webpack-client.node.development.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack-experimental/cjs/react-server-dom-webpack-client.node.production.min.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack-experimental/cjs/react-server-dom-webpack-client.node.unbundled.development.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack-experimental/cjs/react-server-dom-webpack-client.node.unbundled.production.min.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack-experimental/cjs/react-server-dom-webpack-node-register.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack-experimental/cjs/react-server-dom-webpack-plugin.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack-experimental/cjs/react-server-dom-webpack-server.browser.development.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack-experimental/cjs/react-server-dom-webpack-server.browser.production.min.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack-experimental/cjs/react-server-dom-webpack-server.edge.development.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack-experimental/cjs/react-server-dom-webpack-server.edge.production.min.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack-experimental/cjs/react-server-dom-webpack-server.node.development.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack-experimental/cjs/react-server-dom-webpack-server.node.production.min.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack-experimental/cjs/react-server-dom-webpack-server.node.unbundled.development.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack-experimental/cjs/react-server-dom-webpack-server.node.unbundled.production.min.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack-experimental/client.browser.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack-experimental/client.edge.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack-experimental/client.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack-experimental/client.node.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack-experimental/client.node.unbundled.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack-experimental/index.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack-experimental/node-register.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack-experimental/package.json create mode 100644 packages/next/src/compiled/react-server-dom-turbopack-experimental/plugin.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack-experimental/server.browser.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack-experimental/server.edge.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack-experimental/server.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack-experimental/server.node.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack-experimental/server.node.unbundled.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack/LICENSE create mode 100644 packages/next/src/compiled/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.browser.development.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.browser.production.min.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.edge.development.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.edge.production.min.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.node.development.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.node.production.min.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.node.unbundled.development.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.node.unbundled.production.min.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack/cjs/react-server-dom-turbopack-node-register.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.production.min.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.production.min.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.production.min.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.unbundled.development.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.unbundled.production.min.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack/client.browser.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack/client.edge.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack/client.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack/client.node.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack/client.node.unbundled.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack/index.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack/node-register.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack/package.json create mode 100644 packages/next/src/compiled/react-server-dom-turbopack/server.browser.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack/server.edge.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack/server.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack/server.node.js create mode 100644 packages/next/src/compiled/react-server-dom-turbopack/server.node.unbundled.js diff --git a/package.json b/package.json index 5eb99b7e19388..6cd4a3dac6c6f 100644 --- a/package.json +++ b/package.json @@ -192,14 +192,16 @@ "random-seed": "0.3.0", "react": "18.2.0", "react-17": "npm:react@17.0.2", - "react-builtin": "npm:react@18.3.0-canary-09285d5a7-20230925", + "react-builtin": "npm:react@18.3.0-canary-d900fadbf-20230929", "react-dom": "18.2.0", "react-dom-17": "npm:react-dom@17.0.2", - "react-dom-builtin": "npm:react-dom@18.3.0-canary-09285d5a7-20230925", - "react-dom-experimental-builtin": "npm:react-dom@0.0.0-experimental-09285d5a7-20230925", - "react-experimental-builtin": "npm:react@0.0.0-experimental-09285d5a7-20230925", - "react-server-dom-webpack": "18.3.0-canary-09285d5a7-20230925", - "react-server-dom-webpack-experimental": "npm:react-server-dom-webpack@0.0.0-experimental-09285d5a7-20230925", + "react-dom-builtin": "npm:react-dom@18.3.0-canary-d900fadbf-20230929", + "react-dom-experimental-builtin": "npm:react-dom@0.0.0-experimental-d900fadbf-20230929", + "react-experimental-builtin": "npm:react@0.0.0-experimental-d900fadbf-20230929", + "react-server-dom-turbopack": "18.3.0-canary-d900fadbf-20230929", + "react-server-dom-turbopack-experimental": "npm:react-server-dom-webpack@0.0.0-experimental-d900fadbf-20230929", + "react-server-dom-webpack": "18.3.0-canary-d900fadbf-20230929", + "react-server-dom-webpack-experimental": "npm:react-server-dom-webpack@0.0.0-experimental-d900fadbf-20230929", "react-ssr-prepass": "1.0.8", "react-virtualized": "9.22.3", "relay-compiler": "13.0.2", @@ -209,8 +211,8 @@ "resolve-from": "5.0.0", "sass": "1.54.0", "satori": "0.10.6", - "scheduler-builtin": "npm:scheduler@0.24.0-canary-09285d5a7-20230925", - "scheduler-experimental-builtin": "npm:scheduler@0.0.0-experimental-09285d5a7-20230925", + "scheduler-builtin": "npm:scheduler@0.24.0-canary-d900fadbf-20230929", + "scheduler-experimental-builtin": "npm:scheduler@0.0.0-experimental-d900fadbf-20230929", "seedrandom": "3.0.5", "selenium-webdriver": "4.0.0-beta.4", "semver": "7.3.7", diff --git a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server-legacy.browser.development.js b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server-legacy.browser.development.js index 63b0610dee700..d19392ed8d2cd 100644 --- a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server-legacy.browser.development.js +++ b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server-legacy.browser.development.js @@ -17,7 +17,7 @@ if (process.env.NODE_ENV !== "production") { var React = require("next/dist/compiled/react-experimental"); var ReactDOM = require('react-dom'); -var ReactVersion = '18.3.0-experimental-09285d5a7-20230925'; +var ReactVersion = '18.3.0-experimental-d900fadbf-20230929'; var ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; @@ -1651,9 +1651,27 @@ var SentFormReplayingRuntime = 16; // Per request, global state that is not contextual to the rendering subtree. // This cannot be resumed and therefore should only contain things that are // temporary working state or are never used in the prerender pass. -// Per response, global state that is not contextual to the rendering subtree. +// Credentials here are things that affect whether a browser will make a request +// as well as things that affect which connection the browser will use for that request. +// We want these to be aligned across preloads and resources because otherwise the preload +// will be wasted. +// We investigated whether referrerPolicy should be included here but from experimentation +// it seems that browsers do not treat this as part of the http cache key and does not affect +// which connection is used. + +var EXISTS = null; // This constant is to mark preloads that have no unique credentials +// to convey. It should never be checked by identity and we should not +// assume Preload values in ResumableState equal this value because they +// will have come from some parsed input. + +var PRELOAD_NO_CREDS = []; + +{ + Object.freeze(PRELOAD_NO_CREDS); +} // Per response, global state that is not contextual to the rendering subtree. // This is resumable and therefore should be serializable. + var dataElementQuotedEnd = stringToPrecomputedChunk('">'); var startInlineScript = stringToPrecomputedChunk(''); @@ -1760,12 +1778,16 @@ function createRenderState$1(resumableState, nonce, bootstrapScriptContent, boot fontPreloads: new Set(), highImagePreloads: new Set(), // usedImagePreloads: new Set(), - precedences: new Map(), - stylePrecedences: new Map(), + styles: new Map(), bootstrapScripts: new Set(), scripts: new Set(), bulkPreloads: new Set(), - preloadsMap: new Map(), + preloads: { + images: new Map(), + stylesheets: new Map(), + scripts: new Map(), + moduleScripts: new Map() + }, nonce: nonce, // like a module global for currently rendering boundary boundaryResources: null, @@ -1775,17 +1797,32 @@ function createRenderState$1(resumableState, nonce, bootstrapScriptContent, boot if (bootstrapScripts !== undefined) { for (var i = 0; i < bootstrapScripts.length; i++) { var scriptConfig = bootstrapScripts[i]; - var src = typeof scriptConfig === 'string' ? scriptConfig : scriptConfig.src; - var integrity = typeof scriptConfig === 'string' ? undefined : scriptConfig.integrity; - var crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; - preloadBootstrapScript(resumableState, renderState, src, nonce, integrity, crossOrigin); + var src = void 0, + crossOrigin = void 0, + integrity = void 0; + var props = { + rel: 'preload', + as: 'script', + fetchPriority: 'low', + nonce: nonce + }; + + if (typeof scriptConfig === 'string') { + props.href = src = scriptConfig; + } else { + props.href = src = scriptConfig.src; + props.integrity = integrity = typeof scriptConfig.integrity === 'string' ? scriptConfig.integrity : undefined; + props.crossOrigin = crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } + + preloadBootstrapScriptOrModule(resumableState, renderState, src, props); bootstrapChunks.push(startScriptSrc, stringToChunk(escapeTextForBrowser(src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (integrity) { + if (typeof integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(integrity))); } @@ -1801,20 +1838,32 @@ function createRenderState$1(resumableState, nonce, bootstrapScriptContent, boot for (var _i = 0; _i < bootstrapModules.length; _i++) { var _scriptConfig = bootstrapModules[_i]; - var _src = typeof _scriptConfig === 'string' ? _scriptConfig : _scriptConfig.src; + var _src = void 0, + _crossOrigin = void 0, + _integrity = void 0; - var _integrity = typeof _scriptConfig === 'string' ? undefined : _scriptConfig.integrity; + var _props = { + rel: 'modulepreload', + fetchPriority: 'low', + nonce: nonce + }; - var _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + if (typeof _scriptConfig === 'string') { + _props.href = _src = _scriptConfig; + } else { + _props.href = _src = _scriptConfig.src; + _props.integrity = _integrity = typeof _scriptConfig.integrity === 'string' ? _scriptConfig.integrity : undefined; + _props.crossOrigin = _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } - preloadBootstrapModule(resumableState, renderState, _src, nonce, _integrity, _crossOrigin); + preloadBootstrapScriptOrModule(resumableState, renderState, _src, _props); bootstrapChunks.push(startModuleSrc, stringToChunk(escapeTextForBrowser(_src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (_integrity) { + if (typeof _integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(_integrity))); } @@ -1847,10 +1896,18 @@ function createResumableState(identifierPrefix, externalRuntimeConfig) { hasHtml: false, // @TODO add bootstrap script to implicit preloads // persistent - preloadsMap: {}, - preconnectsMap: {}, - stylesMap: {}, - scriptsMap: {} + unknownResources: {}, + dnsResources: {}, + connectResources: { + default: {}, + anonymous: {}, + credentials: {} + }, + imageResources: {}, + styleResources: {}, + scriptResources: {}, + moduleUnknownResources: {}, + moduleScriptResources: {} }; } // Constants for the insertion mode we're currently writing in. We don't encode all HTML5 insertion // modes. We only include the variants as they matter for the sake of our purposes. @@ -3208,7 +3265,7 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse if (props.rel === 'stylesheet') { // This may hoistable as a Stylesheet Resource, otherwise it will emit in place - var key = getResourceKey('style', href); + var key = getResourceKey(href); if (typeof precedence !== 'string' || props.disabled != null || props.onLoad || props.onError) { // This stylesheet is either not opted into Resource semantics or has conflicting properties which @@ -3229,67 +3286,58 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse return pushLinkImpl(target, props); } else { // This stylesheet refers to a Resource and we create a new one if necessary - var stylesInPrecedence = renderState.precedences.get(precedence); - - if (!resumableState.stylesMap.hasOwnProperty(key)) { - var resourceProps = stylesheetPropsFromRawProps(props); - var state = NoState; + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; + + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; // If this is the first time we've encountered this precedence we need + // to create a StyleQueue + + if (!styleQueue) { + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); + } - if (resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = resumableState.preloadsMap[key]; - adoptPreloadPropsForStylesheetProps(resourceProps, preloadProps); - var preloadResource = renderState.preloadsMap.get(key); + var resource = { + state: PENDING$1, + props: stylesheetPropsFromRawProps(props) + }; - if (preloadResource) { - // If we already had a preload we don't want that resource to flush directly. - // We let the newly created resource govern flushing. - preloadResource.state |= Blocked; + if (resourceState) { + // When resourceState is truty it is a Preload state. We cast it for clarity + var preloadState = resourceState; - if (preloadResource.state & Flushed) { - state = PreloadFlushed; - } - } else { - // If we resumed then we assume that this was already flushed - // by the shell. - state = PreloadFlushed; + if (preloadState.length === 2) { + adoptPreloadCredentials(resource.props, preloadState); } - } - var resource = { - type: 'stylesheet', - chunks: [], - state: state, - props: resourceProps - }; - resumableState.stylesMap[key] = null; - - if (!stylesInPrecedence) { - stylesInPrecedence = new Map(); - renderState.precedences.set(precedence, stylesInPrecedence); - var emptyStyleResource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [] - } - }; - stylesInPrecedence.set('', emptyStyleResource); + var preloadResource = renderState.preloads.stylesheets.get(key); - { - if (renderState.stylePrecedences.has(precedence)) { - error('React constructed an empty style resource when a style resource already exists for this precedence: "%s". This is a bug in React.', precedence); - } + if (preloadResource && preloadResource.length > 0) { + // The Preload for this resource was created in this render pass and has not flushed yet so + // we need to clear it to avoid it flushing. + preloadResource.length = 0; + } else { + // Either the preload resource from this render already flushed in this render pass + // or the preload flushed in a prior pass (prerender). In either case we need to mark + // this resource as already having been preloaded. + resource.state = PRELOADED; } + } // We add the newly created resource to our StyleQueue and if necessary + // track the resource with the currently rendering boundary - renderState.stylePrecedences.set(precedence, emptyStyleResource); - } - stylesInPrecedence.set(key, resource); + styleQueue.sheets.set(key, resource); if (renderState.boundaryResources) { - renderState.boundaryResources.add(resource); + renderState.boundaryResources.stylesheets.add(resource); } } else { // We need to track whether this boundary should wait on this resource or not. @@ -3297,12 +3345,12 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse // it. However, it's possible when you resume that the style has already been emitted // and then it wouldn't be recreated in the RenderState and there's no need to track // it again since we should've hoisted it to the shell already. - if (stylesInPrecedence) { - var _resource = stylesInPrecedence.get(key); + if (styleQueue) { + var _resource = styleQueue.sheets.get(key); if (_resource) { if (renderState.boundaryResources) { - renderState.boundaryResources.add(_resource); + renderState.boundaryResources.stylesheets.add(_resource); } } } @@ -3400,47 +3448,47 @@ function pushStyle(target, props, resumableState, renderState, textEmbedded, ins } } - var key = getResourceKey('style', href); - var resource = renderState.stylePrecedences.get(precedence); + var key = getResourceKey(href); + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; - if (!resumableState.stylesMap.hasOwnProperty(key)) { - if (!resource) { - resource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [href] - } - }; - renderState.stylePrecedences.set(precedence, resource); - var stylesInPrecedence = new Map(); - stylesInPrecedence.set('', resource); + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; - { - if (renderState.precedences.has(precedence)) { - error('React constructed a new style precedence set when one already exists for this precedence: "%s". This is a bug in React.', precedence); - } + { + if (resourceState) { + error('React encountered a hoistable style tag for the same href as a preload: "%s". When using a style tag to inline styles you should not also preload it as a stylsheet.', href); } + } - renderState.precedences.set(precedence, stylesInPrecedence); + if (!styleQueue) { + // This is the first time we've encountered this precedence we need + // to create a StyleQueue. + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [stringToChunk(escapeTextForBrowser(href))], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); } else { - resource.props.hrefs.push(href); + // We have seen this precedence before and need to track this href + styleQueue.hrefs.push(stringToChunk(escapeTextForBrowser(href))); } - resumableState.stylesMap[key] = null; - pushStyleContents(resource.chunks, props); + pushStyleContents(styleQueue.rules, props); } - if (resource) { + if (styleQueue) { // We need to track whether this boundary should wait on this resource or not. // Typically this resource should always exist since we either had it or just created // it. However, it's possible when you resume that the style has already been emitted // and then it wouldn't be recreated in the RenderState and there's no need to track // it again since we should've hoisted it to the shell already. if (renderState.boundaryResources) { - renderState.boundaryResources.add(resource); + renderState.boundaryResources.styles.add(styleQueue); } } @@ -3529,36 +3577,35 @@ function pushStyleContents(target, props) { return; } -function getImagePreloadKey(href, imageSrcSet, imageSizes) { - var uniquePart = ''; - - if (typeof imageSrcSet === 'string' && imageSrcSet !== '') { - uniquePart += '[' + imageSrcSet + ']'; - - if (typeof imageSizes === 'string') { - uniquePart += '[' + imageSizes + ']'; - } - } else { - uniquePart += '[][]' + href; - } - - return getResourceKey('image', uniquePart); -} - function pushImg(target, props, resumableState, renderState, pictureTagInScope) { var src = props.src, srcSet = props.srcSet; - if (props.loading !== 'lazy' && (typeof src === 'string' || typeof srcSet === 'string') && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded + if (props.loading !== 'lazy' && (src || srcSet) && (typeof src === 'string' || src == null) && (typeof srcSet === 'string' || srcSet == null) && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded !(typeof src === 'string' && src[4] === ':' && (src[0] === 'd' || src[0] === 'D') && (src[1] === 'a' || src[1] === 'A') && (src[2] === 't' || src[2] === 'T') && (src[3] === 'a' || src[3] === 'A')) && !(typeof srcSet === 'string' && srcSet[4] === ':' && (srcSet[0] === 'd' || srcSet[0] === 'D') && (srcSet[1] === 'a' || srcSet[1] === 'A') && (srcSet[2] === 't' || srcSet[2] === 'T') && (srcSet[3] === 'a' || srcSet[3] === 'A'))) { // We have a suspensey image and ought to preload it to optimize the loading of display blocking // resumableState. - var sizes = props.sizes; - var key = getImagePreloadKey(src, srcSet, sizes); - var resource; + var sizes = typeof props.sizes === 'string' ? props.sizes : undefined; + var key = getImageResourceKey(src, srcSet, sizes); + var promotablePreloads = renderState.preloads.images; + var resource = promotablePreloads.get(key); + + if (resource) { + // We consider whether this preload can be promoted to higher priority flushing queue. + // The only time a resource will exist here is if it was created during this render + // and was not already in the high priority queue. + if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { + // Delete the resource from the map since we are promoting it and don't want to + // reenter this branch in a second pass for duplicate img hrefs. + promotablePreloads.delete(key); // $FlowFixMe - Flow should understand that this is a Resource if the condition was true - if (!resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = { + renderState.highImagePreloads.add(resource); + } + } else if (!resumableState.imageResources.hasOwnProperty(key)) { + // We must construct a new preload resource + resumableState.imageResources[key] = PRELOAD_NO_CREDS; + resource = []; + pushLinkImpl(resource, { rel: 'preload', as: 'image', // There is a bug in Safari where imageSrcSet is not respected on preload links @@ -3573,25 +3620,15 @@ function pushImg(target, props, resumableState, renderState, pictureTagInScope) type: props.type, fetchPriority: props.fetchPriority, referrerPolicy: props.referrerPolicy - }; - resource = { - type: 'preload', - chunks: [], - state: NoState, - props: preloadProps - }; - resumableState.preloadsMap[key] = preloadProps; - renderState.preloadsMap.set(key, resource); - pushLinkImpl(resource.chunks, preloadProps); - } else { - resource = renderState.preloadsMap.get(key); - } + }); - if (resource) { if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { renderState.highImagePreloads.add(resource); } else { - renderState.bulkPreloads.add(resource); + renderState.bulkPreloads.add(resource); // We can bump the priority up if the same img is rendered later + // with fetchPriority="high" + + promotablePreloads.set(key, resource); } } } @@ -3765,35 +3802,50 @@ function pushScript(target, props, resumableState, renderState, textEmbedded, in } var src = props.src; - var key = getResourceKey('script', src); // We can make this '); @@ -1761,12 +1779,16 @@ function createRenderState$1(resumableState, nonce, bootstrapScriptContent, boot fontPreloads: new Set(), highImagePreloads: new Set(), // usedImagePreloads: new Set(), - precedences: new Map(), - stylePrecedences: new Map(), + styles: new Map(), bootstrapScripts: new Set(), scripts: new Set(), bulkPreloads: new Set(), - preloadsMap: new Map(), + preloads: { + images: new Map(), + stylesheets: new Map(), + scripts: new Map(), + moduleScripts: new Map() + }, nonce: nonce, // like a module global for currently rendering boundary boundaryResources: null, @@ -1776,17 +1798,32 @@ function createRenderState$1(resumableState, nonce, bootstrapScriptContent, boot if (bootstrapScripts !== undefined) { for (var i = 0; i < bootstrapScripts.length; i++) { var scriptConfig = bootstrapScripts[i]; - var src = typeof scriptConfig === 'string' ? scriptConfig : scriptConfig.src; - var integrity = typeof scriptConfig === 'string' ? undefined : scriptConfig.integrity; - var crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; - preloadBootstrapScript(resumableState, renderState, src, nonce, integrity, crossOrigin); + var src = void 0, + crossOrigin = void 0, + integrity = void 0; + var props = { + rel: 'preload', + as: 'script', + fetchPriority: 'low', + nonce: nonce + }; + + if (typeof scriptConfig === 'string') { + props.href = src = scriptConfig; + } else { + props.href = src = scriptConfig.src; + props.integrity = integrity = typeof scriptConfig.integrity === 'string' ? scriptConfig.integrity : undefined; + props.crossOrigin = crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } + + preloadBootstrapScriptOrModule(resumableState, renderState, src, props); bootstrapChunks.push(startScriptSrc, stringToChunk(escapeTextForBrowser(src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (integrity) { + if (typeof integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(integrity))); } @@ -1802,20 +1839,32 @@ function createRenderState$1(resumableState, nonce, bootstrapScriptContent, boot for (var _i = 0; _i < bootstrapModules.length; _i++) { var _scriptConfig = bootstrapModules[_i]; - var _src = typeof _scriptConfig === 'string' ? _scriptConfig : _scriptConfig.src; + var _src = void 0, + _crossOrigin = void 0, + _integrity = void 0; - var _integrity = typeof _scriptConfig === 'string' ? undefined : _scriptConfig.integrity; + var _props = { + rel: 'modulepreload', + fetchPriority: 'low', + nonce: nonce + }; - var _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + if (typeof _scriptConfig === 'string') { + _props.href = _src = _scriptConfig; + } else { + _props.href = _src = _scriptConfig.src; + _props.integrity = _integrity = typeof _scriptConfig.integrity === 'string' ? _scriptConfig.integrity : undefined; + _props.crossOrigin = _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } - preloadBootstrapModule(resumableState, renderState, _src, nonce, _integrity, _crossOrigin); + preloadBootstrapScriptOrModule(resumableState, renderState, _src, _props); bootstrapChunks.push(startModuleSrc, stringToChunk(escapeTextForBrowser(_src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (_integrity) { + if (typeof _integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(_integrity))); } @@ -1848,10 +1897,18 @@ function createResumableState(identifierPrefix, externalRuntimeConfig) { hasHtml: false, // @TODO add bootstrap script to implicit preloads // persistent - preloadsMap: {}, - preconnectsMap: {}, - stylesMap: {}, - scriptsMap: {} + unknownResources: {}, + dnsResources: {}, + connectResources: { + default: {}, + anonymous: {}, + credentials: {} + }, + imageResources: {}, + styleResources: {}, + scriptResources: {}, + moduleUnknownResources: {}, + moduleScriptResources: {} }; } // Constants for the insertion mode we're currently writing in. We don't encode all HTML5 insertion // modes. We only include the variants as they matter for the sake of our purposes. @@ -3209,7 +3266,7 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse if (props.rel === 'stylesheet') { // This may hoistable as a Stylesheet Resource, otherwise it will emit in place - var key = getResourceKey('style', href); + var key = getResourceKey(href); if (typeof precedence !== 'string' || props.disabled != null || props.onLoad || props.onError) { // This stylesheet is either not opted into Resource semantics or has conflicting properties which @@ -3230,67 +3287,58 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse return pushLinkImpl(target, props); } else { // This stylesheet refers to a Resource and we create a new one if necessary - var stylesInPrecedence = renderState.precedences.get(precedence); - - if (!resumableState.stylesMap.hasOwnProperty(key)) { - var resourceProps = stylesheetPropsFromRawProps(props); - var state = NoState; + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; + + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; // If this is the first time we've encountered this precedence we need + // to create a StyleQueue + + if (!styleQueue) { + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); + } - if (resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = resumableState.preloadsMap[key]; - adoptPreloadPropsForStylesheetProps(resourceProps, preloadProps); - var preloadResource = renderState.preloadsMap.get(key); + var resource = { + state: PENDING$1, + props: stylesheetPropsFromRawProps(props) + }; - if (preloadResource) { - // If we already had a preload we don't want that resource to flush directly. - // We let the newly created resource govern flushing. - preloadResource.state |= Blocked; + if (resourceState) { + // When resourceState is truty it is a Preload state. We cast it for clarity + var preloadState = resourceState; - if (preloadResource.state & Flushed) { - state = PreloadFlushed; - } - } else { - // If we resumed then we assume that this was already flushed - // by the shell. - state = PreloadFlushed; + if (preloadState.length === 2) { + adoptPreloadCredentials(resource.props, preloadState); } - } - var resource = { - type: 'stylesheet', - chunks: [], - state: state, - props: resourceProps - }; - resumableState.stylesMap[key] = null; - - if (!stylesInPrecedence) { - stylesInPrecedence = new Map(); - renderState.precedences.set(precedence, stylesInPrecedence); - var emptyStyleResource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [] - } - }; - stylesInPrecedence.set('', emptyStyleResource); + var preloadResource = renderState.preloads.stylesheets.get(key); - { - if (renderState.stylePrecedences.has(precedence)) { - error('React constructed an empty style resource when a style resource already exists for this precedence: "%s". This is a bug in React.', precedence); - } + if (preloadResource && preloadResource.length > 0) { + // The Preload for this resource was created in this render pass and has not flushed yet so + // we need to clear it to avoid it flushing. + preloadResource.length = 0; + } else { + // Either the preload resource from this render already flushed in this render pass + // or the preload flushed in a prior pass (prerender). In either case we need to mark + // this resource as already having been preloaded. + resource.state = PRELOADED; } + } // We add the newly created resource to our StyleQueue and if necessary + // track the resource with the currently rendering boundary - renderState.stylePrecedences.set(precedence, emptyStyleResource); - } - stylesInPrecedence.set(key, resource); + styleQueue.sheets.set(key, resource); if (renderState.boundaryResources) { - renderState.boundaryResources.add(resource); + renderState.boundaryResources.stylesheets.add(resource); } } else { // We need to track whether this boundary should wait on this resource or not. @@ -3298,12 +3346,12 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse // it. However, it's possible when you resume that the style has already been emitted // and then it wouldn't be recreated in the RenderState and there's no need to track // it again since we should've hoisted it to the shell already. - if (stylesInPrecedence) { - var _resource = stylesInPrecedence.get(key); + if (styleQueue) { + var _resource = styleQueue.sheets.get(key); if (_resource) { if (renderState.boundaryResources) { - renderState.boundaryResources.add(_resource); + renderState.boundaryResources.stylesheets.add(_resource); } } } @@ -3401,47 +3449,47 @@ function pushStyle(target, props, resumableState, renderState, textEmbedded, ins } } - var key = getResourceKey('style', href); - var resource = renderState.stylePrecedences.get(precedence); + var key = getResourceKey(href); + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; - if (!resumableState.stylesMap.hasOwnProperty(key)) { - if (!resource) { - resource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [href] - } - }; - renderState.stylePrecedences.set(precedence, resource); - var stylesInPrecedence = new Map(); - stylesInPrecedence.set('', resource); + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; - { - if (renderState.precedences.has(precedence)) { - error('React constructed a new style precedence set when one already exists for this precedence: "%s". This is a bug in React.', precedence); - } + { + if (resourceState) { + error('React encountered a hoistable style tag for the same href as a preload: "%s". When using a style tag to inline styles you should not also preload it as a stylsheet.', href); } + } - renderState.precedences.set(precedence, stylesInPrecedence); + if (!styleQueue) { + // This is the first time we've encountered this precedence we need + // to create a StyleQueue. + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [stringToChunk(escapeTextForBrowser(href))], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); } else { - resource.props.hrefs.push(href); + // We have seen this precedence before and need to track this href + styleQueue.hrefs.push(stringToChunk(escapeTextForBrowser(href))); } - resumableState.stylesMap[key] = null; - pushStyleContents(resource.chunks, props); + pushStyleContents(styleQueue.rules, props); } - if (resource) { + if (styleQueue) { // We need to track whether this boundary should wait on this resource or not. // Typically this resource should always exist since we either had it or just created // it. However, it's possible when you resume that the style has already been emitted // and then it wouldn't be recreated in the RenderState and there's no need to track // it again since we should've hoisted it to the shell already. if (renderState.boundaryResources) { - renderState.boundaryResources.add(resource); + renderState.boundaryResources.styles.add(styleQueue); } } @@ -3530,36 +3578,35 @@ function pushStyleContents(target, props) { return; } -function getImagePreloadKey(href, imageSrcSet, imageSizes) { - var uniquePart = ''; - - if (typeof imageSrcSet === 'string' && imageSrcSet !== '') { - uniquePart += '[' + imageSrcSet + ']'; - - if (typeof imageSizes === 'string') { - uniquePart += '[' + imageSizes + ']'; - } - } else { - uniquePart += '[][]' + href; - } - - return getResourceKey('image', uniquePart); -} - function pushImg(target, props, resumableState, renderState, pictureTagInScope) { var src = props.src, srcSet = props.srcSet; - if (props.loading !== 'lazy' && (typeof src === 'string' || typeof srcSet === 'string') && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded + if (props.loading !== 'lazy' && (src || srcSet) && (typeof src === 'string' || src == null) && (typeof srcSet === 'string' || srcSet == null) && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded !(typeof src === 'string' && src[4] === ':' && (src[0] === 'd' || src[0] === 'D') && (src[1] === 'a' || src[1] === 'A') && (src[2] === 't' || src[2] === 'T') && (src[3] === 'a' || src[3] === 'A')) && !(typeof srcSet === 'string' && srcSet[4] === ':' && (srcSet[0] === 'd' || srcSet[0] === 'D') && (srcSet[1] === 'a' || srcSet[1] === 'A') && (srcSet[2] === 't' || srcSet[2] === 'T') && (srcSet[3] === 'a' || srcSet[3] === 'A'))) { // We have a suspensey image and ought to preload it to optimize the loading of display blocking // resumableState. - var sizes = props.sizes; - var key = getImagePreloadKey(src, srcSet, sizes); - var resource; + var sizes = typeof props.sizes === 'string' ? props.sizes : undefined; + var key = getImageResourceKey(src, srcSet, sizes); + var promotablePreloads = renderState.preloads.images; + var resource = promotablePreloads.get(key); + + if (resource) { + // We consider whether this preload can be promoted to higher priority flushing queue. + // The only time a resource will exist here is if it was created during this render + // and was not already in the high priority queue. + if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { + // Delete the resource from the map since we are promoting it and don't want to + // reenter this branch in a second pass for duplicate img hrefs. + promotablePreloads.delete(key); // $FlowFixMe - Flow should understand that this is a Resource if the condition was true - if (!resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = { + renderState.highImagePreloads.add(resource); + } + } else if (!resumableState.imageResources.hasOwnProperty(key)) { + // We must construct a new preload resource + resumableState.imageResources[key] = PRELOAD_NO_CREDS; + resource = []; + pushLinkImpl(resource, { rel: 'preload', as: 'image', // There is a bug in Safari where imageSrcSet is not respected on preload links @@ -3574,25 +3621,15 @@ function pushImg(target, props, resumableState, renderState, pictureTagInScope) type: props.type, fetchPriority: props.fetchPriority, referrerPolicy: props.referrerPolicy - }; - resource = { - type: 'preload', - chunks: [], - state: NoState, - props: preloadProps - }; - resumableState.preloadsMap[key] = preloadProps; - renderState.preloadsMap.set(key, resource); - pushLinkImpl(resource.chunks, preloadProps); - } else { - resource = renderState.preloadsMap.get(key); - } + }); - if (resource) { if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { renderState.highImagePreloads.add(resource); } else { - renderState.bulkPreloads.add(resource); + renderState.bulkPreloads.add(resource); // We can bump the priority up if the same img is rendered later + // with fetchPriority="high" + + promotablePreloads.set(key, resource); } } } @@ -3766,35 +3803,50 @@ function pushScript(target, props, resumableState, renderState, textEmbedded, in } var src = props.src; - var key = getResourceKey('script', src); // We can make this '); @@ -1840,12 +1858,16 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst fontPreloads: new Set(), highImagePreloads: new Set(), // usedImagePreloads: new Set(), - precedences: new Map(), - stylePrecedences: new Map(), + styles: new Map(), bootstrapScripts: new Set(), scripts: new Set(), bulkPreloads: new Set(), - preloadsMap: new Map(), + preloads: { + images: new Map(), + stylesheets: new Map(), + scripts: new Map(), + moduleScripts: new Map() + }, nonce: nonce, // like a module global for currently rendering boundary boundaryResources: null, @@ -1855,17 +1877,32 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst if (bootstrapScripts !== undefined) { for (var i = 0; i < bootstrapScripts.length; i++) { var scriptConfig = bootstrapScripts[i]; - var src = typeof scriptConfig === 'string' ? scriptConfig : scriptConfig.src; - var integrity = typeof scriptConfig === 'string' ? undefined : scriptConfig.integrity; - var crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; - preloadBootstrapScript(resumableState, renderState, src, nonce, integrity, crossOrigin); + var src = void 0, + crossOrigin = void 0, + integrity = void 0; + var props = { + rel: 'preload', + as: 'script', + fetchPriority: 'low', + nonce: nonce + }; + + if (typeof scriptConfig === 'string') { + props.href = src = scriptConfig; + } else { + props.href = src = scriptConfig.src; + props.integrity = integrity = typeof scriptConfig.integrity === 'string' ? scriptConfig.integrity : undefined; + props.crossOrigin = crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } + + preloadBootstrapScriptOrModule(resumableState, renderState, src, props); bootstrapChunks.push(startScriptSrc, stringToChunk(escapeTextForBrowser(src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (integrity) { + if (typeof integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(integrity))); } @@ -1881,20 +1918,32 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst for (var _i = 0; _i < bootstrapModules.length; _i++) { var _scriptConfig = bootstrapModules[_i]; - var _src = typeof _scriptConfig === 'string' ? _scriptConfig : _scriptConfig.src; + var _src = void 0, + _crossOrigin = void 0, + _integrity = void 0; - var _integrity = typeof _scriptConfig === 'string' ? undefined : _scriptConfig.integrity; + var _props = { + rel: 'modulepreload', + fetchPriority: 'low', + nonce: nonce + }; - var _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + if (typeof _scriptConfig === 'string') { + _props.href = _src = _scriptConfig; + } else { + _props.href = _src = _scriptConfig.src; + _props.integrity = _integrity = typeof _scriptConfig.integrity === 'string' ? _scriptConfig.integrity : undefined; + _props.crossOrigin = _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } - preloadBootstrapModule(resumableState, renderState, _src, nonce, _integrity, _crossOrigin); + preloadBootstrapScriptOrModule(resumableState, renderState, _src, _props); bootstrapChunks.push(startModuleSrc, stringToChunk(escapeTextForBrowser(_src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (_integrity) { + if (typeof _integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(_integrity))); } @@ -1931,10 +1980,18 @@ function createResumableState(identifierPrefix, externalRuntimeConfig) { hasHtml: false, // @TODO add bootstrap script to implicit preloads // persistent - preloadsMap: {}, - preconnectsMap: {}, - stylesMap: {}, - scriptsMap: {} + unknownResources: {}, + dnsResources: {}, + connectResources: { + default: {}, + anonymous: {}, + credentials: {} + }, + imageResources: {}, + styleResources: {}, + scriptResources: {}, + moduleUnknownResources: {}, + moduleScriptResources: {} }; } // Constants for the insertion mode we're currently writing in. We don't encode all HTML5 insertion // modes. We only include the variants as they matter for the sake of our purposes. @@ -3292,7 +3349,7 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse if (props.rel === 'stylesheet') { // This may hoistable as a Stylesheet Resource, otherwise it will emit in place - var key = getResourceKey('style', href); + var key = getResourceKey(href); if (typeof precedence !== 'string' || props.disabled != null || props.onLoad || props.onError) { // This stylesheet is either not opted into Resource semantics or has conflicting properties which @@ -3313,67 +3370,58 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse return pushLinkImpl(target, props); } else { // This stylesheet refers to a Resource and we create a new one if necessary - var stylesInPrecedence = renderState.precedences.get(precedence); - - if (!resumableState.stylesMap.hasOwnProperty(key)) { - var resourceProps = stylesheetPropsFromRawProps(props); - var state = NoState; + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; + + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; // If this is the first time we've encountered this precedence we need + // to create a StyleQueue + + if (!styleQueue) { + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); + } - if (resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = resumableState.preloadsMap[key]; - adoptPreloadPropsForStylesheetProps(resourceProps, preloadProps); - var preloadResource = renderState.preloadsMap.get(key); + var resource = { + state: PENDING$1, + props: stylesheetPropsFromRawProps(props) + }; - if (preloadResource) { - // If we already had a preload we don't want that resource to flush directly. - // We let the newly created resource govern flushing. - preloadResource.state |= Blocked; + if (resourceState) { + // When resourceState is truty it is a Preload state. We cast it for clarity + var preloadState = resourceState; - if (preloadResource.state & Flushed) { - state = PreloadFlushed; - } - } else { - // If we resumed then we assume that this was already flushed - // by the shell. - state = PreloadFlushed; + if (preloadState.length === 2) { + adoptPreloadCredentials(resource.props, preloadState); } - } - var resource = { - type: 'stylesheet', - chunks: [], - state: state, - props: resourceProps - }; - resumableState.stylesMap[key] = null; - - if (!stylesInPrecedence) { - stylesInPrecedence = new Map(); - renderState.precedences.set(precedence, stylesInPrecedence); - var emptyStyleResource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [] - } - }; - stylesInPrecedence.set('', emptyStyleResource); + var preloadResource = renderState.preloads.stylesheets.get(key); - { - if (renderState.stylePrecedences.has(precedence)) { - error('React constructed an empty style resource when a style resource already exists for this precedence: "%s". This is a bug in React.', precedence); - } + if (preloadResource && preloadResource.length > 0) { + // The Preload for this resource was created in this render pass and has not flushed yet so + // we need to clear it to avoid it flushing. + preloadResource.length = 0; + } else { + // Either the preload resource from this render already flushed in this render pass + // or the preload flushed in a prior pass (prerender). In either case we need to mark + // this resource as already having been preloaded. + resource.state = PRELOADED; } + } // We add the newly created resource to our StyleQueue and if necessary + // track the resource with the currently rendering boundary - renderState.stylePrecedences.set(precedence, emptyStyleResource); - } - stylesInPrecedence.set(key, resource); + styleQueue.sheets.set(key, resource); if (renderState.boundaryResources) { - renderState.boundaryResources.add(resource); + renderState.boundaryResources.stylesheets.add(resource); } } else { // We need to track whether this boundary should wait on this resource or not. @@ -3381,12 +3429,12 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse // it. However, it's possible when you resume that the style has already been emitted // and then it wouldn't be recreated in the RenderState and there's no need to track // it again since we should've hoisted it to the shell already. - if (stylesInPrecedence) { - var _resource = stylesInPrecedence.get(key); + if (styleQueue) { + var _resource = styleQueue.sheets.get(key); if (_resource) { if (renderState.boundaryResources) { - renderState.boundaryResources.add(_resource); + renderState.boundaryResources.stylesheets.add(_resource); } } } @@ -3484,47 +3532,47 @@ function pushStyle(target, props, resumableState, renderState, textEmbedded, ins } } - var key = getResourceKey('style', href); - var resource = renderState.stylePrecedences.get(precedence); + var key = getResourceKey(href); + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; - if (!resumableState.stylesMap.hasOwnProperty(key)) { - if (!resource) { - resource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [href] - } - }; - renderState.stylePrecedences.set(precedence, resource); - var stylesInPrecedence = new Map(); - stylesInPrecedence.set('', resource); + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; - { - if (renderState.precedences.has(precedence)) { - error('React constructed a new style precedence set when one already exists for this precedence: "%s". This is a bug in React.', precedence); - } + { + if (resourceState) { + error('React encountered a hoistable style tag for the same href as a preload: "%s". When using a style tag to inline styles you should not also preload it as a stylsheet.', href); } + } - renderState.precedences.set(precedence, stylesInPrecedence); + if (!styleQueue) { + // This is the first time we've encountered this precedence we need + // to create a StyleQueue. + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [stringToChunk(escapeTextForBrowser(href))], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); } else { - resource.props.hrefs.push(href); + // We have seen this precedence before and need to track this href + styleQueue.hrefs.push(stringToChunk(escapeTextForBrowser(href))); } - resumableState.stylesMap[key] = null; - pushStyleContents(resource.chunks, props); + pushStyleContents(styleQueue.rules, props); } - if (resource) { + if (styleQueue) { // We need to track whether this boundary should wait on this resource or not. // Typically this resource should always exist since we either had it or just created // it. However, it's possible when you resume that the style has already been emitted // and then it wouldn't be recreated in the RenderState and there's no need to track // it again since we should've hoisted it to the shell already. if (renderState.boundaryResources) { - renderState.boundaryResources.add(resource); + renderState.boundaryResources.styles.add(styleQueue); } } @@ -3613,36 +3661,35 @@ function pushStyleContents(target, props) { return; } -function getImagePreloadKey(href, imageSrcSet, imageSizes) { - var uniquePart = ''; - - if (typeof imageSrcSet === 'string' && imageSrcSet !== '') { - uniquePart += '[' + imageSrcSet + ']'; - - if (typeof imageSizes === 'string') { - uniquePart += '[' + imageSizes + ']'; - } - } else { - uniquePart += '[][]' + href; - } - - return getResourceKey('image', uniquePart); -} - function pushImg(target, props, resumableState, renderState, pictureTagInScope) { var src = props.src, srcSet = props.srcSet; - if (props.loading !== 'lazy' && (typeof src === 'string' || typeof srcSet === 'string') && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded + if (props.loading !== 'lazy' && (src || srcSet) && (typeof src === 'string' || src == null) && (typeof srcSet === 'string' || srcSet == null) && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded !(typeof src === 'string' && src[4] === ':' && (src[0] === 'd' || src[0] === 'D') && (src[1] === 'a' || src[1] === 'A') && (src[2] === 't' || src[2] === 'T') && (src[3] === 'a' || src[3] === 'A')) && !(typeof srcSet === 'string' && srcSet[4] === ':' && (srcSet[0] === 'd' || srcSet[0] === 'D') && (srcSet[1] === 'a' || srcSet[1] === 'A') && (srcSet[2] === 't' || srcSet[2] === 'T') && (srcSet[3] === 'a' || srcSet[3] === 'A'))) { // We have a suspensey image and ought to preload it to optimize the loading of display blocking // resumableState. - var sizes = props.sizes; - var key = getImagePreloadKey(src, srcSet, sizes); - var resource; + var sizes = typeof props.sizes === 'string' ? props.sizes : undefined; + var key = getImageResourceKey(src, srcSet, sizes); + var promotablePreloads = renderState.preloads.images; + var resource = promotablePreloads.get(key); + + if (resource) { + // We consider whether this preload can be promoted to higher priority flushing queue. + // The only time a resource will exist here is if it was created during this render + // and was not already in the high priority queue. + if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { + // Delete the resource from the map since we are promoting it and don't want to + // reenter this branch in a second pass for duplicate img hrefs. + promotablePreloads.delete(key); // $FlowFixMe - Flow should understand that this is a Resource if the condition was true - if (!resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = { + renderState.highImagePreloads.add(resource); + } + } else if (!resumableState.imageResources.hasOwnProperty(key)) { + // We must construct a new preload resource + resumableState.imageResources[key] = PRELOAD_NO_CREDS; + resource = []; + pushLinkImpl(resource, { rel: 'preload', as: 'image', // There is a bug in Safari where imageSrcSet is not respected on preload links @@ -3657,25 +3704,15 @@ function pushImg(target, props, resumableState, renderState, pictureTagInScope) type: props.type, fetchPriority: props.fetchPriority, referrerPolicy: props.referrerPolicy - }; - resource = { - type: 'preload', - chunks: [], - state: NoState, - props: preloadProps - }; - resumableState.preloadsMap[key] = preloadProps; - renderState.preloadsMap.set(key, resource); - pushLinkImpl(resource.chunks, preloadProps); - } else { - resource = renderState.preloadsMap.get(key); - } + }); - if (resource) { if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { renderState.highImagePreloads.add(resource); } else { - renderState.bulkPreloads.add(resource); + renderState.bulkPreloads.add(resource); // We can bump the priority up if the same img is rendered later + // with fetchPriority="high" + + promotablePreloads.set(key, resource); } } } @@ -3849,35 +3886,50 @@ function pushScript(target, props, resumableState, renderState, textEmbedded, in } var src = props.src; - var key = getResourceKey('script', src); // We can make this '); @@ -1840,12 +1858,16 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst fontPreloads: new Set(), highImagePreloads: new Set(), // usedImagePreloads: new Set(), - precedences: new Map(), - stylePrecedences: new Map(), + styles: new Map(), bootstrapScripts: new Set(), scripts: new Set(), bulkPreloads: new Set(), - preloadsMap: new Map(), + preloads: { + images: new Map(), + stylesheets: new Map(), + scripts: new Map(), + moduleScripts: new Map() + }, nonce: nonce, // like a module global for currently rendering boundary boundaryResources: null, @@ -1855,17 +1877,32 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst if (bootstrapScripts !== undefined) { for (var i = 0; i < bootstrapScripts.length; i++) { var scriptConfig = bootstrapScripts[i]; - var src = typeof scriptConfig === 'string' ? scriptConfig : scriptConfig.src; - var integrity = typeof scriptConfig === 'string' ? undefined : scriptConfig.integrity; - var crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; - preloadBootstrapScript(resumableState, renderState, src, nonce, integrity, crossOrigin); + var src = void 0, + crossOrigin = void 0, + integrity = void 0; + var props = { + rel: 'preload', + as: 'script', + fetchPriority: 'low', + nonce: nonce + }; + + if (typeof scriptConfig === 'string') { + props.href = src = scriptConfig; + } else { + props.href = src = scriptConfig.src; + props.integrity = integrity = typeof scriptConfig.integrity === 'string' ? scriptConfig.integrity : undefined; + props.crossOrigin = crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } + + preloadBootstrapScriptOrModule(resumableState, renderState, src, props); bootstrapChunks.push(startScriptSrc, stringToChunk(escapeTextForBrowser(src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (integrity) { + if (typeof integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(integrity))); } @@ -1881,20 +1918,32 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst for (var _i = 0; _i < bootstrapModules.length; _i++) { var _scriptConfig = bootstrapModules[_i]; - var _src = typeof _scriptConfig === 'string' ? _scriptConfig : _scriptConfig.src; + var _src = void 0, + _crossOrigin = void 0, + _integrity = void 0; - var _integrity = typeof _scriptConfig === 'string' ? undefined : _scriptConfig.integrity; + var _props = { + rel: 'modulepreload', + fetchPriority: 'low', + nonce: nonce + }; - var _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + if (typeof _scriptConfig === 'string') { + _props.href = _src = _scriptConfig; + } else { + _props.href = _src = _scriptConfig.src; + _props.integrity = _integrity = typeof _scriptConfig.integrity === 'string' ? _scriptConfig.integrity : undefined; + _props.crossOrigin = _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } - preloadBootstrapModule(resumableState, renderState, _src, nonce, _integrity, _crossOrigin); + preloadBootstrapScriptOrModule(resumableState, renderState, _src, _props); bootstrapChunks.push(startModuleSrc, stringToChunk(escapeTextForBrowser(_src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (_integrity) { + if (typeof _integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(_integrity))); } @@ -1931,10 +1980,18 @@ function createResumableState(identifierPrefix, externalRuntimeConfig) { hasHtml: false, // @TODO add bootstrap script to implicit preloads // persistent - preloadsMap: {}, - preconnectsMap: {}, - stylesMap: {}, - scriptsMap: {} + unknownResources: {}, + dnsResources: {}, + connectResources: { + default: {}, + anonymous: {}, + credentials: {} + }, + imageResources: {}, + styleResources: {}, + scriptResources: {}, + moduleUnknownResources: {}, + moduleScriptResources: {} }; } // Constants for the insertion mode we're currently writing in. We don't encode all HTML5 insertion // modes. We only include the variants as they matter for the sake of our purposes. @@ -3292,7 +3349,7 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse if (props.rel === 'stylesheet') { // This may hoistable as a Stylesheet Resource, otherwise it will emit in place - var key = getResourceKey('style', href); + var key = getResourceKey(href); if (typeof precedence !== 'string' || props.disabled != null || props.onLoad || props.onError) { // This stylesheet is either not opted into Resource semantics or has conflicting properties which @@ -3313,67 +3370,58 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse return pushLinkImpl(target, props); } else { // This stylesheet refers to a Resource and we create a new one if necessary - var stylesInPrecedence = renderState.precedences.get(precedence); - - if (!resumableState.stylesMap.hasOwnProperty(key)) { - var resourceProps = stylesheetPropsFromRawProps(props); - var state = NoState; + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; + + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; // If this is the first time we've encountered this precedence we need + // to create a StyleQueue + + if (!styleQueue) { + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); + } - if (resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = resumableState.preloadsMap[key]; - adoptPreloadPropsForStylesheetProps(resourceProps, preloadProps); - var preloadResource = renderState.preloadsMap.get(key); + var resource = { + state: PENDING$1, + props: stylesheetPropsFromRawProps(props) + }; - if (preloadResource) { - // If we already had a preload we don't want that resource to flush directly. - // We let the newly created resource govern flushing. - preloadResource.state |= Blocked; + if (resourceState) { + // When resourceState is truty it is a Preload state. We cast it for clarity + var preloadState = resourceState; - if (preloadResource.state & Flushed) { - state = PreloadFlushed; - } - } else { - // If we resumed then we assume that this was already flushed - // by the shell. - state = PreloadFlushed; + if (preloadState.length === 2) { + adoptPreloadCredentials(resource.props, preloadState); } - } - var resource = { - type: 'stylesheet', - chunks: [], - state: state, - props: resourceProps - }; - resumableState.stylesMap[key] = null; - - if (!stylesInPrecedence) { - stylesInPrecedence = new Map(); - renderState.precedences.set(precedence, stylesInPrecedence); - var emptyStyleResource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [] - } - }; - stylesInPrecedence.set('', emptyStyleResource); + var preloadResource = renderState.preloads.stylesheets.get(key); - { - if (renderState.stylePrecedences.has(precedence)) { - error('React constructed an empty style resource when a style resource already exists for this precedence: "%s". This is a bug in React.', precedence); - } + if (preloadResource && preloadResource.length > 0) { + // The Preload for this resource was created in this render pass and has not flushed yet so + // we need to clear it to avoid it flushing. + preloadResource.length = 0; + } else { + // Either the preload resource from this render already flushed in this render pass + // or the preload flushed in a prior pass (prerender). In either case we need to mark + // this resource as already having been preloaded. + resource.state = PRELOADED; } + } // We add the newly created resource to our StyleQueue and if necessary + // track the resource with the currently rendering boundary - renderState.stylePrecedences.set(precedence, emptyStyleResource); - } - stylesInPrecedence.set(key, resource); + styleQueue.sheets.set(key, resource); if (renderState.boundaryResources) { - renderState.boundaryResources.add(resource); + renderState.boundaryResources.stylesheets.add(resource); } } else { // We need to track whether this boundary should wait on this resource or not. @@ -3381,12 +3429,12 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse // it. However, it's possible when you resume that the style has already been emitted // and then it wouldn't be recreated in the RenderState and there's no need to track // it again since we should've hoisted it to the shell already. - if (stylesInPrecedence) { - var _resource = stylesInPrecedence.get(key); + if (styleQueue) { + var _resource = styleQueue.sheets.get(key); if (_resource) { if (renderState.boundaryResources) { - renderState.boundaryResources.add(_resource); + renderState.boundaryResources.stylesheets.add(_resource); } } } @@ -3484,47 +3532,47 @@ function pushStyle(target, props, resumableState, renderState, textEmbedded, ins } } - var key = getResourceKey('style', href); - var resource = renderState.stylePrecedences.get(precedence); + var key = getResourceKey(href); + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; - if (!resumableState.stylesMap.hasOwnProperty(key)) { - if (!resource) { - resource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [href] - } - }; - renderState.stylePrecedences.set(precedence, resource); - var stylesInPrecedence = new Map(); - stylesInPrecedence.set('', resource); + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; - { - if (renderState.precedences.has(precedence)) { - error('React constructed a new style precedence set when one already exists for this precedence: "%s". This is a bug in React.', precedence); - } + { + if (resourceState) { + error('React encountered a hoistable style tag for the same href as a preload: "%s". When using a style tag to inline styles you should not also preload it as a stylsheet.', href); } + } - renderState.precedences.set(precedence, stylesInPrecedence); + if (!styleQueue) { + // This is the first time we've encountered this precedence we need + // to create a StyleQueue. + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [stringToChunk(escapeTextForBrowser(href))], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); } else { - resource.props.hrefs.push(href); + // We have seen this precedence before and need to track this href + styleQueue.hrefs.push(stringToChunk(escapeTextForBrowser(href))); } - resumableState.stylesMap[key] = null; - pushStyleContents(resource.chunks, props); + pushStyleContents(styleQueue.rules, props); } - if (resource) { + if (styleQueue) { // We need to track whether this boundary should wait on this resource or not. // Typically this resource should always exist since we either had it or just created // it. However, it's possible when you resume that the style has already been emitted // and then it wouldn't be recreated in the RenderState and there's no need to track // it again since we should've hoisted it to the shell already. if (renderState.boundaryResources) { - renderState.boundaryResources.add(resource); + renderState.boundaryResources.styles.add(styleQueue); } } @@ -3613,36 +3661,35 @@ function pushStyleContents(target, props) { return; } -function getImagePreloadKey(href, imageSrcSet, imageSizes) { - var uniquePart = ''; - - if (typeof imageSrcSet === 'string' && imageSrcSet !== '') { - uniquePart += '[' + imageSrcSet + ']'; - - if (typeof imageSizes === 'string') { - uniquePart += '[' + imageSizes + ']'; - } - } else { - uniquePart += '[][]' + href; - } - - return getResourceKey('image', uniquePart); -} - function pushImg(target, props, resumableState, renderState, pictureTagInScope) { var src = props.src, srcSet = props.srcSet; - if (props.loading !== 'lazy' && (typeof src === 'string' || typeof srcSet === 'string') && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded + if (props.loading !== 'lazy' && (src || srcSet) && (typeof src === 'string' || src == null) && (typeof srcSet === 'string' || srcSet == null) && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded !(typeof src === 'string' && src[4] === ':' && (src[0] === 'd' || src[0] === 'D') && (src[1] === 'a' || src[1] === 'A') && (src[2] === 't' || src[2] === 'T') && (src[3] === 'a' || src[3] === 'A')) && !(typeof srcSet === 'string' && srcSet[4] === ':' && (srcSet[0] === 'd' || srcSet[0] === 'D') && (srcSet[1] === 'a' || srcSet[1] === 'A') && (srcSet[2] === 't' || srcSet[2] === 'T') && (srcSet[3] === 'a' || srcSet[3] === 'A'))) { // We have a suspensey image and ought to preload it to optimize the loading of display blocking // resumableState. - var sizes = props.sizes; - var key = getImagePreloadKey(src, srcSet, sizes); - var resource; + var sizes = typeof props.sizes === 'string' ? props.sizes : undefined; + var key = getImageResourceKey(src, srcSet, sizes); + var promotablePreloads = renderState.preloads.images; + var resource = promotablePreloads.get(key); + + if (resource) { + // We consider whether this preload can be promoted to higher priority flushing queue. + // The only time a resource will exist here is if it was created during this render + // and was not already in the high priority queue. + if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { + // Delete the resource from the map since we are promoting it and don't want to + // reenter this branch in a second pass for duplicate img hrefs. + promotablePreloads.delete(key); // $FlowFixMe - Flow should understand that this is a Resource if the condition was true - if (!resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = { + renderState.highImagePreloads.add(resource); + } + } else if (!resumableState.imageResources.hasOwnProperty(key)) { + // We must construct a new preload resource + resumableState.imageResources[key] = PRELOAD_NO_CREDS; + resource = []; + pushLinkImpl(resource, { rel: 'preload', as: 'image', // There is a bug in Safari where imageSrcSet is not respected on preload links @@ -3657,25 +3704,15 @@ function pushImg(target, props, resumableState, renderState, pictureTagInScope) type: props.type, fetchPriority: props.fetchPriority, referrerPolicy: props.referrerPolicy - }; - resource = { - type: 'preload', - chunks: [], - state: NoState, - props: preloadProps - }; - resumableState.preloadsMap[key] = preloadProps; - renderState.preloadsMap.set(key, resource); - pushLinkImpl(resource.chunks, preloadProps); - } else { - resource = renderState.preloadsMap.get(key); - } + }); - if (resource) { if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { renderState.highImagePreloads.add(resource); } else { - renderState.bulkPreloads.add(resource); + renderState.bulkPreloads.add(resource); // We can bump the priority up if the same img is rendered later + // with fetchPriority="high" + + promotablePreloads.set(key, resource); } } } @@ -3849,35 +3886,50 @@ function pushScript(target, props, resumableState, renderState, textEmbedded, in } var src = props.src; - var key = getResourceKey('script', src); // We can make this '); @@ -1823,12 +1841,16 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst fontPreloads: new Set(), highImagePreloads: new Set(), // usedImagePreloads: new Set(), - precedences: new Map(), - stylePrecedences: new Map(), + styles: new Map(), bootstrapScripts: new Set(), scripts: new Set(), bulkPreloads: new Set(), - preloadsMap: new Map(), + preloads: { + images: new Map(), + stylesheets: new Map(), + scripts: new Map(), + moduleScripts: new Map() + }, nonce: nonce, // like a module global for currently rendering boundary boundaryResources: null, @@ -1838,17 +1860,32 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst if (bootstrapScripts !== undefined) { for (var i = 0; i < bootstrapScripts.length; i++) { var scriptConfig = bootstrapScripts[i]; - var src = typeof scriptConfig === 'string' ? scriptConfig : scriptConfig.src; - var integrity = typeof scriptConfig === 'string' ? undefined : scriptConfig.integrity; - var crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; - preloadBootstrapScript(resumableState, renderState, src, nonce, integrity, crossOrigin); + var src = void 0, + crossOrigin = void 0, + integrity = void 0; + var props = { + rel: 'preload', + as: 'script', + fetchPriority: 'low', + nonce: nonce + }; + + if (typeof scriptConfig === 'string') { + props.href = src = scriptConfig; + } else { + props.href = src = scriptConfig.src; + props.integrity = integrity = typeof scriptConfig.integrity === 'string' ? scriptConfig.integrity : undefined; + props.crossOrigin = crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } + + preloadBootstrapScriptOrModule(resumableState, renderState, src, props); bootstrapChunks.push(startScriptSrc, stringToChunk(escapeTextForBrowser(src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (integrity) { + if (typeof integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(integrity))); } @@ -1864,20 +1901,32 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst for (var _i = 0; _i < bootstrapModules.length; _i++) { var _scriptConfig = bootstrapModules[_i]; - var _src = typeof _scriptConfig === 'string' ? _scriptConfig : _scriptConfig.src; + var _src = void 0, + _crossOrigin = void 0, + _integrity = void 0; - var _integrity = typeof _scriptConfig === 'string' ? undefined : _scriptConfig.integrity; + var _props = { + rel: 'modulepreload', + fetchPriority: 'low', + nonce: nonce + }; - var _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + if (typeof _scriptConfig === 'string') { + _props.href = _src = _scriptConfig; + } else { + _props.href = _src = _scriptConfig.src; + _props.integrity = _integrity = typeof _scriptConfig.integrity === 'string' ? _scriptConfig.integrity : undefined; + _props.crossOrigin = _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } - preloadBootstrapModule(resumableState, renderState, _src, nonce, _integrity, _crossOrigin); + preloadBootstrapScriptOrModule(resumableState, renderState, _src, _props); bootstrapChunks.push(startModuleSrc, stringToChunk(escapeTextForBrowser(_src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (_integrity) { + if (typeof _integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(_integrity))); } @@ -1914,10 +1963,18 @@ function createResumableState(identifierPrefix, externalRuntimeConfig) { hasHtml: false, // @TODO add bootstrap script to implicit preloads // persistent - preloadsMap: {}, - preconnectsMap: {}, - stylesMap: {}, - scriptsMap: {} + unknownResources: {}, + dnsResources: {}, + connectResources: { + default: {}, + anonymous: {}, + credentials: {} + }, + imageResources: {}, + styleResources: {}, + scriptResources: {}, + moduleUnknownResources: {}, + moduleScriptResources: {} }; } // Constants for the insertion mode we're currently writing in. We don't encode all HTML5 insertion // modes. We only include the variants as they matter for the sake of our purposes. @@ -3275,7 +3332,7 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse if (props.rel === 'stylesheet') { // This may hoistable as a Stylesheet Resource, otherwise it will emit in place - var key = getResourceKey('style', href); + var key = getResourceKey(href); if (typeof precedence !== 'string' || props.disabled != null || props.onLoad || props.onError) { // This stylesheet is either not opted into Resource semantics or has conflicting properties which @@ -3296,67 +3353,58 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse return pushLinkImpl(target, props); } else { // This stylesheet refers to a Resource and we create a new one if necessary - var stylesInPrecedence = renderState.precedences.get(precedence); - - if (!resumableState.stylesMap.hasOwnProperty(key)) { - var resourceProps = stylesheetPropsFromRawProps(props); - var state = NoState; + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; + + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; // If this is the first time we've encountered this precedence we need + // to create a StyleQueue + + if (!styleQueue) { + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); + } - if (resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = resumableState.preloadsMap[key]; - adoptPreloadPropsForStylesheetProps(resourceProps, preloadProps); - var preloadResource = renderState.preloadsMap.get(key); + var resource = { + state: PENDING$1, + props: stylesheetPropsFromRawProps(props) + }; - if (preloadResource) { - // If we already had a preload we don't want that resource to flush directly. - // We let the newly created resource govern flushing. - preloadResource.state |= Blocked; + if (resourceState) { + // When resourceState is truty it is a Preload state. We cast it for clarity + var preloadState = resourceState; - if (preloadResource.state & Flushed) { - state = PreloadFlushed; - } - } else { - // If we resumed then we assume that this was already flushed - // by the shell. - state = PreloadFlushed; + if (preloadState.length === 2) { + adoptPreloadCredentials(resource.props, preloadState); } - } - var resource = { - type: 'stylesheet', - chunks: [], - state: state, - props: resourceProps - }; - resumableState.stylesMap[key] = null; - - if (!stylesInPrecedence) { - stylesInPrecedence = new Map(); - renderState.precedences.set(precedence, stylesInPrecedence); - var emptyStyleResource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [] - } - }; - stylesInPrecedence.set('', emptyStyleResource); + var preloadResource = renderState.preloads.stylesheets.get(key); - { - if (renderState.stylePrecedences.has(precedence)) { - error('React constructed an empty style resource when a style resource already exists for this precedence: "%s". This is a bug in React.', precedence); - } + if (preloadResource && preloadResource.length > 0) { + // The Preload for this resource was created in this render pass and has not flushed yet so + // we need to clear it to avoid it flushing. + preloadResource.length = 0; + } else { + // Either the preload resource from this render already flushed in this render pass + // or the preload flushed in a prior pass (prerender). In either case we need to mark + // this resource as already having been preloaded. + resource.state = PRELOADED; } + } // We add the newly created resource to our StyleQueue and if necessary + // track the resource with the currently rendering boundary - renderState.stylePrecedences.set(precedence, emptyStyleResource); - } - stylesInPrecedence.set(key, resource); + styleQueue.sheets.set(key, resource); if (renderState.boundaryResources) { - renderState.boundaryResources.add(resource); + renderState.boundaryResources.stylesheets.add(resource); } } else { // We need to track whether this boundary should wait on this resource or not. @@ -3364,12 +3412,12 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse // it. However, it's possible when you resume that the style has already been emitted // and then it wouldn't be recreated in the RenderState and there's no need to track // it again since we should've hoisted it to the shell already. - if (stylesInPrecedence) { - var _resource = stylesInPrecedence.get(key); + if (styleQueue) { + var _resource = styleQueue.sheets.get(key); if (_resource) { if (renderState.boundaryResources) { - renderState.boundaryResources.add(_resource); + renderState.boundaryResources.stylesheets.add(_resource); } } } @@ -3467,47 +3515,47 @@ function pushStyle(target, props, resumableState, renderState, textEmbedded, ins } } - var key = getResourceKey('style', href); - var resource = renderState.stylePrecedences.get(precedence); + var key = getResourceKey(href); + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; - if (!resumableState.stylesMap.hasOwnProperty(key)) { - if (!resource) { - resource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [href] - } - }; - renderState.stylePrecedences.set(precedence, resource); - var stylesInPrecedence = new Map(); - stylesInPrecedence.set('', resource); + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; - { - if (renderState.precedences.has(precedence)) { - error('React constructed a new style precedence set when one already exists for this precedence: "%s". This is a bug in React.', precedence); - } + { + if (resourceState) { + error('React encountered a hoistable style tag for the same href as a preload: "%s". When using a style tag to inline styles you should not also preload it as a stylsheet.', href); } + } - renderState.precedences.set(precedence, stylesInPrecedence); + if (!styleQueue) { + // This is the first time we've encountered this precedence we need + // to create a StyleQueue. + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [stringToChunk(escapeTextForBrowser(href))], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); } else { - resource.props.hrefs.push(href); + // We have seen this precedence before and need to track this href + styleQueue.hrefs.push(stringToChunk(escapeTextForBrowser(href))); } - resumableState.stylesMap[key] = null; - pushStyleContents(resource.chunks, props); + pushStyleContents(styleQueue.rules, props); } - if (resource) { + if (styleQueue) { // We need to track whether this boundary should wait on this resource or not. // Typically this resource should always exist since we either had it or just created // it. However, it's possible when you resume that the style has already been emitted // and then it wouldn't be recreated in the RenderState and there's no need to track // it again since we should've hoisted it to the shell already. if (renderState.boundaryResources) { - renderState.boundaryResources.add(resource); + renderState.boundaryResources.styles.add(styleQueue); } } @@ -3596,36 +3644,35 @@ function pushStyleContents(target, props) { return; } -function getImagePreloadKey(href, imageSrcSet, imageSizes) { - var uniquePart = ''; - - if (typeof imageSrcSet === 'string' && imageSrcSet !== '') { - uniquePart += '[' + imageSrcSet + ']'; - - if (typeof imageSizes === 'string') { - uniquePart += '[' + imageSizes + ']'; - } - } else { - uniquePart += '[][]' + href; - } - - return getResourceKey('image', uniquePart); -} - function pushImg(target, props, resumableState, renderState, pictureTagInScope) { var src = props.src, srcSet = props.srcSet; - if (props.loading !== 'lazy' && (typeof src === 'string' || typeof srcSet === 'string') && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded + if (props.loading !== 'lazy' && (src || srcSet) && (typeof src === 'string' || src == null) && (typeof srcSet === 'string' || srcSet == null) && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded !(typeof src === 'string' && src[4] === ':' && (src[0] === 'd' || src[0] === 'D') && (src[1] === 'a' || src[1] === 'A') && (src[2] === 't' || src[2] === 'T') && (src[3] === 'a' || src[3] === 'A')) && !(typeof srcSet === 'string' && srcSet[4] === ':' && (srcSet[0] === 'd' || srcSet[0] === 'D') && (srcSet[1] === 'a' || srcSet[1] === 'A') && (srcSet[2] === 't' || srcSet[2] === 'T') && (srcSet[3] === 'a' || srcSet[3] === 'A'))) { // We have a suspensey image and ought to preload it to optimize the loading of display blocking // resumableState. - var sizes = props.sizes; - var key = getImagePreloadKey(src, srcSet, sizes); - var resource; + var sizes = typeof props.sizes === 'string' ? props.sizes : undefined; + var key = getImageResourceKey(src, srcSet, sizes); + var promotablePreloads = renderState.preloads.images; + var resource = promotablePreloads.get(key); + + if (resource) { + // We consider whether this preload can be promoted to higher priority flushing queue. + // The only time a resource will exist here is if it was created during this render + // and was not already in the high priority queue. + if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { + // Delete the resource from the map since we are promoting it and don't want to + // reenter this branch in a second pass for duplicate img hrefs. + promotablePreloads.delete(key); // $FlowFixMe - Flow should understand that this is a Resource if the condition was true - if (!resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = { + renderState.highImagePreloads.add(resource); + } + } else if (!resumableState.imageResources.hasOwnProperty(key)) { + // We must construct a new preload resource + resumableState.imageResources[key] = PRELOAD_NO_CREDS; + resource = []; + pushLinkImpl(resource, { rel: 'preload', as: 'image', // There is a bug in Safari where imageSrcSet is not respected on preload links @@ -3640,25 +3687,15 @@ function pushImg(target, props, resumableState, renderState, pictureTagInScope) type: props.type, fetchPriority: props.fetchPriority, referrerPolicy: props.referrerPolicy - }; - resource = { - type: 'preload', - chunks: [], - state: NoState, - props: preloadProps - }; - resumableState.preloadsMap[key] = preloadProps; - renderState.preloadsMap.set(key, resource); - pushLinkImpl(resource.chunks, preloadProps); - } else { - resource = renderState.preloadsMap.get(key); - } + }); - if (resource) { if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { renderState.highImagePreloads.add(resource); } else { - renderState.bulkPreloads.add(resource); + renderState.bulkPreloads.add(resource); // We can bump the priority up if the same img is rendered later + // with fetchPriority="high" + + promotablePreloads.set(key, resource); } } } @@ -3832,35 +3869,50 @@ function pushScript(target, props, resumableState, renderState, textEmbedded, in } var src = props.src; - var key = getResourceKey('script', src); // We can make this '); @@ -1630,12 +1648,16 @@ function createRenderState$1(resumableState, nonce, bootstrapScriptContent, boot fontPreloads: new Set(), highImagePreloads: new Set(), // usedImagePreloads: new Set(), - precedences: new Map(), - stylePrecedences: new Map(), + styles: new Map(), bootstrapScripts: new Set(), scripts: new Set(), bulkPreloads: new Set(), - preloadsMap: new Map(), + preloads: { + images: new Map(), + stylesheets: new Map(), + scripts: new Map(), + moduleScripts: new Map() + }, nonce: nonce, // like a module global for currently rendering boundary boundaryResources: null, @@ -1645,17 +1667,32 @@ function createRenderState$1(resumableState, nonce, bootstrapScriptContent, boot if (bootstrapScripts !== undefined) { for (var i = 0; i < bootstrapScripts.length; i++) { var scriptConfig = bootstrapScripts[i]; - var src = typeof scriptConfig === 'string' ? scriptConfig : scriptConfig.src; - var integrity = typeof scriptConfig === 'string' ? undefined : scriptConfig.integrity; - var crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; - preloadBootstrapScript(resumableState, renderState, src, nonce, integrity, crossOrigin); + var src = void 0, + crossOrigin = void 0, + integrity = void 0; + var props = { + rel: 'preload', + as: 'script', + fetchPriority: 'low', + nonce: nonce + }; + + if (typeof scriptConfig === 'string') { + props.href = src = scriptConfig; + } else { + props.href = src = scriptConfig.src; + props.integrity = integrity = typeof scriptConfig.integrity === 'string' ? scriptConfig.integrity : undefined; + props.crossOrigin = crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } + + preloadBootstrapScriptOrModule(resumableState, renderState, src, props); bootstrapChunks.push(startScriptSrc, stringToChunk(escapeTextForBrowser(src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (integrity) { + if (typeof integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(integrity))); } @@ -1671,20 +1708,32 @@ function createRenderState$1(resumableState, nonce, bootstrapScriptContent, boot for (var _i = 0; _i < bootstrapModules.length; _i++) { var _scriptConfig = bootstrapModules[_i]; - var _src = typeof _scriptConfig === 'string' ? _scriptConfig : _scriptConfig.src; + var _src = void 0, + _crossOrigin = void 0, + _integrity = void 0; - var _integrity = typeof _scriptConfig === 'string' ? undefined : _scriptConfig.integrity; + var _props = { + rel: 'modulepreload', + fetchPriority: 'low', + nonce: nonce + }; - var _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + if (typeof _scriptConfig === 'string') { + _props.href = _src = _scriptConfig; + } else { + _props.href = _src = _scriptConfig.src; + _props.integrity = _integrity = typeof _scriptConfig.integrity === 'string' ? _scriptConfig.integrity : undefined; + _props.crossOrigin = _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } - preloadBootstrapModule(resumableState, renderState, _src, nonce, _integrity, _crossOrigin); + preloadBootstrapScriptOrModule(resumableState, renderState, _src, _props); bootstrapChunks.push(startModuleSrc, stringToChunk(escapeTextForBrowser(_src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (_integrity) { + if (typeof _integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(_integrity))); } @@ -1717,10 +1766,18 @@ function createResumableState(identifierPrefix, externalRuntimeConfig) { hasHtml: false, // @TODO add bootstrap script to implicit preloads // persistent - preloadsMap: {}, - preconnectsMap: {}, - stylesMap: {}, - scriptsMap: {} + unknownResources: {}, + dnsResources: {}, + connectResources: { + default: {}, + anonymous: {}, + credentials: {} + }, + imageResources: {}, + styleResources: {}, + scriptResources: {}, + moduleUnknownResources: {}, + moduleScriptResources: {} }; } // Constants for the insertion mode we're currently writing in. We don't encode all HTML5 insertion // modes. We only include the variants as they matter for the sake of our purposes. @@ -2938,7 +2995,7 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse if (props.rel === 'stylesheet') { // This may hoistable as a Stylesheet Resource, otherwise it will emit in place - var key = getResourceKey('style', href); + var key = getResourceKey(href); if (typeof precedence !== 'string' || props.disabled != null || props.onLoad || props.onError) { // This stylesheet is either not opted into Resource semantics or has conflicting properties which @@ -2959,67 +3016,58 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse return pushLinkImpl(target, props); } else { // This stylesheet refers to a Resource and we create a new one if necessary - var stylesInPrecedence = renderState.precedences.get(precedence); - - if (!resumableState.stylesMap.hasOwnProperty(key)) { - var resourceProps = stylesheetPropsFromRawProps(props); - var state = NoState; + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; + + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; // If this is the first time we've encountered this precedence we need + // to create a StyleQueue + + if (!styleQueue) { + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); + } - if (resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = resumableState.preloadsMap[key]; - adoptPreloadPropsForStylesheetProps(resourceProps, preloadProps); - var preloadResource = renderState.preloadsMap.get(key); + var resource = { + state: PENDING$1, + props: stylesheetPropsFromRawProps(props) + }; - if (preloadResource) { - // If we already had a preload we don't want that resource to flush directly. - // We let the newly created resource govern flushing. - preloadResource.state |= Blocked; + if (resourceState) { + // When resourceState is truty it is a Preload state. We cast it for clarity + var preloadState = resourceState; - if (preloadResource.state & Flushed) { - state = PreloadFlushed; - } - } else { - // If we resumed then we assume that this was already flushed - // by the shell. - state = PreloadFlushed; + if (preloadState.length === 2) { + adoptPreloadCredentials(resource.props, preloadState); } - } - var resource = { - type: 'stylesheet', - chunks: [], - state: state, - props: resourceProps - }; - resumableState.stylesMap[key] = null; - - if (!stylesInPrecedence) { - stylesInPrecedence = new Map(); - renderState.precedences.set(precedence, stylesInPrecedence); - var emptyStyleResource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [] - } - }; - stylesInPrecedence.set('', emptyStyleResource); + var preloadResource = renderState.preloads.stylesheets.get(key); - { - if (renderState.stylePrecedences.has(precedence)) { - error('React constructed an empty style resource when a style resource already exists for this precedence: "%s". This is a bug in React.', precedence); - } + if (preloadResource && preloadResource.length > 0) { + // The Preload for this resource was created in this render pass and has not flushed yet so + // we need to clear it to avoid it flushing. + preloadResource.length = 0; + } else { + // Either the preload resource from this render already flushed in this render pass + // or the preload flushed in a prior pass (prerender). In either case we need to mark + // this resource as already having been preloaded. + resource.state = PRELOADED; } + } // We add the newly created resource to our StyleQueue and if necessary + // track the resource with the currently rendering boundary - renderState.stylePrecedences.set(precedence, emptyStyleResource); - } - stylesInPrecedence.set(key, resource); + styleQueue.sheets.set(key, resource); if (renderState.boundaryResources) { - renderState.boundaryResources.add(resource); + renderState.boundaryResources.stylesheets.add(resource); } } else { // We need to track whether this boundary should wait on this resource or not. @@ -3027,12 +3075,12 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse // it. However, it's possible when you resume that the style has already been emitted // and then it wouldn't be recreated in the RenderState and there's no need to track // it again since we should've hoisted it to the shell already. - if (stylesInPrecedence) { - var _resource = stylesInPrecedence.get(key); + if (styleQueue) { + var _resource = styleQueue.sheets.get(key); if (_resource) { if (renderState.boundaryResources) { - renderState.boundaryResources.add(_resource); + renderState.boundaryResources.stylesheets.add(_resource); } } } @@ -3130,47 +3178,47 @@ function pushStyle(target, props, resumableState, renderState, textEmbedded, ins } } - var key = getResourceKey('style', href); - var resource = renderState.stylePrecedences.get(precedence); + var key = getResourceKey(href); + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; - if (!resumableState.stylesMap.hasOwnProperty(key)) { - if (!resource) { - resource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [href] - } - }; - renderState.stylePrecedences.set(precedence, resource); - var stylesInPrecedence = new Map(); - stylesInPrecedence.set('', resource); + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; - { - if (renderState.precedences.has(precedence)) { - error('React constructed a new style precedence set when one already exists for this precedence: "%s". This is a bug in React.', precedence); - } + { + if (resourceState) { + error('React encountered a hoistable style tag for the same href as a preload: "%s". When using a style tag to inline styles you should not also preload it as a stylsheet.', href); } + } - renderState.precedences.set(precedence, stylesInPrecedence); + if (!styleQueue) { + // This is the first time we've encountered this precedence we need + // to create a StyleQueue. + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [stringToChunk(escapeTextForBrowser(href))], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); } else { - resource.props.hrefs.push(href); + // We have seen this precedence before and need to track this href + styleQueue.hrefs.push(stringToChunk(escapeTextForBrowser(href))); } - resumableState.stylesMap[key] = null; - pushStyleContents(resource.chunks, props); + pushStyleContents(styleQueue.rules, props); } - if (resource) { + if (styleQueue) { // We need to track whether this boundary should wait on this resource or not. // Typically this resource should always exist since we either had it or just created // it. However, it's possible when you resume that the style has already been emitted // and then it wouldn't be recreated in the RenderState and there's no need to track // it again since we should've hoisted it to the shell already. if (renderState.boundaryResources) { - renderState.boundaryResources.add(resource); + renderState.boundaryResources.styles.add(styleQueue); } } @@ -3259,36 +3307,35 @@ function pushStyleContents(target, props) { return; } -function getImagePreloadKey(href, imageSrcSet, imageSizes) { - var uniquePart = ''; - - if (typeof imageSrcSet === 'string' && imageSrcSet !== '') { - uniquePart += '[' + imageSrcSet + ']'; - - if (typeof imageSizes === 'string') { - uniquePart += '[' + imageSizes + ']'; - } - } else { - uniquePart += '[][]' + href; - } - - return getResourceKey('image', uniquePart); -} - function pushImg(target, props, resumableState, renderState, pictureTagInScope) { var src = props.src, srcSet = props.srcSet; - if (props.loading !== 'lazy' && (typeof src === 'string' || typeof srcSet === 'string') && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded + if (props.loading !== 'lazy' && (src || srcSet) && (typeof src === 'string' || src == null) && (typeof srcSet === 'string' || srcSet == null) && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded !(typeof src === 'string' && src[4] === ':' && (src[0] === 'd' || src[0] === 'D') && (src[1] === 'a' || src[1] === 'A') && (src[2] === 't' || src[2] === 'T') && (src[3] === 'a' || src[3] === 'A')) && !(typeof srcSet === 'string' && srcSet[4] === ':' && (srcSet[0] === 'd' || srcSet[0] === 'D') && (srcSet[1] === 'a' || srcSet[1] === 'A') && (srcSet[2] === 't' || srcSet[2] === 'T') && (srcSet[3] === 'a' || srcSet[3] === 'A'))) { // We have a suspensey image and ought to preload it to optimize the loading of display blocking // resumableState. - var sizes = props.sizes; - var key = getImagePreloadKey(src, srcSet, sizes); - var resource; + var sizes = typeof props.sizes === 'string' ? props.sizes : undefined; + var key = getImageResourceKey(src, srcSet, sizes); + var promotablePreloads = renderState.preloads.images; + var resource = promotablePreloads.get(key); - if (!resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = { + if (resource) { + // We consider whether this preload can be promoted to higher priority flushing queue. + // The only time a resource will exist here is if it was created during this render + // and was not already in the high priority queue. + if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { + // Delete the resource from the map since we are promoting it and don't want to + // reenter this branch in a second pass for duplicate img hrefs. + promotablePreloads.delete(key); // $FlowFixMe - Flow should understand that this is a Resource if the condition was true + + renderState.highImagePreloads.add(resource); + } + } else if (!resumableState.imageResources.hasOwnProperty(key)) { + // We must construct a new preload resource + resumableState.imageResources[key] = PRELOAD_NO_CREDS; + resource = []; + pushLinkImpl(resource, { rel: 'preload', as: 'image', // There is a bug in Safari where imageSrcSet is not respected on preload links @@ -3303,25 +3350,15 @@ function pushImg(target, props, resumableState, renderState, pictureTagInScope) type: props.type, fetchPriority: props.fetchPriority, referrerPolicy: props.referrerPolicy - }; - resource = { - type: 'preload', - chunks: [], - state: NoState, - props: preloadProps - }; - resumableState.preloadsMap[key] = preloadProps; - renderState.preloadsMap.set(key, resource); - pushLinkImpl(resource.chunks, preloadProps); - } else { - resource = renderState.preloadsMap.get(key); - } + }); - if (resource) { if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { renderState.highImagePreloads.add(resource); } else { - renderState.bulkPreloads.add(resource); + renderState.bulkPreloads.add(resource); // We can bump the priority up if the same img is rendered later + // with fetchPriority="high" + + promotablePreloads.set(key, resource); } } } @@ -3495,35 +3532,50 @@ function pushScript(target, props, resumableState, renderState, textEmbedded, in } var src = props.src; - var key = getResourceKey('script', src); // We can make this '); @@ -1631,12 +1649,16 @@ function createRenderState$1(resumableState, nonce, bootstrapScriptContent, boot fontPreloads: new Set(), highImagePreloads: new Set(), // usedImagePreloads: new Set(), - precedences: new Map(), - stylePrecedences: new Map(), + styles: new Map(), bootstrapScripts: new Set(), scripts: new Set(), bulkPreloads: new Set(), - preloadsMap: new Map(), + preloads: { + images: new Map(), + stylesheets: new Map(), + scripts: new Map(), + moduleScripts: new Map() + }, nonce: nonce, // like a module global for currently rendering boundary boundaryResources: null, @@ -1646,17 +1668,32 @@ function createRenderState$1(resumableState, nonce, bootstrapScriptContent, boot if (bootstrapScripts !== undefined) { for (var i = 0; i < bootstrapScripts.length; i++) { var scriptConfig = bootstrapScripts[i]; - var src = typeof scriptConfig === 'string' ? scriptConfig : scriptConfig.src; - var integrity = typeof scriptConfig === 'string' ? undefined : scriptConfig.integrity; - var crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; - preloadBootstrapScript(resumableState, renderState, src, nonce, integrity, crossOrigin); + var src = void 0, + crossOrigin = void 0, + integrity = void 0; + var props = { + rel: 'preload', + as: 'script', + fetchPriority: 'low', + nonce: nonce + }; + + if (typeof scriptConfig === 'string') { + props.href = src = scriptConfig; + } else { + props.href = src = scriptConfig.src; + props.integrity = integrity = typeof scriptConfig.integrity === 'string' ? scriptConfig.integrity : undefined; + props.crossOrigin = crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } + + preloadBootstrapScriptOrModule(resumableState, renderState, src, props); bootstrapChunks.push(startScriptSrc, stringToChunk(escapeTextForBrowser(src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (integrity) { + if (typeof integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(integrity))); } @@ -1672,20 +1709,32 @@ function createRenderState$1(resumableState, nonce, bootstrapScriptContent, boot for (var _i = 0; _i < bootstrapModules.length; _i++) { var _scriptConfig = bootstrapModules[_i]; - var _src = typeof _scriptConfig === 'string' ? _scriptConfig : _scriptConfig.src; + var _src = void 0, + _crossOrigin = void 0, + _integrity = void 0; - var _integrity = typeof _scriptConfig === 'string' ? undefined : _scriptConfig.integrity; + var _props = { + rel: 'modulepreload', + fetchPriority: 'low', + nonce: nonce + }; - var _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + if (typeof _scriptConfig === 'string') { + _props.href = _src = _scriptConfig; + } else { + _props.href = _src = _scriptConfig.src; + _props.integrity = _integrity = typeof _scriptConfig.integrity === 'string' ? _scriptConfig.integrity : undefined; + _props.crossOrigin = _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } - preloadBootstrapModule(resumableState, renderState, _src, nonce, _integrity, _crossOrigin); + preloadBootstrapScriptOrModule(resumableState, renderState, _src, _props); bootstrapChunks.push(startModuleSrc, stringToChunk(escapeTextForBrowser(_src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (_integrity) { + if (typeof _integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(_integrity))); } @@ -1718,10 +1767,18 @@ function createResumableState(identifierPrefix, externalRuntimeConfig) { hasHtml: false, // @TODO add bootstrap script to implicit preloads // persistent - preloadsMap: {}, - preconnectsMap: {}, - stylesMap: {}, - scriptsMap: {} + unknownResources: {}, + dnsResources: {}, + connectResources: { + default: {}, + anonymous: {}, + credentials: {} + }, + imageResources: {}, + styleResources: {}, + scriptResources: {}, + moduleUnknownResources: {}, + moduleScriptResources: {} }; } // Constants for the insertion mode we're currently writing in. We don't encode all HTML5 insertion // modes. We only include the variants as they matter for the sake of our purposes. @@ -2939,7 +2996,7 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse if (props.rel === 'stylesheet') { // This may hoistable as a Stylesheet Resource, otherwise it will emit in place - var key = getResourceKey('style', href); + var key = getResourceKey(href); if (typeof precedence !== 'string' || props.disabled != null || props.onLoad || props.onError) { // This stylesheet is either not opted into Resource semantics or has conflicting properties which @@ -2960,67 +3017,58 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse return pushLinkImpl(target, props); } else { // This stylesheet refers to a Resource and we create a new one if necessary - var stylesInPrecedence = renderState.precedences.get(precedence); - - if (!resumableState.stylesMap.hasOwnProperty(key)) { - var resourceProps = stylesheetPropsFromRawProps(props); - var state = NoState; + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; + + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; // If this is the first time we've encountered this precedence we need + // to create a StyleQueue + + if (!styleQueue) { + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); + } - if (resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = resumableState.preloadsMap[key]; - adoptPreloadPropsForStylesheetProps(resourceProps, preloadProps); - var preloadResource = renderState.preloadsMap.get(key); + var resource = { + state: PENDING$1, + props: stylesheetPropsFromRawProps(props) + }; - if (preloadResource) { - // If we already had a preload we don't want that resource to flush directly. - // We let the newly created resource govern flushing. - preloadResource.state |= Blocked; + if (resourceState) { + // When resourceState is truty it is a Preload state. We cast it for clarity + var preloadState = resourceState; - if (preloadResource.state & Flushed) { - state = PreloadFlushed; - } - } else { - // If we resumed then we assume that this was already flushed - // by the shell. - state = PreloadFlushed; + if (preloadState.length === 2) { + adoptPreloadCredentials(resource.props, preloadState); } - } - var resource = { - type: 'stylesheet', - chunks: [], - state: state, - props: resourceProps - }; - resumableState.stylesMap[key] = null; - - if (!stylesInPrecedence) { - stylesInPrecedence = new Map(); - renderState.precedences.set(precedence, stylesInPrecedence); - var emptyStyleResource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [] - } - }; - stylesInPrecedence.set('', emptyStyleResource); + var preloadResource = renderState.preloads.stylesheets.get(key); - { - if (renderState.stylePrecedences.has(precedence)) { - error('React constructed an empty style resource when a style resource already exists for this precedence: "%s". This is a bug in React.', precedence); - } + if (preloadResource && preloadResource.length > 0) { + // The Preload for this resource was created in this render pass and has not flushed yet so + // we need to clear it to avoid it flushing. + preloadResource.length = 0; + } else { + // Either the preload resource from this render already flushed in this render pass + // or the preload flushed in a prior pass (prerender). In either case we need to mark + // this resource as already having been preloaded. + resource.state = PRELOADED; } + } // We add the newly created resource to our StyleQueue and if necessary + // track the resource with the currently rendering boundary - renderState.stylePrecedences.set(precedence, emptyStyleResource); - } - stylesInPrecedence.set(key, resource); + styleQueue.sheets.set(key, resource); if (renderState.boundaryResources) { - renderState.boundaryResources.add(resource); + renderState.boundaryResources.stylesheets.add(resource); } } else { // We need to track whether this boundary should wait on this resource or not. @@ -3028,12 +3076,12 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse // it. However, it's possible when you resume that the style has already been emitted // and then it wouldn't be recreated in the RenderState and there's no need to track // it again since we should've hoisted it to the shell already. - if (stylesInPrecedence) { - var _resource = stylesInPrecedence.get(key); + if (styleQueue) { + var _resource = styleQueue.sheets.get(key); if (_resource) { if (renderState.boundaryResources) { - renderState.boundaryResources.add(_resource); + renderState.boundaryResources.stylesheets.add(_resource); } } } @@ -3131,47 +3179,47 @@ function pushStyle(target, props, resumableState, renderState, textEmbedded, ins } } - var key = getResourceKey('style', href); - var resource = renderState.stylePrecedences.get(precedence); + var key = getResourceKey(href); + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; - if (!resumableState.stylesMap.hasOwnProperty(key)) { - if (!resource) { - resource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [href] - } - }; - renderState.stylePrecedences.set(precedence, resource); - var stylesInPrecedence = new Map(); - stylesInPrecedence.set('', resource); + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; - { - if (renderState.precedences.has(precedence)) { - error('React constructed a new style precedence set when one already exists for this precedence: "%s". This is a bug in React.', precedence); - } + { + if (resourceState) { + error('React encountered a hoistable style tag for the same href as a preload: "%s". When using a style tag to inline styles you should not also preload it as a stylsheet.', href); } + } - renderState.precedences.set(precedence, stylesInPrecedence); + if (!styleQueue) { + // This is the first time we've encountered this precedence we need + // to create a StyleQueue. + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [stringToChunk(escapeTextForBrowser(href))], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); } else { - resource.props.hrefs.push(href); + // We have seen this precedence before and need to track this href + styleQueue.hrefs.push(stringToChunk(escapeTextForBrowser(href))); } - resumableState.stylesMap[key] = null; - pushStyleContents(resource.chunks, props); + pushStyleContents(styleQueue.rules, props); } - if (resource) { + if (styleQueue) { // We need to track whether this boundary should wait on this resource or not. // Typically this resource should always exist since we either had it or just created // it. However, it's possible when you resume that the style has already been emitted // and then it wouldn't be recreated in the RenderState and there's no need to track // it again since we should've hoisted it to the shell already. if (renderState.boundaryResources) { - renderState.boundaryResources.add(resource); + renderState.boundaryResources.styles.add(styleQueue); } } @@ -3260,36 +3308,35 @@ function pushStyleContents(target, props) { return; } -function getImagePreloadKey(href, imageSrcSet, imageSizes) { - var uniquePart = ''; - - if (typeof imageSrcSet === 'string' && imageSrcSet !== '') { - uniquePart += '[' + imageSrcSet + ']'; - - if (typeof imageSizes === 'string') { - uniquePart += '[' + imageSizes + ']'; - } - } else { - uniquePart += '[][]' + href; - } - - return getResourceKey('image', uniquePart); -} - function pushImg(target, props, resumableState, renderState, pictureTagInScope) { var src = props.src, srcSet = props.srcSet; - if (props.loading !== 'lazy' && (typeof src === 'string' || typeof srcSet === 'string') && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded + if (props.loading !== 'lazy' && (src || srcSet) && (typeof src === 'string' || src == null) && (typeof srcSet === 'string' || srcSet == null) && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded !(typeof src === 'string' && src[4] === ':' && (src[0] === 'd' || src[0] === 'D') && (src[1] === 'a' || src[1] === 'A') && (src[2] === 't' || src[2] === 'T') && (src[3] === 'a' || src[3] === 'A')) && !(typeof srcSet === 'string' && srcSet[4] === ':' && (srcSet[0] === 'd' || srcSet[0] === 'D') && (srcSet[1] === 'a' || srcSet[1] === 'A') && (srcSet[2] === 't' || srcSet[2] === 'T') && (srcSet[3] === 'a' || srcSet[3] === 'A'))) { // We have a suspensey image and ought to preload it to optimize the loading of display blocking // resumableState. - var sizes = props.sizes; - var key = getImagePreloadKey(src, srcSet, sizes); - var resource; + var sizes = typeof props.sizes === 'string' ? props.sizes : undefined; + var key = getImageResourceKey(src, srcSet, sizes); + var promotablePreloads = renderState.preloads.images; + var resource = promotablePreloads.get(key); - if (!resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = { + if (resource) { + // We consider whether this preload can be promoted to higher priority flushing queue. + // The only time a resource will exist here is if it was created during this render + // and was not already in the high priority queue. + if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { + // Delete the resource from the map since we are promoting it and don't want to + // reenter this branch in a second pass for duplicate img hrefs. + promotablePreloads.delete(key); // $FlowFixMe - Flow should understand that this is a Resource if the condition was true + + renderState.highImagePreloads.add(resource); + } + } else if (!resumableState.imageResources.hasOwnProperty(key)) { + // We must construct a new preload resource + resumableState.imageResources[key] = PRELOAD_NO_CREDS; + resource = []; + pushLinkImpl(resource, { rel: 'preload', as: 'image', // There is a bug in Safari where imageSrcSet is not respected on preload links @@ -3304,25 +3351,15 @@ function pushImg(target, props, resumableState, renderState, pictureTagInScope) type: props.type, fetchPriority: props.fetchPriority, referrerPolicy: props.referrerPolicy - }; - resource = { - type: 'preload', - chunks: [], - state: NoState, - props: preloadProps - }; - resumableState.preloadsMap[key] = preloadProps; - renderState.preloadsMap.set(key, resource); - pushLinkImpl(resource.chunks, preloadProps); - } else { - resource = renderState.preloadsMap.get(key); - } + }); - if (resource) { if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { renderState.highImagePreloads.add(resource); } else { - renderState.bulkPreloads.add(resource); + renderState.bulkPreloads.add(resource); // We can bump the priority up if the same img is rendered later + // with fetchPriority="high" + + promotablePreloads.set(key, resource); } } } @@ -3496,35 +3533,50 @@ function pushScript(target, props, resumableState, renderState, textEmbedded, in } var src = props.src; - var key = getResourceKey('script', src); // We can make this '); @@ -1711,12 +1729,16 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst fontPreloads: new Set(), highImagePreloads: new Set(), // usedImagePreloads: new Set(), - precedences: new Map(), - stylePrecedences: new Map(), + styles: new Map(), bootstrapScripts: new Set(), scripts: new Set(), bulkPreloads: new Set(), - preloadsMap: new Map(), + preloads: { + images: new Map(), + stylesheets: new Map(), + scripts: new Map(), + moduleScripts: new Map() + }, nonce: nonce, // like a module global for currently rendering boundary boundaryResources: null, @@ -1726,17 +1748,32 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst if (bootstrapScripts !== undefined) { for (var i = 0; i < bootstrapScripts.length; i++) { var scriptConfig = bootstrapScripts[i]; - var src = typeof scriptConfig === 'string' ? scriptConfig : scriptConfig.src; - var integrity = typeof scriptConfig === 'string' ? undefined : scriptConfig.integrity; - var crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; - preloadBootstrapScript(resumableState, renderState, src, nonce, integrity, crossOrigin); + var src = void 0, + crossOrigin = void 0, + integrity = void 0; + var props = { + rel: 'preload', + as: 'script', + fetchPriority: 'low', + nonce: nonce + }; + + if (typeof scriptConfig === 'string') { + props.href = src = scriptConfig; + } else { + props.href = src = scriptConfig.src; + props.integrity = integrity = typeof scriptConfig.integrity === 'string' ? scriptConfig.integrity : undefined; + props.crossOrigin = crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } + + preloadBootstrapScriptOrModule(resumableState, renderState, src, props); bootstrapChunks.push(startScriptSrc, stringToChunk(escapeTextForBrowser(src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (integrity) { + if (typeof integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(integrity))); } @@ -1752,20 +1789,32 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst for (var _i = 0; _i < bootstrapModules.length; _i++) { var _scriptConfig = bootstrapModules[_i]; - var _src = typeof _scriptConfig === 'string' ? _scriptConfig : _scriptConfig.src; + var _src = void 0, + _crossOrigin = void 0, + _integrity = void 0; - var _integrity = typeof _scriptConfig === 'string' ? undefined : _scriptConfig.integrity; + var _props = { + rel: 'modulepreload', + fetchPriority: 'low', + nonce: nonce + }; - var _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + if (typeof _scriptConfig === 'string') { + _props.href = _src = _scriptConfig; + } else { + _props.href = _src = _scriptConfig.src; + _props.integrity = _integrity = typeof _scriptConfig.integrity === 'string' ? _scriptConfig.integrity : undefined; + _props.crossOrigin = _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } - preloadBootstrapModule(resumableState, renderState, _src, nonce, _integrity, _crossOrigin); + preloadBootstrapScriptOrModule(resumableState, renderState, _src, _props); bootstrapChunks.push(startModuleSrc, stringToChunk(escapeTextForBrowser(_src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (_integrity) { + if (typeof _integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(_integrity))); } @@ -1798,10 +1847,18 @@ function createResumableState(identifierPrefix, externalRuntimeConfig) { hasHtml: false, // @TODO add bootstrap script to implicit preloads // persistent - preloadsMap: {}, - preconnectsMap: {}, - stylesMap: {}, - scriptsMap: {} + unknownResources: {}, + dnsResources: {}, + connectResources: { + default: {}, + anonymous: {}, + credentials: {} + }, + imageResources: {}, + styleResources: {}, + scriptResources: {}, + moduleUnknownResources: {}, + moduleScriptResources: {} }; } // Constants for the insertion mode we're currently writing in. We don't encode all HTML5 insertion // modes. We only include the variants as they matter for the sake of our purposes. @@ -3021,7 +3078,7 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse if (props.rel === 'stylesheet') { // This may hoistable as a Stylesheet Resource, otherwise it will emit in place - var key = getResourceKey('style', href); + var key = getResourceKey(href); if (typeof precedence !== 'string' || props.disabled != null || props.onLoad || props.onError) { // This stylesheet is either not opted into Resource semantics or has conflicting properties which @@ -3042,67 +3099,58 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse return pushLinkImpl(target, props); } else { // This stylesheet refers to a Resource and we create a new one if necessary - var stylesInPrecedence = renderState.precedences.get(precedence); - - if (!resumableState.stylesMap.hasOwnProperty(key)) { - var resourceProps = stylesheetPropsFromRawProps(props); - var state = NoState; + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; + + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; // If this is the first time we've encountered this precedence we need + // to create a StyleQueue + + if (!styleQueue) { + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); + } - if (resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = resumableState.preloadsMap[key]; - adoptPreloadPropsForStylesheetProps(resourceProps, preloadProps); - var preloadResource = renderState.preloadsMap.get(key); + var resource = { + state: PENDING$1, + props: stylesheetPropsFromRawProps(props) + }; - if (preloadResource) { - // If we already had a preload we don't want that resource to flush directly. - // We let the newly created resource govern flushing. - preloadResource.state |= Blocked; + if (resourceState) { + // When resourceState is truty it is a Preload state. We cast it for clarity + var preloadState = resourceState; - if (preloadResource.state & Flushed) { - state = PreloadFlushed; - } - } else { - // If we resumed then we assume that this was already flushed - // by the shell. - state = PreloadFlushed; + if (preloadState.length === 2) { + adoptPreloadCredentials(resource.props, preloadState); } - } - var resource = { - type: 'stylesheet', - chunks: [], - state: state, - props: resourceProps - }; - resumableState.stylesMap[key] = null; - - if (!stylesInPrecedence) { - stylesInPrecedence = new Map(); - renderState.precedences.set(precedence, stylesInPrecedence); - var emptyStyleResource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [] - } - }; - stylesInPrecedence.set('', emptyStyleResource); + var preloadResource = renderState.preloads.stylesheets.get(key); - { - if (renderState.stylePrecedences.has(precedence)) { - error('React constructed an empty style resource when a style resource already exists for this precedence: "%s". This is a bug in React.', precedence); - } + if (preloadResource && preloadResource.length > 0) { + // The Preload for this resource was created in this render pass and has not flushed yet so + // we need to clear it to avoid it flushing. + preloadResource.length = 0; + } else { + // Either the preload resource from this render already flushed in this render pass + // or the preload flushed in a prior pass (prerender). In either case we need to mark + // this resource as already having been preloaded. + resource.state = PRELOADED; } + } // We add the newly created resource to our StyleQueue and if necessary + // track the resource with the currently rendering boundary - renderState.stylePrecedences.set(precedence, emptyStyleResource); - } - stylesInPrecedence.set(key, resource); + styleQueue.sheets.set(key, resource); if (renderState.boundaryResources) { - renderState.boundaryResources.add(resource); + renderState.boundaryResources.stylesheets.add(resource); } } else { // We need to track whether this boundary should wait on this resource or not. @@ -3110,12 +3158,12 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse // it. However, it's possible when you resume that the style has already been emitted // and then it wouldn't be recreated in the RenderState and there's no need to track // it again since we should've hoisted it to the shell already. - if (stylesInPrecedence) { - var _resource = stylesInPrecedence.get(key); + if (styleQueue) { + var _resource = styleQueue.sheets.get(key); if (_resource) { if (renderState.boundaryResources) { - renderState.boundaryResources.add(_resource); + renderState.boundaryResources.stylesheets.add(_resource); } } } @@ -3213,47 +3261,47 @@ function pushStyle(target, props, resumableState, renderState, textEmbedded, ins } } - var key = getResourceKey('style', href); - var resource = renderState.stylePrecedences.get(precedence); + var key = getResourceKey(href); + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; - if (!resumableState.stylesMap.hasOwnProperty(key)) { - if (!resource) { - resource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [href] - } - }; - renderState.stylePrecedences.set(precedence, resource); - var stylesInPrecedence = new Map(); - stylesInPrecedence.set('', resource); + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; - { - if (renderState.precedences.has(precedence)) { - error('React constructed a new style precedence set when one already exists for this precedence: "%s". This is a bug in React.', precedence); - } + { + if (resourceState) { + error('React encountered a hoistable style tag for the same href as a preload: "%s". When using a style tag to inline styles you should not also preload it as a stylsheet.', href); } + } - renderState.precedences.set(precedence, stylesInPrecedence); + if (!styleQueue) { + // This is the first time we've encountered this precedence we need + // to create a StyleQueue. + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [stringToChunk(escapeTextForBrowser(href))], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); } else { - resource.props.hrefs.push(href); + // We have seen this precedence before and need to track this href + styleQueue.hrefs.push(stringToChunk(escapeTextForBrowser(href))); } - resumableState.stylesMap[key] = null; - pushStyleContents(resource.chunks, props); + pushStyleContents(styleQueue.rules, props); } - if (resource) { + if (styleQueue) { // We need to track whether this boundary should wait on this resource or not. // Typically this resource should always exist since we either had it or just created // it. However, it's possible when you resume that the style has already been emitted // and then it wouldn't be recreated in the RenderState and there's no need to track // it again since we should've hoisted it to the shell already. if (renderState.boundaryResources) { - renderState.boundaryResources.add(resource); + renderState.boundaryResources.styles.add(styleQueue); } } @@ -3342,36 +3390,35 @@ function pushStyleContents(target, props) { return; } -function getImagePreloadKey(href, imageSrcSet, imageSizes) { - var uniquePart = ''; - - if (typeof imageSrcSet === 'string' && imageSrcSet !== '') { - uniquePart += '[' + imageSrcSet + ']'; - - if (typeof imageSizes === 'string') { - uniquePart += '[' + imageSizes + ']'; - } - } else { - uniquePart += '[][]' + href; - } - - return getResourceKey('image', uniquePart); -} - function pushImg(target, props, resumableState, renderState, pictureTagInScope) { var src = props.src, srcSet = props.srcSet; - if (props.loading !== 'lazy' && (typeof src === 'string' || typeof srcSet === 'string') && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded + if (props.loading !== 'lazy' && (src || srcSet) && (typeof src === 'string' || src == null) && (typeof srcSet === 'string' || srcSet == null) && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded !(typeof src === 'string' && src[4] === ':' && (src[0] === 'd' || src[0] === 'D') && (src[1] === 'a' || src[1] === 'A') && (src[2] === 't' || src[2] === 'T') && (src[3] === 'a' || src[3] === 'A')) && !(typeof srcSet === 'string' && srcSet[4] === ':' && (srcSet[0] === 'd' || srcSet[0] === 'D') && (srcSet[1] === 'a' || srcSet[1] === 'A') && (srcSet[2] === 't' || srcSet[2] === 'T') && (srcSet[3] === 'a' || srcSet[3] === 'A'))) { // We have a suspensey image and ought to preload it to optimize the loading of display blocking // resumableState. - var sizes = props.sizes; - var key = getImagePreloadKey(src, srcSet, sizes); - var resource; + var sizes = typeof props.sizes === 'string' ? props.sizes : undefined; + var key = getImageResourceKey(src, srcSet, sizes); + var promotablePreloads = renderState.preloads.images; + var resource = promotablePreloads.get(key); - if (!resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = { + if (resource) { + // We consider whether this preload can be promoted to higher priority flushing queue. + // The only time a resource will exist here is if it was created during this render + // and was not already in the high priority queue. + if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { + // Delete the resource from the map since we are promoting it and don't want to + // reenter this branch in a second pass for duplicate img hrefs. + promotablePreloads.delete(key); // $FlowFixMe - Flow should understand that this is a Resource if the condition was true + + renderState.highImagePreloads.add(resource); + } + } else if (!resumableState.imageResources.hasOwnProperty(key)) { + // We must construct a new preload resource + resumableState.imageResources[key] = PRELOAD_NO_CREDS; + resource = []; + pushLinkImpl(resource, { rel: 'preload', as: 'image', // There is a bug in Safari where imageSrcSet is not respected on preload links @@ -3386,25 +3433,15 @@ function pushImg(target, props, resumableState, renderState, pictureTagInScope) type: props.type, fetchPriority: props.fetchPriority, referrerPolicy: props.referrerPolicy - }; - resource = { - type: 'preload', - chunks: [], - state: NoState, - props: preloadProps - }; - resumableState.preloadsMap[key] = preloadProps; - renderState.preloadsMap.set(key, resource); - pushLinkImpl(resource.chunks, preloadProps); - } else { - resource = renderState.preloadsMap.get(key); - } + }); - if (resource) { if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { renderState.highImagePreloads.add(resource); } else { - renderState.bulkPreloads.add(resource); + renderState.bulkPreloads.add(resource); // We can bump the priority up if the same img is rendered later + // with fetchPriority="high" + + promotablePreloads.set(key, resource); } } } @@ -3578,35 +3615,50 @@ function pushScript(target, props, resumableState, renderState, textEmbedded, in } var src = props.src; - var key = getResourceKey('script', src); // We can make this '); @@ -1711,12 +1729,16 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst fontPreloads: new Set(), highImagePreloads: new Set(), // usedImagePreloads: new Set(), - precedences: new Map(), - stylePrecedences: new Map(), + styles: new Map(), bootstrapScripts: new Set(), scripts: new Set(), bulkPreloads: new Set(), - preloadsMap: new Map(), + preloads: { + images: new Map(), + stylesheets: new Map(), + scripts: new Map(), + moduleScripts: new Map() + }, nonce: nonce, // like a module global for currently rendering boundary boundaryResources: null, @@ -1726,17 +1748,32 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst if (bootstrapScripts !== undefined) { for (var i = 0; i < bootstrapScripts.length; i++) { var scriptConfig = bootstrapScripts[i]; - var src = typeof scriptConfig === 'string' ? scriptConfig : scriptConfig.src; - var integrity = typeof scriptConfig === 'string' ? undefined : scriptConfig.integrity; - var crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; - preloadBootstrapScript(resumableState, renderState, src, nonce, integrity, crossOrigin); + var src = void 0, + crossOrigin = void 0, + integrity = void 0; + var props = { + rel: 'preload', + as: 'script', + fetchPriority: 'low', + nonce: nonce + }; + + if (typeof scriptConfig === 'string') { + props.href = src = scriptConfig; + } else { + props.href = src = scriptConfig.src; + props.integrity = integrity = typeof scriptConfig.integrity === 'string' ? scriptConfig.integrity : undefined; + props.crossOrigin = crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } + + preloadBootstrapScriptOrModule(resumableState, renderState, src, props); bootstrapChunks.push(startScriptSrc, stringToChunk(escapeTextForBrowser(src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (integrity) { + if (typeof integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(integrity))); } @@ -1752,20 +1789,32 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst for (var _i = 0; _i < bootstrapModules.length; _i++) { var _scriptConfig = bootstrapModules[_i]; - var _src = typeof _scriptConfig === 'string' ? _scriptConfig : _scriptConfig.src; + var _src = void 0, + _crossOrigin = void 0, + _integrity = void 0; - var _integrity = typeof _scriptConfig === 'string' ? undefined : _scriptConfig.integrity; + var _props = { + rel: 'modulepreload', + fetchPriority: 'low', + nonce: nonce + }; - var _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + if (typeof _scriptConfig === 'string') { + _props.href = _src = _scriptConfig; + } else { + _props.href = _src = _scriptConfig.src; + _props.integrity = _integrity = typeof _scriptConfig.integrity === 'string' ? _scriptConfig.integrity : undefined; + _props.crossOrigin = _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } - preloadBootstrapModule(resumableState, renderState, _src, nonce, _integrity, _crossOrigin); + preloadBootstrapScriptOrModule(resumableState, renderState, _src, _props); bootstrapChunks.push(startModuleSrc, stringToChunk(escapeTextForBrowser(_src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (_integrity) { + if (typeof _integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(_integrity))); } @@ -1798,10 +1847,18 @@ function createResumableState(identifierPrefix, externalRuntimeConfig) { hasHtml: false, // @TODO add bootstrap script to implicit preloads // persistent - preloadsMap: {}, - preconnectsMap: {}, - stylesMap: {}, - scriptsMap: {} + unknownResources: {}, + dnsResources: {}, + connectResources: { + default: {}, + anonymous: {}, + credentials: {} + }, + imageResources: {}, + styleResources: {}, + scriptResources: {}, + moduleUnknownResources: {}, + moduleScriptResources: {} }; } // Constants for the insertion mode we're currently writing in. We don't encode all HTML5 insertion // modes. We only include the variants as they matter for the sake of our purposes. @@ -3021,7 +3078,7 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse if (props.rel === 'stylesheet') { // This may hoistable as a Stylesheet Resource, otherwise it will emit in place - var key = getResourceKey('style', href); + var key = getResourceKey(href); if (typeof precedence !== 'string' || props.disabled != null || props.onLoad || props.onError) { // This stylesheet is either not opted into Resource semantics or has conflicting properties which @@ -3042,67 +3099,58 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse return pushLinkImpl(target, props); } else { // This stylesheet refers to a Resource and we create a new one if necessary - var stylesInPrecedence = renderState.precedences.get(precedence); - - if (!resumableState.stylesMap.hasOwnProperty(key)) { - var resourceProps = stylesheetPropsFromRawProps(props); - var state = NoState; + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; + + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; // If this is the first time we've encountered this precedence we need + // to create a StyleQueue + + if (!styleQueue) { + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); + } - if (resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = resumableState.preloadsMap[key]; - adoptPreloadPropsForStylesheetProps(resourceProps, preloadProps); - var preloadResource = renderState.preloadsMap.get(key); + var resource = { + state: PENDING$1, + props: stylesheetPropsFromRawProps(props) + }; - if (preloadResource) { - // If we already had a preload we don't want that resource to flush directly. - // We let the newly created resource govern flushing. - preloadResource.state |= Blocked; + if (resourceState) { + // When resourceState is truty it is a Preload state. We cast it for clarity + var preloadState = resourceState; - if (preloadResource.state & Flushed) { - state = PreloadFlushed; - } - } else { - // If we resumed then we assume that this was already flushed - // by the shell. - state = PreloadFlushed; + if (preloadState.length === 2) { + adoptPreloadCredentials(resource.props, preloadState); } - } - var resource = { - type: 'stylesheet', - chunks: [], - state: state, - props: resourceProps - }; - resumableState.stylesMap[key] = null; - - if (!stylesInPrecedence) { - stylesInPrecedence = new Map(); - renderState.precedences.set(precedence, stylesInPrecedence); - var emptyStyleResource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [] - } - }; - stylesInPrecedence.set('', emptyStyleResource); + var preloadResource = renderState.preloads.stylesheets.get(key); - { - if (renderState.stylePrecedences.has(precedence)) { - error('React constructed an empty style resource when a style resource already exists for this precedence: "%s". This is a bug in React.', precedence); - } + if (preloadResource && preloadResource.length > 0) { + // The Preload for this resource was created in this render pass and has not flushed yet so + // we need to clear it to avoid it flushing. + preloadResource.length = 0; + } else { + // Either the preload resource from this render already flushed in this render pass + // or the preload flushed in a prior pass (prerender). In either case we need to mark + // this resource as already having been preloaded. + resource.state = PRELOADED; } + } // We add the newly created resource to our StyleQueue and if necessary + // track the resource with the currently rendering boundary - renderState.stylePrecedences.set(precedence, emptyStyleResource); - } - stylesInPrecedence.set(key, resource); + styleQueue.sheets.set(key, resource); if (renderState.boundaryResources) { - renderState.boundaryResources.add(resource); + renderState.boundaryResources.stylesheets.add(resource); } } else { // We need to track whether this boundary should wait on this resource or not. @@ -3110,12 +3158,12 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse // it. However, it's possible when you resume that the style has already been emitted // and then it wouldn't be recreated in the RenderState and there's no need to track // it again since we should've hoisted it to the shell already. - if (stylesInPrecedence) { - var _resource = stylesInPrecedence.get(key); + if (styleQueue) { + var _resource = styleQueue.sheets.get(key); if (_resource) { if (renderState.boundaryResources) { - renderState.boundaryResources.add(_resource); + renderState.boundaryResources.stylesheets.add(_resource); } } } @@ -3213,47 +3261,47 @@ function pushStyle(target, props, resumableState, renderState, textEmbedded, ins } } - var key = getResourceKey('style', href); - var resource = renderState.stylePrecedences.get(precedence); + var key = getResourceKey(href); + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; - if (!resumableState.stylesMap.hasOwnProperty(key)) { - if (!resource) { - resource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [href] - } - }; - renderState.stylePrecedences.set(precedence, resource); - var stylesInPrecedence = new Map(); - stylesInPrecedence.set('', resource); + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; - { - if (renderState.precedences.has(precedence)) { - error('React constructed a new style precedence set when one already exists for this precedence: "%s". This is a bug in React.', precedence); - } + { + if (resourceState) { + error('React encountered a hoistable style tag for the same href as a preload: "%s". When using a style tag to inline styles you should not also preload it as a stylsheet.', href); } + } - renderState.precedences.set(precedence, stylesInPrecedence); + if (!styleQueue) { + // This is the first time we've encountered this precedence we need + // to create a StyleQueue. + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [stringToChunk(escapeTextForBrowser(href))], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); } else { - resource.props.hrefs.push(href); + // We have seen this precedence before and need to track this href + styleQueue.hrefs.push(stringToChunk(escapeTextForBrowser(href))); } - resumableState.stylesMap[key] = null; - pushStyleContents(resource.chunks, props); + pushStyleContents(styleQueue.rules, props); } - if (resource) { + if (styleQueue) { // We need to track whether this boundary should wait on this resource or not. // Typically this resource should always exist since we either had it or just created // it. However, it's possible when you resume that the style has already been emitted // and then it wouldn't be recreated in the RenderState and there's no need to track // it again since we should've hoisted it to the shell already. if (renderState.boundaryResources) { - renderState.boundaryResources.add(resource); + renderState.boundaryResources.styles.add(styleQueue); } } @@ -3342,36 +3390,35 @@ function pushStyleContents(target, props) { return; } -function getImagePreloadKey(href, imageSrcSet, imageSizes) { - var uniquePart = ''; - - if (typeof imageSrcSet === 'string' && imageSrcSet !== '') { - uniquePart += '[' + imageSrcSet + ']'; - - if (typeof imageSizes === 'string') { - uniquePart += '[' + imageSizes + ']'; - } - } else { - uniquePart += '[][]' + href; - } - - return getResourceKey('image', uniquePart); -} - function pushImg(target, props, resumableState, renderState, pictureTagInScope) { var src = props.src, srcSet = props.srcSet; - if (props.loading !== 'lazy' && (typeof src === 'string' || typeof srcSet === 'string') && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded + if (props.loading !== 'lazy' && (src || srcSet) && (typeof src === 'string' || src == null) && (typeof srcSet === 'string' || srcSet == null) && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded !(typeof src === 'string' && src[4] === ':' && (src[0] === 'd' || src[0] === 'D') && (src[1] === 'a' || src[1] === 'A') && (src[2] === 't' || src[2] === 'T') && (src[3] === 'a' || src[3] === 'A')) && !(typeof srcSet === 'string' && srcSet[4] === ':' && (srcSet[0] === 'd' || srcSet[0] === 'D') && (srcSet[1] === 'a' || srcSet[1] === 'A') && (srcSet[2] === 't' || srcSet[2] === 'T') && (srcSet[3] === 'a' || srcSet[3] === 'A'))) { // We have a suspensey image and ought to preload it to optimize the loading of display blocking // resumableState. - var sizes = props.sizes; - var key = getImagePreloadKey(src, srcSet, sizes); - var resource; + var sizes = typeof props.sizes === 'string' ? props.sizes : undefined; + var key = getImageResourceKey(src, srcSet, sizes); + var promotablePreloads = renderState.preloads.images; + var resource = promotablePreloads.get(key); - if (!resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = { + if (resource) { + // We consider whether this preload can be promoted to higher priority flushing queue. + // The only time a resource will exist here is if it was created during this render + // and was not already in the high priority queue. + if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { + // Delete the resource from the map since we are promoting it and don't want to + // reenter this branch in a second pass for duplicate img hrefs. + promotablePreloads.delete(key); // $FlowFixMe - Flow should understand that this is a Resource if the condition was true + + renderState.highImagePreloads.add(resource); + } + } else if (!resumableState.imageResources.hasOwnProperty(key)) { + // We must construct a new preload resource + resumableState.imageResources[key] = PRELOAD_NO_CREDS; + resource = []; + pushLinkImpl(resource, { rel: 'preload', as: 'image', // There is a bug in Safari where imageSrcSet is not respected on preload links @@ -3386,25 +3433,15 @@ function pushImg(target, props, resumableState, renderState, pictureTagInScope) type: props.type, fetchPriority: props.fetchPriority, referrerPolicy: props.referrerPolicy - }; - resource = { - type: 'preload', - chunks: [], - state: NoState, - props: preloadProps - }; - resumableState.preloadsMap[key] = preloadProps; - renderState.preloadsMap.set(key, resource); - pushLinkImpl(resource.chunks, preloadProps); - } else { - resource = renderState.preloadsMap.get(key); - } + }); - if (resource) { if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { renderState.highImagePreloads.add(resource); } else { - renderState.bulkPreloads.add(resource); + renderState.bulkPreloads.add(resource); // We can bump the priority up if the same img is rendered later + // with fetchPriority="high" + + promotablePreloads.set(key, resource); } } } @@ -3578,35 +3615,50 @@ function pushScript(target, props, resumableState, renderState, textEmbedded, in } var src = props.src; - var key = getResourceKey('script', src); // We can make this '); @@ -1780,12 +1798,16 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst fontPreloads: new Set(), highImagePreloads: new Set(), // usedImagePreloads: new Set(), - precedences: new Map(), - stylePrecedences: new Map(), + styles: new Map(), bootstrapScripts: new Set(), scripts: new Set(), bulkPreloads: new Set(), - preloadsMap: new Map(), + preloads: { + images: new Map(), + stylesheets: new Map(), + scripts: new Map(), + moduleScripts: new Map() + }, nonce: nonce, // like a module global for currently rendering boundary boundaryResources: null, @@ -1795,17 +1817,32 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst if (bootstrapScripts !== undefined) { for (var i = 0; i < bootstrapScripts.length; i++) { var scriptConfig = bootstrapScripts[i]; - var src = typeof scriptConfig === 'string' ? scriptConfig : scriptConfig.src; - var integrity = typeof scriptConfig === 'string' ? undefined : scriptConfig.integrity; - var crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; - preloadBootstrapScript(resumableState, renderState, src, nonce, integrity, crossOrigin); + var src = void 0, + crossOrigin = void 0, + integrity = void 0; + var props = { + rel: 'preload', + as: 'script', + fetchPriority: 'low', + nonce: nonce + }; + + if (typeof scriptConfig === 'string') { + props.href = src = scriptConfig; + } else { + props.href = src = scriptConfig.src; + props.integrity = integrity = typeof scriptConfig.integrity === 'string' ? scriptConfig.integrity : undefined; + props.crossOrigin = crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } + + preloadBootstrapScriptOrModule(resumableState, renderState, src, props); bootstrapChunks.push(startScriptSrc, stringToChunk(escapeTextForBrowser(src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (integrity) { + if (typeof integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(integrity))); } @@ -1821,20 +1858,32 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst for (var _i = 0; _i < bootstrapModules.length; _i++) { var _scriptConfig = bootstrapModules[_i]; - var _src = typeof _scriptConfig === 'string' ? _scriptConfig : _scriptConfig.src; + var _src = void 0, + _crossOrigin = void 0, + _integrity = void 0; - var _integrity = typeof _scriptConfig === 'string' ? undefined : _scriptConfig.integrity; + var _props = { + rel: 'modulepreload', + fetchPriority: 'low', + nonce: nonce + }; - var _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + if (typeof _scriptConfig === 'string') { + _props.href = _src = _scriptConfig; + } else { + _props.href = _src = _scriptConfig.src; + _props.integrity = _integrity = typeof _scriptConfig.integrity === 'string' ? _scriptConfig.integrity : undefined; + _props.crossOrigin = _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } - preloadBootstrapModule(resumableState, renderState, _src, nonce, _integrity, _crossOrigin); + preloadBootstrapScriptOrModule(resumableState, renderState, _src, _props); bootstrapChunks.push(startModuleSrc, stringToChunk(escapeTextForBrowser(_src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (_integrity) { + if (typeof _integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(_integrity))); } @@ -1867,10 +1916,18 @@ function createResumableState(identifierPrefix, externalRuntimeConfig) { hasHtml: false, // @TODO add bootstrap script to implicit preloads // persistent - preloadsMap: {}, - preconnectsMap: {}, - stylesMap: {}, - scriptsMap: {} + unknownResources: {}, + dnsResources: {}, + connectResources: { + default: {}, + anonymous: {}, + credentials: {} + }, + imageResources: {}, + styleResources: {}, + scriptResources: {}, + moduleUnknownResources: {}, + moduleScriptResources: {} }; } // Constants for the insertion mode we're currently writing in. We don't encode all HTML5 insertion // modes. We only include the variants as they matter for the sake of our purposes. @@ -3090,7 +3147,7 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse if (props.rel === 'stylesheet') { // This may hoistable as a Stylesheet Resource, otherwise it will emit in place - var key = getResourceKey('style', href); + var key = getResourceKey(href); if (typeof precedence !== 'string' || props.disabled != null || props.onLoad || props.onError) { // This stylesheet is either not opted into Resource semantics or has conflicting properties which @@ -3111,67 +3168,58 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse return pushLinkImpl(target, props); } else { // This stylesheet refers to a Resource and we create a new one if necessary - var stylesInPrecedence = renderState.precedences.get(precedence); - - if (!resumableState.stylesMap.hasOwnProperty(key)) { - var resourceProps = stylesheetPropsFromRawProps(props); - var state = NoState; + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; + + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; // If this is the first time we've encountered this precedence we need + // to create a StyleQueue + + if (!styleQueue) { + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); + } - if (resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = resumableState.preloadsMap[key]; - adoptPreloadPropsForStylesheetProps(resourceProps, preloadProps); - var preloadResource = renderState.preloadsMap.get(key); + var resource = { + state: PENDING$1, + props: stylesheetPropsFromRawProps(props) + }; - if (preloadResource) { - // If we already had a preload we don't want that resource to flush directly. - // We let the newly created resource govern flushing. - preloadResource.state |= Blocked; + if (resourceState) { + // When resourceState is truty it is a Preload state. We cast it for clarity + var preloadState = resourceState; - if (preloadResource.state & Flushed) { - state = PreloadFlushed; - } - } else { - // If we resumed then we assume that this was already flushed - // by the shell. - state = PreloadFlushed; + if (preloadState.length === 2) { + adoptPreloadCredentials(resource.props, preloadState); } - } - var resource = { - type: 'stylesheet', - chunks: [], - state: state, - props: resourceProps - }; - resumableState.stylesMap[key] = null; - - if (!stylesInPrecedence) { - stylesInPrecedence = new Map(); - renderState.precedences.set(precedence, stylesInPrecedence); - var emptyStyleResource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [] - } - }; - stylesInPrecedence.set('', emptyStyleResource); + var preloadResource = renderState.preloads.stylesheets.get(key); - { - if (renderState.stylePrecedences.has(precedence)) { - error('React constructed an empty style resource when a style resource already exists for this precedence: "%s". This is a bug in React.', precedence); - } + if (preloadResource && preloadResource.length > 0) { + // The Preload for this resource was created in this render pass and has not flushed yet so + // we need to clear it to avoid it flushing. + preloadResource.length = 0; + } else { + // Either the preload resource from this render already flushed in this render pass + // or the preload flushed in a prior pass (prerender). In either case we need to mark + // this resource as already having been preloaded. + resource.state = PRELOADED; } + } // We add the newly created resource to our StyleQueue and if necessary + // track the resource with the currently rendering boundary - renderState.stylePrecedences.set(precedence, emptyStyleResource); - } - stylesInPrecedence.set(key, resource); + styleQueue.sheets.set(key, resource); if (renderState.boundaryResources) { - renderState.boundaryResources.add(resource); + renderState.boundaryResources.stylesheets.add(resource); } } else { // We need to track whether this boundary should wait on this resource or not. @@ -3179,12 +3227,12 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse // it. However, it's possible when you resume that the style has already been emitted // and then it wouldn't be recreated in the RenderState and there's no need to track // it again since we should've hoisted it to the shell already. - if (stylesInPrecedence) { - var _resource = stylesInPrecedence.get(key); + if (styleQueue) { + var _resource = styleQueue.sheets.get(key); if (_resource) { if (renderState.boundaryResources) { - renderState.boundaryResources.add(_resource); + renderState.boundaryResources.stylesheets.add(_resource); } } } @@ -3282,47 +3330,47 @@ function pushStyle(target, props, resumableState, renderState, textEmbedded, ins } } - var key = getResourceKey('style', href); - var resource = renderState.stylePrecedences.get(precedence); + var key = getResourceKey(href); + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; - if (!resumableState.stylesMap.hasOwnProperty(key)) { - if (!resource) { - resource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [href] - } - }; - renderState.stylePrecedences.set(precedence, resource); - var stylesInPrecedence = new Map(); - stylesInPrecedence.set('', resource); + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; - { - if (renderState.precedences.has(precedence)) { - error('React constructed a new style precedence set when one already exists for this precedence: "%s". This is a bug in React.', precedence); - } + { + if (resourceState) { + error('React encountered a hoistable style tag for the same href as a preload: "%s". When using a style tag to inline styles you should not also preload it as a stylsheet.', href); } + } - renderState.precedences.set(precedence, stylesInPrecedence); + if (!styleQueue) { + // This is the first time we've encountered this precedence we need + // to create a StyleQueue. + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [stringToChunk(escapeTextForBrowser(href))], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); } else { - resource.props.hrefs.push(href); + // We have seen this precedence before and need to track this href + styleQueue.hrefs.push(stringToChunk(escapeTextForBrowser(href))); } - resumableState.stylesMap[key] = null; - pushStyleContents(resource.chunks, props); + pushStyleContents(styleQueue.rules, props); } - if (resource) { + if (styleQueue) { // We need to track whether this boundary should wait on this resource or not. // Typically this resource should always exist since we either had it or just created // it. However, it's possible when you resume that the style has already been emitted // and then it wouldn't be recreated in the RenderState and there's no need to track // it again since we should've hoisted it to the shell already. if (renderState.boundaryResources) { - renderState.boundaryResources.add(resource); + renderState.boundaryResources.styles.add(styleQueue); } } @@ -3411,36 +3459,35 @@ function pushStyleContents(target, props) { return; } -function getImagePreloadKey(href, imageSrcSet, imageSizes) { - var uniquePart = ''; - - if (typeof imageSrcSet === 'string' && imageSrcSet !== '') { - uniquePart += '[' + imageSrcSet + ']'; - - if (typeof imageSizes === 'string') { - uniquePart += '[' + imageSizes + ']'; - } - } else { - uniquePart += '[][]' + href; - } - - return getResourceKey('image', uniquePart); -} - function pushImg(target, props, resumableState, renderState, pictureTagInScope) { var src = props.src, srcSet = props.srcSet; - if (props.loading !== 'lazy' && (typeof src === 'string' || typeof srcSet === 'string') && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded + if (props.loading !== 'lazy' && (src || srcSet) && (typeof src === 'string' || src == null) && (typeof srcSet === 'string' || srcSet == null) && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded !(typeof src === 'string' && src[4] === ':' && (src[0] === 'd' || src[0] === 'D') && (src[1] === 'a' || src[1] === 'A') && (src[2] === 't' || src[2] === 'T') && (src[3] === 'a' || src[3] === 'A')) && !(typeof srcSet === 'string' && srcSet[4] === ':' && (srcSet[0] === 'd' || srcSet[0] === 'D') && (srcSet[1] === 'a' || srcSet[1] === 'A') && (srcSet[2] === 't' || srcSet[2] === 'T') && (srcSet[3] === 'a' || srcSet[3] === 'A'))) { // We have a suspensey image and ought to preload it to optimize the loading of display blocking // resumableState. - var sizes = props.sizes; - var key = getImagePreloadKey(src, srcSet, sizes); - var resource; + var sizes = typeof props.sizes === 'string' ? props.sizes : undefined; + var key = getImageResourceKey(src, srcSet, sizes); + var promotablePreloads = renderState.preloads.images; + var resource = promotablePreloads.get(key); - if (!resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = { + if (resource) { + // We consider whether this preload can be promoted to higher priority flushing queue. + // The only time a resource will exist here is if it was created during this render + // and was not already in the high priority queue. + if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { + // Delete the resource from the map since we are promoting it and don't want to + // reenter this branch in a second pass for duplicate img hrefs. + promotablePreloads.delete(key); // $FlowFixMe - Flow should understand that this is a Resource if the condition was true + + renderState.highImagePreloads.add(resource); + } + } else if (!resumableState.imageResources.hasOwnProperty(key)) { + // We must construct a new preload resource + resumableState.imageResources[key] = PRELOAD_NO_CREDS; + resource = []; + pushLinkImpl(resource, { rel: 'preload', as: 'image', // There is a bug in Safari where imageSrcSet is not respected on preload links @@ -3455,25 +3502,15 @@ function pushImg(target, props, resumableState, renderState, pictureTagInScope) type: props.type, fetchPriority: props.fetchPriority, referrerPolicy: props.referrerPolicy - }; - resource = { - type: 'preload', - chunks: [], - state: NoState, - props: preloadProps - }; - resumableState.preloadsMap[key] = preloadProps; - renderState.preloadsMap.set(key, resource); - pushLinkImpl(resource.chunks, preloadProps); - } else { - resource = renderState.preloadsMap.get(key); - } + }); - if (resource) { if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { renderState.highImagePreloads.add(resource); } else { - renderState.bulkPreloads.add(resource); + renderState.bulkPreloads.add(resource); // We can bump the priority up if the same img is rendered later + // with fetchPriority="high" + + promotablePreloads.set(key, resource); } } } @@ -3647,35 +3684,50 @@ function pushScript(target, props, resumableState, renderState, textEmbedded, in } var src = props.src; - var key = getResourceKey('script', src); // We can make this