Repository: vercel/next.js
Pull request: Perf: Skip turning Set into Array

## What?

Converting from Set->Array is not needed as it's only used to iterate over the list. Avoids the overhead of converting this.

Changed files:
filename: packages/next/src/server/app-render/create-component-styles-and-scripts.tsx
patch: @@ -29,17 +29,20 @@ export async function createComponentStylesAndScripts({
 
   const styles = renderCssResource(entryCssFiles, ctx)
 
-  const scripts = jsHrefs
-    ? jsHrefs.map((href, index) =>
-        createElement('script', {
-          src: `${ctx.assetPrefix}/_next/${encodeURIPath(href)}${getAssetQueryString(ctx, true)}`,
-          async: true,
-          key: `script-${index}`,
-        })
-      )
-    : null
+  const scripts: React.ReactNode[] = []
+  let scriptIndex = 0
+  for (const href of jsHrefs) {
+    scripts.push(
+      createElement('script', {
+        src: `${ctx.assetPrefix}/_next/${encodeURIPath(href)}${getAssetQueryString(ctx, true)}`,
+        async: true,
+        key: `script-${scriptIndex}`,
+      })
+    )
+    scriptIndex++
+  }
 
   const Comp = interopDefault(await getComponent())
 
-  return [Comp, styles, scripts]
+  return [Comp, styles, scripts.length ? scripts : null]
 }

filename: packages/next/src/server/app-render/get-css-inlined-link-tags.tsx
patch: @@ -9,7 +9,7 @@ export function getLinkAndScriptTags(
   injectedCSS: Set<string>,
   injectedScripts: Set<string>,
   collectNewImports?: boolean
-): { styles: CssResource[]; scripts: string[] } {
+): { styles: ReadonlySet<CssResource>; scripts: ReadonlySet<string> } {
   const filePathWithoutExt = filePath.replace(/\.[^.]+$/, '')
   const cssChunks = new Set<CssResource>()
   const jsChunks = new Set<string>()
@@ -39,5 +39,5 @@ export function getLinkAndScriptTags(
     }
   }
 
-  return { styles: [...cssChunks], scripts: [...jsChunks] }
+  return { styles: cssChunks, scripts: jsChunks }
 }

filename: packages/next/src/server/app-render/get-layer-assets.tsx
patch: @@ -5,6 +5,7 @@ import { getAssetQueryString } from './get-asset-query-string'
 import { encodeURIPath } from '../../shared/lib/encode-uri-path'
 import type { PreloadCallbacks } from './types'
 import { renderCssResource } from './render-css-resource'
+const EMPTY_SET: ReadonlySet<never> = new Set()
 
 export function getLayerAssets({
   ctx,
@@ -31,7 +32,7 @@ export function getLayerAssets({
         injectedJSWithCurrentLayout,
         true
       )
-    : { styles: [], scripts: [] }
+    : { styles: EMPTY_SET, scripts: EMPTY_SET }
 
   const preloadedFontFiles = layoutOrPagePath
     ? getPreloadableFonts(
@@ -76,20 +77,23 @@ export function getLayerAssets({
 
   const styles = renderCssResource(styleTags, ctx, preloadCallbacks)
 
-  const scripts = scriptTags
-    ? scriptTags.map((href, index) => {
-        const fullSrc = `${ctx.assetPrefix}/_next/${encodeURIPath(
-          href
-        )}${getAssetQueryString(ctx, true)}`
+  const scripts: React.ReactNode[] = []
+  let scriptIndex = 0
+  for (const href of scriptTags) {
+    const fullSrc = `${ctx.assetPrefix}/_next/${encodeURIPath(
+      href
+    )}${getAssetQueryString(ctx, true)}`
 
-        return createElement('script', {
-          src: fullSrc,
-          async: true,
-          key: `script-${index}`,
-          nonce: ctx.nonce,
-        })
+    scripts.push(
+      createElement('script', {
+        src: fullSrc,
+        async: true,
+        key: `script-${scriptIndex}`,
+        nonce: ctx.nonce,
       })
-    : []
+    )
+    scriptIndex++
+  }
 
   return styles.length || scripts.length ? [...styles, ...scripts] : null
 }

filename: packages/next/src/server/app-render/render-css-resource.tsx
patch: @@ -10,14 +10,16 @@ import type { PreloadCallbacks } from './types'
  * For external CSS files, renders a <link> tag pointing to the CSS file.
  */
 export function renderCssResource(
-  entryCssFiles: CssResource[],
+  entryCssFiles: Iterable<CssResource>,
   ctx: AppRenderContext,
   preloadCallbacks?: PreloadCallbacks
 ) {
   const {
     componentMod: { createElement },
   } = ctx
-  return entryCssFiles.map((entryCssFile, index) => {
+  const elements: React.ReactNode[] = []
+  let index = 0
+  for (const entryCssFile of entryCssFiles) {
     // `Precedence` is an opt-in signal for React to handle resource
     // loading and deduplication, etc. It's also used as the key to sort
     // resources so they will be injected in the correct order.
@@ -40,33 +42,39 @@ export function renderCssResource(
     )}${getAssetQueryString(ctx, true)}`
 
     if (entryCssFile.inlined && !ctx.parsedRequestHeaders.isRSCRequest) {
-      return createElement(
-        'style',
-        {
+      elements.push(
+        createElement(
+          'style',
+          {
+            key: index,
+            nonce: ctx.nonce,
+            precedence: precedence,
+            href: fullHref,
+          },
+          entryCssFile.content
+        )
+      )
+    } else {
+      preloadCallbacks?.push(() => {
+        ctx.componentMod.preloadStyle(
+          fullHref,
+          ctx.renderOpts.crossOrigin,
+          ctx.nonce
+        )
+      })
+
+      elements.push(
+        createElement('link', {
           key: index,
-          nonce: ctx.nonce,
-          precedence: precedence,
+          rel: 'stylesheet',
           href: fullHref,
-        },
-        entryCssFile.content
+          precedence: precedence,
+          crossOrigin: ctx.renderOpts.crossOrigin,
+          nonce: ctx.nonce,
+        })
       )
     }
-
-    preloadCallbacks?.push(() => {
-      ctx.componentMod.preloadStyle(
-        fullHref,
-        ctx.renderOpts.crossOrigin,
-        ctx.nonce
-      )
-    })
-
-    return createElement('link', {
-      key: index,
-      rel: 'stylesheet',
-      href: fullHref,
-      precedence: precedence,
-      crossOrigin: ctx.renderOpts.crossOrigin,
-      nonce: ctx.nonce,
-    })
-  })
+    index++
+  }
+  return elements
 }