Skip to content

Conversation

@mischnic
Copy link
Member

@mischnic mischnic commented Jan 7, 2026

Somewhat surprisingly, no breaking API changes

swc-project/swc@v1.15.5...v1.15.8 tracks the changes

@nextjs-bot nextjs-bot added the created-by: Turbopack team PRs by the Turbopack team. label Jan 7, 2026
Copy link
Member Author

mischnic commented Jan 7, 2026

This stack of pull requests is managed by Graphite. Learn more about stacking.

@nextjs-bot
Copy link
Collaborator

Allow CI Workflow Run

  • approve CI run for commit: 13b09b2

Note: this should only be enabled once the PR is ready to go and can only be enabled by a maintainer

@nextjs-bot
Copy link
Collaborator

nextjs-bot commented Jan 7, 2026

Allow CI Workflow Run

  • approve CI run for commit: 8cfac4c

Note: this should only be enabled once the PR is ready to go and can only be enabled by a maintainer

@nextjs-bot
Copy link
Collaborator

nextjs-bot commented Jan 7, 2026

Tests Passed

@nextjs-bot nextjs-bot added the Turbopack Related to Turbopack with Next.js. label Jan 7, 2026
Copy link
Contributor

@vercel vercel bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional Suggestion:

Telemetry object must be initialized before installBindings is called so that SWC load failure events can be recorded

