Repository: vercel/next.js
Pull request: [turbopack] tweak the ui of the module-cost benchmark

Have the module-cost benchmark page self identify and output into a text box to make copy-pasting the data out easier.

Closes PACK-5144

Changed files:
filename: bench/module-cost/components/client.js
patch: @@ -1,45 +1,63 @@
 'use client'
 
+import { useEffect, useRef, useState } from 'react'
 import { format, measure } from '../lib/measure'
 
-async function measureClientButton(element, name, fn) {
+function report(result, element, textarea) {
+  const formattedResult = format(result)
+  element.textContent += `: ${formattedResult}`
+  textarea.current.value += `\n    ${formattedResult}`
+  console.log(formattedResult)
+  element.disabled = true
+}
+
+async function measureClientButton(element, textarea, name, fn) {
   if (element.textContent.includes('Loading time')) {
     return
   }
 
   const result = await measure(name, fn)
-
-  element.textContent += ` (${format(result)})`
+  report(result, element, textarea)
 }
 
-async function measureActionButton(element, action) {
+async function measureActionButton(element, textarea, action) {
   if (element.textContent.includes('Loading time')) {
     return
   }
 
   const result = await action()
 
-  element.textContent += ` (${format(result)})`
+  report(result, element, textarea)
 }
 
-async function measureApiButton(element, url) {
+async function measureApiButton(element, textarea, url) {
   if (element.textContent.includes('Loading time')) {
     return
   }
 
   const result = await fetch(url).then((res) => res.json())
 
-  element.textContent += ` (${format(result)})`
+  report(result, element, textarea)
 }
 
 export function Client({ prefix, commonjsAction, esmAction }) {
+  const [runtime, setRuntime] = useState('')
+  const textarea = useRef()
+  useEffect(() => {
+    setRuntime(
+      `${globalThis.TURBOPACK ? 'Turbopack' : 'Webpack'} (${process.env.NODE_ENV})`
+    )
+  }, [])
   return (
     <>
+      <h1>{runtime}</h1>
       <p>
         <button
+          type="button"
           onClick={(e) =>
             measureClientButton(
               e.target,
+              textarea,
               'client commonjs',
               () => import('../lib/commonjs.js')
             )
@@ -50,9 +68,11 @@ export function Client({ prefix, commonjsAction, esmAction }) {
       </p>
       <p>
         <button
+          type="button"
           onClick={(e) =>
             measureClientButton(
               e.target,
+              textarea,
               'client esm',
               () => import('../lib/esm.js')
             )
@@ -64,31 +84,52 @@ export function Client({ prefix, commonjsAction, esmAction }) {
       {commonjsAction && (
         <p>
           <button
-            onClick={(e) => measureActionButton(e.target, commonjsAction)}
+            type="button"
+            onClick={(e) =>
+              measureActionButton(e.target, textarea, commonjsAction)
+            }
           >
             CommonJs server action
           </button>
         </p>
       )}
       {esmAction && (
         <p>
-          <button onClick={(e) => measureActionButton(e.target, esmAction)}>
+          <button
+            type="button"
+            onClick={(e) => measureActionButton(e.target, textarea, esmAction)}
+          >
             ESM server action
           </button>
         </p>
       )}
       <p>
         <button
-          onClick={(e) => measureApiButton(e.target, `${prefix}/commonjs`)}
+          type="button"
+          onClick={(e) =>
+            measureApiButton(e.target, textarea, `${prefix}/commonjs`)
+          }
         >
           CommonJs API
         </button>
       </p>
       <p>
-        <button onClick={(e) => measureApiButton(e.target, `${prefix}/esm`)}>
+        <button
+          type="button"
+          onClick={(e) => measureApiButton(e.target, textarea, `${prefix}/esm`)}
+        >
           ESM API
         </button>
       </p>
+      {
+        // holds all the timing data for easier copy paste
+      }
+      <textarea
+        readOnly={true}
+        ref={textarea}
+        value={runtime}
+        style={{ fieldSizing: 'content' }}
+      ></textarea>
     </>
   )
 }

filename: bench/module-cost/lib/measure.js
patch: @@ -18,12 +18,11 @@ export async function measure(name, fn) {
     executeDuration = end - start
   }
 
-  const result = { loadDuration, executeDuration, files }
-  console.log(`${name} Measurement: ${format(result)}`)
+  const result = { name, loadDuration, executeDuration, files }
 
   return result
 }
 
 export function format(result) {
-  return `Load duration: ${result.loadDuration.toFixed(2)}ms, Execution duration: ${result.executeDuration.toFixed(2)}ms, Files: ${result.files}`
+  return `${result.name}: Load duration: ${result.loadDuration.toFixed(2)}ms, Execution duration: ${result.executeDuration.toFixed(2)}ms, Files: ${result.files}`
 }

filename: bench/module-cost/scripts/prepare-bench.mjs
patch: @@ -1,6 +1,6 @@
-import fs from 'fs/promises'
-import path from 'path'
-import { fileURLToPath } from 'url'
+import fs from 'node:fs/promises'
+import path from 'node:path'
+import { fileURLToPath } from 'node:url'
 
 const __dirname = path.dirname(fileURLToPath(import.meta.url))
 const commonjsDir = path.join(__dirname, '../commonjs')