View Details
📝 Patch Details
diff --git a/packages/next/src/build/index.ts b/packages/next/src/build/index.ts
index 49051f0710..2f70e45b9a 100644
--- a/packages/next/src/build/index.ts
+++ b/packages/next/src/build/index.ts
@@ -950,8 +950,6 @@ export default async function build(
       // Reading the config can modify environment variables that influence the bundler selection.
       bundler = finalizeBundlerFromConfig(bundler)
       nextBuildSpan.setAttribute('bundler', getBundlerForTelemetry(bundler))
-      // Install the native bindings early so we can have synchronous access later.
-      await installBindings(config.experimental?.useWasmBinary)
 
       process.env.NEXT_DEPLOYMENT_ID = config.deploymentId || ''
       NextBuildContext.config = config
@@ -962,6 +960,14 @@ export default async function build(
         config.distDir = '.next'
       }
       const distDir = path.join(dir, config.distDir)
+      
+      // Initialize telemetry early so it's available if installBindings fails
+      const telemetry = new Telemetry({ distDir })
+      setGlobal('telemetry', telemetry)
+      
+      // Install the native bindings early so we can have synchronous access later.
+      await installBindings(config.experimental?.useWasmBinary)
+      
       NextBuildContext.distDir = distDir
       setGlobal('phase', PHASE_PRODUCTION_BUILD)
       setGlobal('distDir', distDir)
@@ -1055,10 +1061,6 @@ export default async function build(
 
       const cacheDir = getCacheDir(distDir)
 
-      const telemetry = new Telemetry({ distDir })
-
-      setGlobal('telemetry', telemetry)
-
       const publicDir = path.join(dir, 'public')
       const { pagesDir, appDir } = findPagesDir(dir)
 

Analysis

Problem

The test "Telemetry CLI > production mode > emits event when swc fails to load" was failing because the NEXT_SWC_LOAD_FAILURE telemetry event was not being emitted when SWC failed to load.

Root Cause

In packages/next/src/build/index.ts, the telemetry object was being initialized AFTER installBindings() was called:

  • Line ~952: await installBindings(config.experimental?.useWasmBinary) was called
  • Line ~1058: Telemetry was initialized with const telemetry = new Telemetry({ distDir })

When installBindings() failed and called logLoadFailure()eventSwcLoadFailure(), the telemetry object retrieved from traceGlobals.get('telemetry') was undefined. The eventSwcLoadFailure() function contains an early return check: if (!telemetry) return, so no event was recorded.

Solution Implemented

Moved the telemetry initialization to occur BEFORE installBindings() is called:

  1. Moved the distDir calculation forward (it was needed as a parameter for Telemetry initialization)
  2. Added telemetry initialization immediately after distDir calculation
  3. Moved installBindings() call to after telemetry is initialized
  4. Removed the duplicate telemetry initialization that was at the original location

Code Changes

In packages/next/src/build/index.ts:

  • Line 961: const distDir = path.join(dir, config.distDir) (existing, just positioned earlier)
  • Line 964-966: Added telemetry initialization:
    const telemetry = new Telemetry({ distDir })
    setGlobal('telemetry', telemetry)
  • Line 968: await installBindings(config.experimental?.useWasmBinary) (moved after telemetry init)
  • Removed duplicate telemetry initialization that was previously at line ~1058

Why This Works

Now when SWC fails to load with NODE_OPTIONS='--no-addons':

  1. installBindings() calls loadBindings() which fails
  2. loadFailure() is called which calls await eventSwcLoadFailure()
  3. eventSwcLoadFailure() now finds the telemetry object in traceGlobals
  4. The event is recorded and flushed with await telemetry.flush()
  5. With NEXT_TELEMETRY_DEBUG=1, the event is printed to stderr
  6. The test can successfully find "NEXT_SWC_LOAD_FAILURE" in stderr

Testing

The fix was verified by:

  1. Confirming telemetry is initialized before installBindings
  2. Confirming there are no duplicate telemetry initializations
  3. Confirming setGlobal('telemetry', telemetry) is called
  4. Tracing through the execution flow to confirm the event will be recorded
@nextjs-bot
Copy link
Collaborator

nextjs-bot commented Jan 7, 2026

Stats from current PR

✅ No significant changes detected

📊 All Metrics
📖 Metrics Glossary

Dev Server Metrics:

  • Listen = TCP port starts accepting connections
  • First Request = HTTP server returns successful response
  • Cold = Fresh build (no cache)
  • Warm = With cached build artifacts

Build Metrics:

  • Fresh = Clean build (no .next directory)
  • Cached = With existing .next directory

Change Thresholds:

  • Time: Changes < 50ms AND < 10%, OR < 2% are insignificant
  • Size: Changes < 1KB AND < 1% are insignificant
  • All other changes are flagged to catch regressions

⚡ Dev Server

Metric Canary PR Change Trend
Cold (Listen) 456ms 455ms █▁█▁▁
Cold (First Request) 1.185s 1.170s ▇█▁█▁
Warm (Listen) 456ms 457ms ██▁█▁
Warm (First Request) 385ms 343ms ▁█▂▁▇
📦 Dev Server (Webpack) (Legacy)

📦 Dev Server (Webpack)

Metric Canary PR Change Trend
Cold (Listen) 456ms 455ms ▁▁█▅▁
Cold (First Request) 1.744s 1.728s ▁▁█▄▁
Warm (Listen) 456ms 456ms ▁▁█▄▁
Warm (First Request) 1.757s 1.765s ▁▁█▅▁

⚡ Production Builds

Metric Canary PR Change Trend
Fresh Build 4.119s 4.119s ▁▃█▂█
Cached Build 4.121s 4.129s ▂▁█▁▇
📦 Production Builds (Webpack) (Legacy)

📦 Production Builds (Webpack)

Metric Canary PR Change Trend
Fresh Build 13.843s 13.883s ▁▁█▄▁
Cached Build 13.900s 13.856s ▁▁█▄▁
node_modules Size 457 MB 457 MB ▁▁▁▁▁
📦 Bundle Sizes

Bundle Sizes

⚡ Turbopack

Client

Main Bundles: **430 kB** → **430 kB** ✅ -3 B

82 files with content-based hashes (individual files not comparable between builds)

Server

Middleware
Canary PR Change
middleware-b..fest.js gzip 787 B 785 B
Total 787 B 785 B ✅ -2 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 448 B 451 B
Total 448 B 451 B ⚠️ +3 B

📦 Webpack

Client

Main Bundles
Canary PR Change
2086.HASH.js gzip 169 B N/A -
2161-HASH.js gzip 5.4 kB N/A -
2747-HASH.js gzip 4.48 kB N/A -
4322-HASH.js gzip 52.5 kB N/A -
ec793fe8-HASH.js gzip 62.3 kB N/A -
framework-HASH.js gzip 59.8 kB 59.8 kB
main-app-HASH.js gzip 251 B 254 B 🔴 +3 B (+1%)
main-HASH.js gzip 38.4 kB 38.8 kB
webpack-HASH.js gzip 1.68 kB 1.68 kB
1596.HASH.js gzip N/A 169 B -
2658-HASH.js gzip N/A 52.2 kB -
6349-HASH.js gzip N/A 4.46 kB -
7019-HASH.js gzip N/A 5.42 kB -
b17a3386-HASH.js gzip N/A 62.3 kB -
Total 225 kB 225 kB ⚠️ +128 B
Polyfills
Canary PR Change
polyfills-HASH.js gzip 39.4 kB 39.4 kB
Total 39.4 kB 39.4 kB
Pages
Canary PR Change
_app-HASH.js gzip 194 B 193 B
_error-HASH.js gzip 182 B 182 B
css-HASH.js gzip 336 B 335 B
dynamic-HASH.js gzip 1.8 kB 1.8 kB
edge-ssr-HASH.js gzip 256 B 256 B
head-HASH.js gzip 352 B 349 B
hooks-HASH.js gzip 385 B 384 B
image-HASH.js gzip 580 B 580 B
index-HASH.js gzip 259 B 258 B
link-HASH.js gzip 2.5 kB 2.51 kB
routerDirect..HASH.js gzip 319 B 317 B
script-HASH.js gzip 385 B 387 B
withRouter-HASH.js gzip 316 B 315 B
1afbb74e6ecf..834.css gzip 106 B 106 B
Total 7.97 kB 7.96 kB ✅ -8 B

Server

Edge SSR
Canary PR Change
edge-ssr.js gzip 124 kB 124 kB
page.js gzip 240 kB 240 kB
Total 364 kB 365 kB ⚠️ +730 B
Middleware
Canary PR Change
middleware-b..fest.js gzip 653 B 654 B
middleware-r..fest.js gzip 155 B 156 B
middleware.js gzip 32.6 kB 32.9 kB
edge-runtime..pack.js gzip 842 B 842 B
Total 34.3 kB 34.6 kB ⚠️ +267 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 738 B 738 B
Total 738 B 738 B
Build Cache
Canary PR Change
0.pack gzip 3.62 MB 3.63 MB 🔴 +6.04 kB (+0%)
index.pack gzip 98.7 kB 99.4 kB
index.pack.old gzip 99.7 kB 97.7 kB 🟢 1.95 kB (-2%)
Total 3.82 MB 3.83 MB ⚠️ +4.81 kB

🔄 Shared (bundler-independent)

Runtimes
Canary PR Change
app-page-exp...dev.js gzip 303 kB 303 kB
app-page-exp..prod.js gzip 158 kB 158 kB
app-page-tur...dev.js gzip 302 kB 302 kB
app-page-tur..prod.js gzip 158 kB 158 kB
app-page-tur...dev.js gzip 299 kB 299 kB
app-page-tur..prod.js gzip 156 kB 156 kB
app-page.run...dev.js gzip 299 kB 299 kB
app-page.run..prod.js gzip 156 kB 156 kB
app-route-ex...dev.js gzip 68.7 kB 68.7 kB
app-route-ex..prod.js gzip 47.5 kB 47.5 kB
app-route-tu...dev.js gzip 68.7 kB 68.7 kB
app-route-tu..prod.js gzip 47.5 kB 47.5 kB
app-route-tu...dev.js gzip 68.3 kB 68.3 kB
app-route-tu..prod.js gzip 47.3 kB 47.3 kB
app-route.ru...dev.js gzip 68.3 kB 68.3 kB
app-route.ru..prod.js gzip 47.3 kB 47.3 kB
dist_client_...dev.js gzip 324 B 324 B
dist_client_...dev.js gzip 326 B 326 B
dist_client_...dev.js gzip 318 B 318 B
dist_client_...dev.js gzip 317 B 317 B
pages-api-tu...dev.js gzip 41.1 kB 41.1 kB
pages-api-tu..prod.js gzip 31.2 kB 31.2 kB
pages-api.ru...dev.js gzip 41 kB 41 kB
pages-api.ru..prod.js gzip 31.2 kB 31.2 kB
pages-turbo....dev.js gzip 50.8 kB 50.8 kB
pages-turbo...prod.js gzip 38.2 kB 38.2 kB
pages.runtim...dev.js gzip 50.7 kB 50.7 kB
pages.runtim..prod.js gzip 38.2 kB 38.2 kB
server.runti..prod.js gzip 62.2 kB 62.2 kB
Total 2.68 MB 2.68 MB ✅ -3 B
Copy link
Contributor

@vercel vercel bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional Suggestion:

Telemetry object must be initialized before installBindings is called so that SWC load failure events can be recorded

View Details
📝 Patch Details
diff --git a/packages/next/src/build/index.ts b/packages/next/src/build/index.ts
index 49051f0710..2f70e45b9a 100644
--- a/packages/next/src/build/index.ts
+++ b/packages/next/src/build/index.ts
@@ -950,8 +950,6 @@ export default async function build(
       // Reading the config can modify environment variables that influence the bundler selection.
       bundler = finalizeBundlerFromConfig(bundler)
       nextBuildSpan.setAttribute('bundler', getBundlerForTelemetry(bundler))
-      // Install the native bindings early so we can have synchronous access later.
-      await installBindings(config.experimental?.useWasmBinary)
 
       process.env.NEXT_DEPLOYMENT_ID = config.deploymentId || ''
       NextBuildContext.config = config
@@ -962,6 +960,14 @@ export default async function build(
         config.distDir = '.next'
       }
       const distDir = path.join(dir, config.distDir)
+      
+      // Initialize telemetry early so it's available if installBindings fails
+      const telemetry = new Telemetry({ distDir })
+      setGlobal('telemetry', telemetry)
+      
+      // Install the native bindings early so we can have synchronous access later.
+      await installBindings(config.experimental?.useWasmBinary)
+      
       NextBuildContext.distDir = distDir
       setGlobal('phase', PHASE_PRODUCTION_BUILD)
       setGlobal('distDir', distDir)
@@ -1055,10 +1061,6 @@ export default async function build(
 
       const cacheDir = getCacheDir(distDir)
 
-      const telemetry = new Telemetry({ distDir })
-
-      setGlobal('telemetry', telemetry)
-
       const publicDir = path.join(dir, 'public')
       const { pagesDir, appDir } = findPagesDir(dir)
 

Analysis

Problem

The test "Telemetry CLI > production mode > emits event when swc fails to load" was failing because the NEXT_SWC_LOAD_FAILURE telemetry event was not being emitted when SWC failed to load.

Root Cause

In packages/next/src/build/index.ts, the telemetry object was being initialized AFTER installBindings() was called:

  • Line ~952: await installBindings(config.experimental?.useWasmBinary) was called
  • Line ~1058: Telemetry was initialized with const telemetry = new Telemetry({ distDir })

When installBindings() failed and called logLoadFailure()eventSwcLoadFailure(), the telemetry object retrieved from traceGlobals.get('telemetry') was undefined. The eventSwcLoadFailure() function contains an early return check: if (!telemetry) return, so no event was recorded.

Solution Implemented

Moved the telemetry initialization to occur BEFORE installBindings() is called:

  1. Moved the distDir calculation forward (it was needed as a parameter for Telemetry initialization)
  2. Added telemetry initialization immediately after distDir calculation
  3. Moved installBindings() call to after telemetry is initialized
  4. Removed the duplicate telemetry initialization that was at the original location

Code Changes

In packages/next/src/build/index.ts:

  • Line 961: const distDir = path.join(dir, config.distDir) (existing, just positioned earlier)
  • Line 964-966: Added telemetry initialization:
    const telemetry = new Telemetry({ distDir })
    setGlobal('telemetry', telemetry)
  • Line 968: await installBindings(config.experimental?.useWasmBinary) (moved after telemetry init)
  • Removed duplicate telemetry initialization that was previously at line ~1058

Why This Works

Now when SWC fails to load with NODE_OPTIONS='--no-addons':

  1. installBindings() calls loadBindings() which fails
  2. loadFailure() is called which calls await eventSwcLoadFailure()
  3. eventSwcLoadFailure() now finds the telemetry object in traceGlobals
  4. The event is recorded and flushed with await telemetry.flush()
  5. With NEXT_TELEMETRY_DEBUG=1, the event is printed to stderr
  6. The test can successfully find "NEXT_SWC_LOAD_FAILURE" in stderr

Testing

The fix was verified by:

  1. Confirming telemetry is initialized before installBindings
  2. Confirming there are no duplicate telemetry initializations
  3. Confirming setGlobal('telemetry', telemetry) is called
  4. Tracing through the execution flow to confirm the event will be recorded
@mischnic mischnic marked this pull request as ready for review January 7, 2026 14:54
@mischnic mischnic requested a review from a team January 7, 2026 14:54
@codspeed-hq
Copy link

codspeed-hq bot commented Jan 7, 2026

Merging this PR will not alter performance

Summary

✅ 17 untouched benchmarks
⏩ 3 skipped benchmarks1


Comparing mischnic/swc-54 (8e6eb98) with canary (6805af4)2

Open in CodSpeed

Footnotes

  1. 3 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on canary (f59cf2e) during the generation of this report, so 6805af4 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@mischnic mischnic merged commit 8cf1d1f into canary Jan 7, 2026
297 of 299 checks passed
Copy link
Member Author

mischnic commented Jan 7, 2026

Merge activity

@mischnic mischnic deleted the mischnic/swc-54 branch January 7, 2026 22:30
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jan 22, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

created-by: Turbopack team PRs by the Turbopack team. locked Turbopack Related to Turbopack with Next.js.

4 participants