@wular/pnext 0.0.4 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (395) hide show
  1. package/README.md +22 -16
  2. package/bin/pnext +6 -1
  3. package/config/lint/base.js +7 -7
  4. package/config/ts/base.json +2 -4
  5. package/config/ts/react.json +2 -6
  6. package/package.json +23 -2
  7. package/reference/compat.md +87 -22
  8. package/reference/config.md +40 -93
  9. package/reference/css.md +42 -33
  10. package/reference/dev.md +26 -33
  11. package/reference/env.md +18 -20
  12. package/reference/getting-started.md +132 -0
  13. package/reference/metadata.md +31 -46
  14. package/reference/navigation.md +65 -105
  15. package/reference/performance.md +21 -142
  16. package/reference/rendering.md +78 -75
  17. package/reference/routing.md +97 -107
  18. package/reference/typegen.md +27 -29
  19. package/src/api/cache.ts +36 -37
  20. package/src/api/client-cache.ts +2 -2
  21. package/src/api/client-navigation.ts +121 -114
  22. package/src/api/dynamic.tsx +58 -55
  23. package/src/api/link.tsx +52 -53
  24. package/src/api/navigation.ts +59 -58
  25. package/src/api/server.ts +152 -144
  26. package/src/api/suspense.ts +4 -4
  27. package/src/cli/adapters/vercel-warm.ts +126 -121
  28. package/src/cli/adapters/vercel.ts +437 -443
  29. package/src/cli/analyze.ts +333 -144
  30. package/src/cli/{named-bin.ts → boot/named-bin.ts} +37 -37
  31. package/src/cli/{boot-trace.ts → boot/trace.ts} +10 -10
  32. package/src/cli/build.ts +976 -978
  33. package/src/cli/create.ts +72 -64
  34. package/src/cli/dev.ts +116 -116
  35. package/src/cli/index.ts +94 -85
  36. package/src/cli/migrate/package-json.ts +73 -71
  37. package/src/cli/migrate/report.ts +32 -33
  38. package/src/cli/migrate/{index.ts → run.ts} +48 -49
  39. package/src/cli/migrate/scan.ts +46 -46
  40. package/src/cli/migrate/spinner.ts +9 -9
  41. package/src/cli/migrate/tsconfig.ts +35 -35
  42. package/src/cli/{server-entry.ts → serve/entry.ts} +57 -57
  43. package/src/cli/{request-pipeline.ts → serve/pipeline.ts} +386 -394
  44. package/src/cli/{serve-ui.ts → serve/ui.ts} +47 -47
  45. package/src/cli/start.ts +65 -66
  46. package/src/{typegen.ts → cli/typegen.ts} +59 -59
  47. package/src/client/build.ts +802 -768
  48. package/src/client/chunk-fold.ts +245 -240
  49. package/src/client/{paths.ts → chunk-name.ts} +6 -6
  50. package/src/client/entry.ts +162 -147
  51. package/src/client/prebuilt.ts +231 -223
  52. package/src/client/profile.ts +29 -29
  53. package/src/client/react-compiler.ts +20 -15
  54. package/src/client/{compat-surface.ts → react-tier.ts} +64 -64
  55. package/src/client/reference-stub.ts +51 -51
  56. package/src/client/reference.ts +19 -19
  57. package/src/{api → client}/router/events.ts +32 -17
  58. package/src/{api → client}/router/history.ts +16 -16
  59. package/src/{api → client}/router/hub.ts +52 -53
  60. package/src/{api/router.ts → client/router/index.ts} +61 -60
  61. package/src/{api → client}/router/policies.ts +24 -24
  62. package/src/{api → client}/router/runtime.ts +2022 -1995
  63. package/src/{api → client}/router/types.ts +98 -98
  64. package/src/compat/actions/client-plugin.ts +42 -43
  65. package/src/compat/actions/client-stub.ts +14 -14
  66. package/src/compat/actions/{action-client.ts → client.ts} +194 -193
  67. package/src/compat/actions/config.ts +63 -64
  68. package/src/compat/actions/detect.ts +68 -68
  69. package/src/compat/actions/discovery.ts +105 -105
  70. package/src/compat/actions/{action-dispatch.ts → dispatch.ts} +277 -274
  71. package/src/compat/actions/early-submit.ts +1 -1
  72. package/src/compat/actions/endpoint.ts +198 -198
  73. package/src/compat/actions/extensions.ts +811 -0
  74. package/src/compat/actions/flight.ts +23 -23
  75. package/src/compat/actions/form-state.ts +34 -34
  76. package/src/compat/actions/hoist.ts +382 -236
  77. package/src/compat/actions/ids.ts +7 -12
  78. package/src/compat/actions/index.ts +8 -8
  79. package/src/compat/actions/instances.ts +46 -46
  80. package/src/compat/actions/origin.ts +47 -48
  81. package/src/compat/actions/protocol.ts +22 -22
  82. package/src/compat/actions/registry.ts +19 -19
  83. package/src/compat/{misc/action-return.ts → actions/return.ts} +60 -57
  84. package/src/compat/actions/rewrite.ts +125 -125
  85. package/src/compat/actions/{action-router.ts → router.ts} +10 -10
  86. package/src/compat/actions/serve.ts +132 -136
  87. package/src/compat/actions/server-tag.ts +4 -4
  88. package/src/compat/actions/{action-shared.ts → shared.ts} +34 -34
  89. package/src/compat/actions/unrecognized-error.ts +4 -4
  90. package/src/compat/{index.ts → aliases.ts} +112 -109
  91. package/src/compat/bundler/bun-externals.ts +18 -18
  92. package/src/compat/bundler/cjs-exports.ts +271 -223
  93. package/src/compat/bundler/config.ts +116 -111
  94. package/src/compat/bundler/externals.ts +12 -12
  95. package/src/compat/bundler/import-meta-url.ts +19 -19
  96. package/src/compat/bundler/modularize-imports.ts +36 -33
  97. package/src/compat/bundler/new-url-asset.ts +22 -24
  98. package/src/compat/bundler/optimize-package-imports.ts +111 -107
  99. package/src/compat/bundler/polyfill.ts +28 -28
  100. package/src/compat/bundler/react-compiler.ts +35 -29
  101. package/src/compat/bundler/react-profiler.tsx +11 -11
  102. package/src/compat/bundler/relay-transform.ts +48 -47
  103. package/src/compat/bundler/require-context.ts +119 -113
  104. package/src/compat/bundler/resolve-extensions.ts +19 -19
  105. package/src/compat/bundler/source-cache.ts +25 -25
  106. package/src/compat/bundler/static-imports.ts +7 -7
  107. package/src/compat/bundler/symlink-imports.ts +46 -46
  108. package/src/compat/bundler/tsconfig-paths.ts +13 -15
  109. package/src/compat/bundler/wasm.ts +58 -60
  110. package/src/compat/bundler/webpack-loaders.ts +254 -241
  111. package/src/compat/bundler/worker.ts +101 -104
  112. package/src/compat/cache/build-flags.ts +29 -29
  113. package/src/compat/cache/build-prerender-errors.ts +40 -44
  114. package/src/compat/cache/custom-handler.ts +60 -53
  115. package/src/compat/cache/fetch-patch.ts +240 -240
  116. package/src/compat/cache/handler.ts +27 -31
  117. package/src/compat/cache/modern-handler.ts +148 -126
  118. package/src/compat/cache/resume-data-cache.ts +47 -45
  119. package/src/compat/cache/revalidate.ts +233 -232
  120. package/src/compat/cache/runtime-error.ts +35 -35
  121. package/src/compat/cache/use-cache-transform.ts +442 -417
  122. package/src/compat/cache/use-cache.ts +650 -614
  123. package/src/compat/cache-control.ts +140 -142
  124. package/src/compat/client/base-path.ts +21 -20
  125. package/src/compat/client/css-order.ts +18 -18
  126. package/src/compat/client/errors/bare-boundary.ts +11 -11
  127. package/src/compat/client/errors/control-flow.ts +23 -23
  128. package/src/compat/client/errors/error-boundary.ts +61 -61
  129. package/src/compat/client/errors/global-error.ts +101 -91
  130. package/src/compat/client/errors/install.ts +71 -72
  131. package/src/compat/client/errors/lazy.ts +23 -23
  132. package/src/compat/client/errors/primitive-throw.ts +47 -45
  133. package/src/compat/client/errors/soft-refresh.ts +4 -4
  134. package/src/compat/client/link-status.ts +33 -33
  135. package/src/compat/client/{nav-compat-runtime.ts → nav-runtime.ts} +20 -20
  136. package/src/compat/client/{nav-compat.ts → nav.ts} +12 -13
  137. package/src/compat/client/navigation-scroll.ts +63 -63
  138. package/src/compat/client/optimistic-routing.ts +93 -88
  139. package/src/compat/client/prefetch-cache.ts +23 -24
  140. package/src/compat/client/route-announcer.ts +35 -35
  141. package/src/compat/client/segment-cache-policy.ts +20 -20
  142. package/src/compat/client/segment-cache.ts +309 -315
  143. package/src/compat/client/segment-prefetch.ts +127 -132
  144. package/src/compat/client/trailing-slash.ts +5 -4
  145. package/src/compat/css/chunking.ts +113 -116
  146. package/src/compat/css/inline-css.ts +21 -21
  147. package/src/compat/css/lightningcss.ts +37 -38
  148. package/src/compat/css/modules.ts +161 -175
  149. package/src/compat/css/nonce.ts +7 -7
  150. package/src/compat/css/sass-plugin.ts +18 -21
  151. package/src/compat/css/sass.ts +150 -152
  152. package/src/compat/css/styled-jsx-runtime.ts +27 -27
  153. package/src/compat/css/styled-jsx.ts +21 -21
  154. package/src/compat/edge-runtime.ts +27 -27
  155. package/src/compat/{adapter → export}/build-complete.ts +72 -75
  156. package/src/compat/export/client.ts +29 -31
  157. package/src/compat/export/{index.ts → emit.ts} +111 -110
  158. package/src/compat/export/standalone.ts +62 -54
  159. package/src/compat/image-optimizer/cache.ts +51 -49
  160. package/src/compat/image-optimizer/detect.ts +52 -52
  161. package/src/compat/image-optimizer/{index.ts → optimize.ts} +189 -194
  162. package/src/compat/image-optimizer/source.ts +77 -80
  163. package/src/compat/lifecycle/after-scope.ts +26 -26
  164. package/src/compat/lifecycle/after.ts +48 -45
  165. package/src/compat/lifecycle/error-funnel.ts +98 -102
  166. package/src/compat/lifecycle/error-serialize.ts +30 -26
  167. package/src/compat/lifecycle/error-ui.ts +67 -32
  168. package/src/compat/lifecycle/instrumentation-client.ts +36 -38
  169. package/src/compat/lifecycle/instrumentation.ts +85 -85
  170. package/src/compat/lifecycle/node-console.ts +11 -11
  171. package/src/compat/lifecycle/testmode.ts +160 -132
  172. package/src/compat/mdx/compile.ts +60 -57
  173. package/src/compat/mdx/plugin.ts +11 -11
  174. package/src/compat/mdx/{next-mdx-stub.ts → stub.ts} +5 -5
  175. package/src/compat/{metadata-route-artifacts.ts → metadata-artifacts.ts} +195 -191
  176. package/src/compat/metadata.ts +75 -81
  177. package/src/compat/next/cache.ts +68 -69
  178. package/src/compat/next/canonical-url.ts +9 -9
  179. package/src/compat/next/client-cache.ts +19 -19
  180. package/src/compat/next/client-navigation.ts +112 -116
  181. package/src/compat/next/client-only.ts +1 -1
  182. package/src/compat/next/client-script.tsx +99 -93
  183. package/src/compat/next/client-server.ts +10 -10
  184. package/src/compat/next/config-loader.ts +176 -173
  185. package/src/compat/next/config.ts +7 -7
  186. package/src/compat/next/constants.cjs +6 -6
  187. package/src/compat/next/constants.ts +6 -6
  188. package/src/compat/next/custom-server.ts +26 -24
  189. package/src/compat/next/dist/client/components/app-router-headers.ts +21 -21
  190. package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +3 -4
  191. package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -1
  192. package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -1
  193. package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -1
  194. package/src/compat/next/dynamic.tsx +21 -18
  195. package/src/compat/next/error.tsx +52 -52
  196. package/src/compat/next/font/cache.ts +74 -74
  197. package/src/compat/next/font/google.ts +2 -2
  198. package/src/compat/next/font/index.ts +1 -1
  199. package/src/compat/next/font/local.ts +3 -3
  200. package/src/compat/next/font/runtime-client.ts +17 -15
  201. package/src/compat/next/font/runtime.ts +443 -408
  202. package/src/compat/next/font/shared.ts +120 -108
  203. package/src/compat/next/form.tsx +63 -63
  204. package/src/compat/next/head.tsx +2 -2
  205. package/src/compat/next/headers.ts +103 -95
  206. package/src/compat/next/image/client.tsx +220 -0
  207. package/src/compat/next/image/config.ts +56 -58
  208. package/src/compat/next/image/optimizer.ts +40 -36
  209. package/src/compat/next/image/patterns.ts +37 -40
  210. package/src/compat/next/{image-props.ts → image/props.ts} +208 -202
  211. package/src/compat/next/image/shared.ts +65 -57
  212. package/src/compat/next/image/static-metadata.ts +98 -107
  213. package/src/compat/next/image/validate.ts +79 -88
  214. package/src/compat/next/image.tsx +19 -23
  215. package/src/compat/next/index.ts +1 -1
  216. package/src/compat/next/legacy-image.tsx +59 -60
  217. package/src/compat/next/{link-validation-transform.ts → link-transform.ts} +95 -96
  218. package/src/compat/next/link.tsx +158 -158
  219. package/src/compat/next/navigation.cjs +12 -3
  220. package/src/compat/next/navigation.ts +48 -50
  221. package/src/compat/next/offline.ts +27 -27
  222. package/src/compat/next/og.ts +121 -124
  223. package/src/compat/next/preferred-region.ts +13 -14
  224. package/src/compat/next/redirects.ts +58 -56
  225. package/src/compat/next/resource-hints.ts +73 -76
  226. package/src/compat/next/rewrites.ts +130 -133
  227. package/src/compat/next/root-params.ts +45 -45
  228. package/src/compat/next/{optimistic-route-state.ts → route-state.ts} +52 -52
  229. package/src/compat/next/router.cjs +4 -2
  230. package/src/compat/next/router.ts +58 -61
  231. package/src/compat/next/script.tsx +108 -108
  232. package/src/compat/next/server-only.ts +1 -1
  233. package/src/compat/next/server.ts +19 -19
  234. package/src/compat/next/svgr.ts +18 -17
  235. package/src/compat/next/telemetry.ts +24 -24
  236. package/src/compat/next/{image-usage.ts → usage.ts} +70 -39
  237. package/src/compat/next/user-agent.ts +53 -49
  238. package/src/compat/next/web-vitals.ts +22 -24
  239. package/src/compat/otel/api.ts +41 -41
  240. package/src/compat/otel/client-trace-metadata.ts +25 -27
  241. package/src/compat/otel/fetch-span.ts +29 -29
  242. package/src/compat/otel/tracer.ts +331 -331
  243. package/src/compat/pages/api.ts +456 -0
  244. package/src/compat/pages/client-plugin.ts +36 -36
  245. package/src/compat/pages/router-state.ts +34 -34
  246. package/src/compat/pages/{index.ts → router.ts} +130 -135
  247. package/src/compat/ppr/io.ts +12 -12
  248. package/src/compat/ppr/missing-root-params.ts +34 -38
  249. package/src/compat/ppr/root-params-scan.ts +66 -66
  250. package/src/compat/ppr/root-params-transform.ts +24 -26
  251. package/src/compat/ppr/root-params.ts +30 -30
  252. package/src/compat/ppr/segment-config-incompat.ts +6 -7
  253. package/src/compat/protocol.ts +71 -70
  254. package/src/compat/react/action-state.ts +47 -48
  255. package/src/compat/react/client-lite.ts +15 -15
  256. package/src/compat/react/client.ts +4 -4
  257. package/src/compat/react/compiler-runtime.ts +11 -11
  258. package/src/compat/react/dom-client.ts +44 -44
  259. package/src/compat/react/dom-react-server.ts +10 -16
  260. package/src/compat/react/dom-server.ts +10 -10
  261. package/src/compat/react/dom.ts +52 -52
  262. package/src/compat/react/hooks-extra.ts +34 -35
  263. package/src/compat/react/parity.ts +64 -61
  264. package/src/compat/react/preact.ts +81 -82
  265. package/src/compat/react/react-server.ts +28 -28
  266. package/src/compat/react/router-shim.ts +1 -1
  267. package/src/compat/react/server-component-use.ts +8 -8
  268. package/src/compat/react/server-inserted-html.ts +30 -31
  269. package/src/compat/react/server.ts +53 -55
  270. package/src/compat/react/use.ts +32 -32
  271. package/src/compat/react/view-transition.ts +20 -20
  272. package/src/compat/register/actions.ts +35 -824
  273. package/src/compat/register/boot.ts +41 -41
  274. package/src/compat/register/build-tier.ts +5 -5
  275. package/src/compat/register/build.ts +74 -70
  276. package/src/compat/register/bundler.ts +161 -156
  277. package/src/compat/register/cache.ts +20 -20
  278. package/src/compat/register/client-errors.ts +3 -3
  279. package/src/compat/register/config.ts +6 -6
  280. package/src/compat/register/css-extras.ts +30 -34
  281. package/src/compat/register/edge-runtime.ts +3 -3
  282. package/src/compat/register/errors.ts +10 -12
  283. package/src/compat/register/export.ts +16 -16
  284. package/src/compat/register/font.ts +11 -11
  285. package/src/compat/register/hooks.ts +2 -2
  286. package/src/compat/register/image.ts +40 -40
  287. package/src/compat/register/index.ts +59 -62
  288. package/src/compat/register/instrumentation-client.ts +10 -10
  289. package/src/compat/register/lifecycle.ts +25 -28
  290. package/src/compat/register/mdx.ts +10 -10
  291. package/src/compat/register/middleware.ts +226 -16
  292. package/src/compat/register/otel.ts +80 -88
  293. package/src/compat/register/pages-api.ts +10 -463
  294. package/src/compat/register/ppr.ts +16 -16
  295. package/src/compat/register/protocol.ts +17 -18
  296. package/src/compat/register/proxy.ts +49 -51
  297. package/src/compat/register/render.ts +79 -76
  298. package/src/compat/register/routing.ts +162 -159
  299. package/src/compat/register/segment.ts +24 -1897
  300. package/src/compat/register/static-image.ts +3 -3
  301. package/src/compat/register/{misc.ts → taint.ts} +9 -9
  302. package/src/compat/register/typed-routes.ts +14 -14
  303. package/src/compat/register/{usecache.ts → use-cache.ts} +39 -39
  304. package/src/compat/register/validation.ts +18 -18
  305. package/src/compat/segment/loading-boundary.ts +43 -45
  306. package/src/compat/segment/page-slot.ts +69 -69
  307. package/src/compat/segment/serve.ts +1884 -0
  308. package/src/compat/segment/tree.ts +113 -112
  309. package/src/compat/segment/vary-key.ts +38 -38
  310. package/src/compat/segment/vary-params.ts +138 -142
  311. package/src/compat/static-params.ts +14 -12
  312. package/src/compat/tsconfig-defaults.ts +87 -91
  313. package/src/compat/typecheck/{index.ts → check.ts} +234 -212
  314. package/src/compat/typecheck/worker.ts +15 -12
  315. package/src/compat/typed-routes/{index.ts → generate.ts} +36 -36
  316. package/src/compat/typed-routes/manifest.ts +174 -170
  317. package/src/compat/typed-routes/typegen.ts +127 -110
  318. package/src/compat/validation/errors.ts +16 -19
  319. package/src/compat/validation/prerender-diagnostics.ts +521 -504
  320. package/src/compat/validation/{index.ts → validate.ts} +647 -648
  321. package/src/compat-bootstrap.ts +16 -16
  322. package/src/config.ts +86 -69
  323. package/src/css/build.ts +230 -227
  324. package/src/css/postcss.ts +79 -80
  325. package/src/css/worker.ts +14 -15
  326. package/src/dev/client-actions.ts +10 -10
  327. package/src/dev/client-chunk-store.ts +27 -27
  328. package/src/dev/{client-key-cache.ts → restart/client-key.ts} +76 -76
  329. package/src/dev/{restart-cache.ts → restart/enabled.ts} +1 -1
  330. package/src/dev/{global-css-cache.ts → restart/global-css.ts} +91 -83
  331. package/src/dev/{node-module-bundle-cache.ts → restart/node-modules.ts} +24 -24
  332. package/src/dev/{route-bundle-key-cache.ts → restart/route-bundle-key.ts} +53 -53
  333. package/src/dev/{route-facts-cache.ts → restart/route-facts.ts} +82 -82
  334. package/src/dev/server.ts +804 -820
  335. package/src/env.ts +46 -43
  336. package/src/extensions.ts +491 -478
  337. package/src/index.ts +8 -8
  338. package/src/internal.ts +20 -23
  339. package/src/{islands → render}/boundary-error.ts +3 -3
  340. package/src/render/hooks.ts +71 -71
  341. package/src/render/island-context.ts +14 -14
  342. package/src/render/metadata.ts +310 -310
  343. package/src/{ppr-postpone.ts → render/postpone.ts} +5 -5
  344. package/src/{ppr.ts → render/ppr.ts} +244 -245
  345. package/src/render/renderer.ts +2087 -2082
  346. package/src/render/resource-hints.ts +16 -17
  347. package/src/render/slots.tsx +224 -235
  348. package/src/{islands → render}/static-children.ts +9 -12
  349. package/src/{islands → render}/static-slots.ts +37 -37
  350. package/src/{cache/context.ts → request/cache.ts} +20 -20
  351. package/src/request/context.ts +107 -107
  352. package/src/{dynamic/source.ts → resolve/dynamic.ts} +139 -139
  353. package/src/resolve/engine.ts +90 -77
  354. package/src/resolve/imports.ts +475 -463
  355. package/src/resolve/scan-facts.ts +318 -296
  356. package/src/resolve/source-text.ts +37 -37
  357. package/src/{dynamic → resolve}/tree-shake.ts +132 -128
  358. package/src/routing/forwarded.ts +19 -19
  359. package/src/routing/handler.ts +84 -91
  360. package/src/routing/href.ts +87 -70
  361. package/src/routing/{metadata.ts → metadata-files.ts} +403 -401
  362. package/src/{proxy.ts → routing/proxy.ts} +306 -312
  363. package/src/routing/{request-runtime.ts → request-environment.ts} +10 -10
  364. package/src/routing/routes.ts +827 -815
  365. package/src/routing/slots.ts +164 -160
  366. package/src/runtime/loader.ts +954 -0
  367. package/src/{dev → runtime}/module-cache.ts +309 -287
  368. package/src/{dev → runtime}/module-generations.ts +9 -9
  369. package/src/{dev → runtime}/module-transform.ts +81 -72
  370. package/src/{dev/imports.ts → runtime/modules.ts} +934 -847
  371. package/src/runtime/{server.ts → vendor-build.ts} +848 -1696
  372. package/src/runtime/vendor.ts +428 -406
  373. package/src/styles.d.ts +9 -0
  374. package/src/types.ts +324 -335
  375. package/src/utils/ansi.ts +5 -5
  376. package/src/utils/{source.ts → code.ts} +15 -12
  377. package/src/utils/content-type.ts +3 -3
  378. package/src/utils/decode.ts +2 -2
  379. package/src/utils/dev-profile.ts +13 -13
  380. package/src/utils/error-log.ts +6 -6
  381. package/src/utils/esbuild.ts +18 -18
  382. package/src/utils/fs-cache.ts +13 -13
  383. package/src/utils/fs.ts +57 -49
  384. package/src/utils/html.ts +20 -24
  385. package/src/utils/native-require.ts +8 -8
  386. package/src/utils/serialize.ts +139 -146
  387. package/src/utils/verbose.ts +18 -18
  388. package/reference/overview.md +0 -35
  389. package/src/cli/analyze-print.ts +0 -181
  390. package/src/compat/middleware/manifest.ts +0 -210
  391. package/src/compat/next/image-client.tsx +0 -215
  392. package/src/compat/next/link-usage.ts +0 -29
  393. package/src/css/index.ts +0 -2
  394. package/src/render/index.ts +0 -1
  395. package/src/style-modules.d.ts +0 -9
@@ -1,65 +1,54 @@
1
- import { createHash } from 'node:crypto';
2
- import { builtinModules, createRequire } from 'node:module';
3
- import { existsSync, readFileSync, realpathSync, statSync } from 'node:fs';
4
- import { copyFile, mkdir, readFile, rm, stat, symlink, writeFile } from 'node:fs/promises';
5
- import path from 'node:path';
6
- import { fileURLToPath } from 'node:url';
7
- import type { Loader, Metafile, OnLoadResult, OnResolveResult, Plugin } from 'esbuild';
8
- import { build, transform } from '../utils/esbuild';
1
+ /**
2
+ * Vendor package bundling: compiling a server module's node_modules externals into artifacts
3
+ * loadable at request/build time. Covers the vendor artifact plan and cache-key scheme, the
4
+ * content/link/share layers used to reuse compiled bytes across the RSC and client-compat
5
+ * targets, preplan and native-Bun-loadable fast paths for packages that need no rewriting, the
6
+ * plugin-driven esbuild bundling of a vendor group (and its CommonJS-export-recovery/native
7
+ * fallback rounds), and the resolve-time plugins (external dependency, package-shape) that feed
8
+ * those builds. The demand-scheduling pipeline itself lives in `./vendor`; the ESM loader - and
9
+ * the alias/asset helpers this module reuses from it - lives in `./server`.
10
+ */
11
+
12
+ import { builtinModules, createRequire } from 'node:module'
13
+ import { existsSync, readFileSync, statSync } from 'node:fs'
14
+ import { copyFile, mkdir, readFile, rm, symlink, writeFile } from 'node:fs/promises'
15
+ import path from 'node:path'
16
+ import { fileURLToPath } from 'node:url'
17
+ import type { Loader, Metafile, OnLoadResult, OnResolveResult, Plugin } from 'esbuild'
18
+ import { build } from '../utils/esbuild'
9
19
  import {
10
20
  applyBundledSourceTransforms,
11
- applyServerSourcePreTransforms,
12
- applyServerSourceTransforms,
13
- extraLoadableExtensions,
14
21
  getAssetExtensions,
15
22
  getBundlerExtensions,
16
23
  getCssExtensions,
17
24
  getImportAliasExtensions,
18
- onLoadableExtensionsChanged,
19
25
  serverDefineOptions,
20
- sourceNeedsServerTransforms,
21
26
  type ServerEsbuildPluginOptions,
22
- } from '../extensions';
23
- import {
24
- frameworkRuntimeAliasEntries,
25
- pathToFileHref,
26
- pnextAliases,
27
- type CompatAliasTarget,
28
- type ResolvedConfig,
29
- } from '../config';
30
- import {
31
- rewriteDynamicCallTargets,
32
- rewriteLiteralDynamicCalls,
33
- sourceHasDynamicImport,
34
- } from '../dynamic/source';
35
- import { cacheRoot, devArtifactUsable, noteDevArtifactWritten } from '../dev/module-cache';
36
- import { readNodeModuleBundle, writeNodeModuleBundle } from '../dev/node-module-bundle-cache';
37
- import { setBasePathPrefix, setTrailingSlashUrls } from '../routing/href';
27
+ } from '../extensions'
28
+ import { pathToFileHref, type CompatAliasTarget, type ResolvedConfig } from '../config'
29
+ import { devArtifactUsable, noteDevArtifactWritten } from './module-cache'
38
30
  import {
39
31
  getExternalPackagePolicy,
40
32
  isEsmModuleEntry,
41
33
  isEsmModuleFile,
42
34
  isPackageSubpathUnexported,
43
- resolveExternalLoadTarget,
44
- resolveImport,
45
35
  resolveLinkedPackageSpecifier,
46
36
  resolveNestedPackageFromImporter,
47
37
  resolveVendorPackageSpecifier,
48
38
  clearVendorPackageResolutions,
49
39
  clearProvidedEntryResolutions,
50
40
  provideEntryResolution,
51
- workspacePackageRoots,
52
- } from '../resolve/imports';
53
- import { clearNodeModulesResolutionCache } from '../resolve/engine';
54
- import { cachedExistsSync, clearNodeModulesFsCache } from '../utils/fs-cache';
41
+ } from '../resolve/imports'
42
+ import { clearNodeModulesResolutionCache } from '../resolve/engine'
43
+ import { cachedExistsSync, clearNodeModulesFsCache } from '../utils/fs-cache'
55
44
  import {
56
45
  clientReferenceExportNames,
57
46
  clientReferenceModuleSource,
58
47
  hasUseClientDirective,
59
- } from '../client/reference-stub';
60
- import { reactCompatEnabled } from '../render/hooks';
61
- import { escapeRegex } from '../utils/source';
62
- import { writeFileAtomic } from '../utils/fs';
48
+ } from '../client/reference-stub'
49
+ import { reactCompatEnabled } from '../render/hooks'
50
+ import { writeFileAtomic } from '../utils/fs'
51
+ import { formatDuration } from '../utils/verbose'
63
52
  import {
64
53
  addCommonJsNamedExports,
65
54
  canonicalVendorCode,
@@ -84,652 +73,44 @@ import {
84
73
  type VendorBuildPlan,
85
74
  type VendorGroupMember,
86
75
  type VendorGroupPlan,
87
- } from './vendor';
88
-
89
- const require = createRequire(import.meta.url);
90
-
91
- // B4/B5: extra esbuild resolve conditions layered onto the server (RSC) vendor
92
- // bundle. Core always applies `react-server` (the server graph IS the RSC
93
- // layer). Compat can add `next-js` (only when cacheComponents is on) via the
94
- // setter. Kept as a module-level seam so core carries no static edge into
95
- // compat's config reader.
96
- export type ServerBundleTarget =
97
- | CompatAliasTarget
98
- | 'edge'
99
- | 'pages-api'
100
- | 'pages-api-edge'
101
- | 'pages'
102
- | 'pages-edge';
103
-
104
- let extraServerBundleConditions: (target: ServerBundleTarget) => string[] = () => [];
105
-
106
- /** Install compat-driven extra vendor-bundle conditions (B5 `next-js`). */
107
- export function setServerBundleConditions(
108
- factory: ((target: ServerBundleTarget) => string[]) | undefined,
109
- ): void {
110
- extraServerBundleConditions = factory ?? (() => []);
111
- }
112
-
113
- export function serverBundleTargetForRuntime(runtime: string | undefined): ServerBundleTarget {
114
- return runtime === 'edge' || runtime === 'experimental-edge' ? 'edge' : 'server';
115
- }
116
-
117
- export function pagesApiBundleTargetForRuntime(runtime: string | undefined): ServerBundleTarget {
118
- return runtime === 'edge' || runtime === 'experimental-edge' ? 'pages-api-edge' : 'pages-api';
119
- }
120
-
121
- /**
122
- * Pages-router PAGE SSR layer: Next compiles it in the server bundle with `node` (or
123
- * `edge-light`/`browser` for edge) export conditions but WITHOUT `react-server` - a pages page is
124
- * not part of the RSC layer, so `library/react`-style react-server splits must resolve to `default`.
125
- */
126
- export function pagesBundleTargetForRuntime(runtime: string | undefined): ServerBundleTarget {
127
- return runtime === 'edge' || runtime === 'experimental-edge' ? 'pages-edge' : 'pages';
128
- }
129
-
130
- function serverBundleConditions(target: ServerBundleTarget): string[] {
131
- // Preserve the original client-target conditions (module/import) to avoid
132
- // regressing the browser SSR vendor bundle; only the RSC/server layer gains
133
- // `react-server`. Compat-added conditions (next-js) prepend for either target.
134
- const base =
135
- target === 'client'
136
- ? ['module', 'import']
137
- : target === 'edge'
138
- ? ['react-server', 'edge-light', 'browser', 'module', 'import']
139
- : target === 'pages-api-edge'
140
- ? ['edge-light', 'browser', 'module', 'import']
141
- : target === 'pages-api'
142
- ? ['node', 'require', 'import']
143
- : target === 'pages-edge'
144
- ? ['edge-light', 'browser', 'module', 'import']
145
- : target === 'pages'
146
- ? ['node', 'module', 'import']
147
- : ['react-server', 'node', 'module', 'import'];
148
- return [...new Set([...extraServerBundleConditions(target), ...base])];
149
- }
150
-
151
- export function serverBundleRequireConditions(target: ServerBundleTarget): string[] {
152
- const base =
153
- target === 'client'
154
- ? ['module', 'require']
155
- : target === 'edge'
156
- ? ['react-server', 'edge-light', 'browser', 'module', 'require']
157
- : target === 'pages-api-edge'
158
- ? ['edge-light', 'browser', 'module', 'require']
159
- : target === 'pages-api'
160
- ? ['node', 'require']
161
- : target === 'pages-edge'
162
- ? ['edge-light', 'browser', 'module', 'require']
163
- : target === 'pages'
164
- ? ['node', 'module', 'require']
165
- : ['react-server', 'node', 'module', 'require'];
166
- return [...new Set([...extraServerBundleConditions(target), ...base])];
167
- }
168
-
169
- const runtimeConfigs = new Map<string, RuntimeConfig>();
170
- const aliasCache = new WeakMap<ResolvedConfig, Map<string, string>>();
171
- const packageRootCache = new WeakMap<ResolvedConfig, string[]>();
172
- const registeredSignatures = new WeakMap<ResolvedConfig, Set<string>>();
173
- const rootPathCache = new Map<string, string[]>();
174
- const runtimeConfigLookupCache = new Map<string, RuntimeConfig | null>();
175
- const firstAliasCache = new Map<string, string | null>();
176
- const transformCache = new Map<string, TransformCacheEntry>();
177
- const nodeModuleTransformCache = new Map<string, TransformCacheEntry>();
178
- // Freshness of generated `.pnext-require.cjs` sidecars (see inlineModuleScopeRequires).
179
- const requireSidecarCache = new Map<string, { mtimeMs: number; size: number }>();
180
- const registeredLoadRoots = new Set<string>();
181
- let resolveRegistered = false;
182
- let nodeModulesLoadRegistered = false;
183
- // A live Bun.plugin forces every later import through the plugin pipeline (and
184
- // pnext's own src through transformSource), so importing the compat graph with
185
- // the plugins already up costs ~5x. build/start load compat before they ever
186
- // register a runtime; dev arms the plugins on the first request instead.
187
- let pluginsDeferred = false;
188
- const deferredPluginRoots = new Set<string>();
189
-
190
- const packageJsxLoaders = {
191
- '.js': 'jsx',
192
- '.mjs': 'jsx',
193
- '.cjs': 'jsx',
194
- } satisfies Record<string, Loader>;
195
-
196
- interface RuntimeConfig {
197
- root: string;
198
- roots: Set<string>;
199
- aliases: Map<string, string>;
200
- /** Full resolved config, so compile sites can invoke config-aware extensions. */
201
- resolved: ResolvedConfig;
202
- missingImportError: (specifier: string) => string | undefined;
203
- }
204
-
205
- interface TransformCacheEntry {
206
- mtimeMs: number;
207
- size: number;
208
- code: string;
209
- }
210
-
211
- export function registerServerRuntime(config: ResolvedConfig, sourceFiles: string[] = []) {
212
- // Server-rendered Links emit canonical (slashed) hrefs under trailingSlash.
213
- setTrailingSlashUrls(Boolean(config.trailingSlash));
214
- // File-convention metadata asset hrefs (og-image, manifest) carry the
215
- // basePath prefix; core render reads it through the href seam.
216
- setBasePathPrefix(typeof config.basePath === 'string' ? config.basePath : '');
217
- if (typeof Bun === 'undefined') return;
218
- const sourceRoots = [...new Set(sourceFiles.map(file => sourceRootForFile(config, file)))].sort();
219
- const signature = sourceRoots.join('\0');
220
- let registered = registeredSignatures.get(config);
221
- if (!registered) {
222
- registered = new Set();
223
- registeredSignatures.set(config, registered);
224
- }
225
- if (registered.has(signature)) return;
226
- registered.add(signature);
227
-
228
- const aliases = aliasesForConfig(config);
229
- const roots = [
230
- ...rootPaths(config.appPath),
231
- ...rootPaths(path.join(config.outPath, 'cache', 'server')),
232
- // The pnext src root, so the framework's own source transforms too.
233
- ...rootPaths(path.join(import.meta.dirname, '..')),
234
- ...sourceRoots.flatMap(rootPaths),
235
- ];
236
- const key = rootPaths(config.appPath).join('\0');
237
- const missingImportError = (specifier: string) =>
238
- getImportAliasExtensions().missingImportError(config, specifier);
239
- const existing = runtimeConfigs.get(key);
240
- if (existing) {
241
- for (const root of roots) existing.roots.add(root);
242
- for (const [specifier, target] of aliases) existing.aliases.set(specifier, target);
243
- existing.resolved = config;
244
- existing.missingImportError = missingImportError;
245
- } else {
246
- runtimeConfigs.set(key, {
247
- root: config.root,
248
- roots: new Set(roots),
249
- aliases: new Map(aliases),
250
- resolved: config,
251
- missingImportError,
252
- });
253
- }
254
- runtimeConfigLookupCache.clear();
255
- firstAliasCache.clear();
256
- if (pluginsDeferred) {
257
- for (const root of roots) deferredPluginRoots.add(root);
258
- return;
259
- }
260
- registerResolvePlugin();
261
- registerNodeModulesLoadPlugin();
262
- for (const root of roots) registerLoadPlugin(root);
263
- }
264
-
265
- /**
266
- * Hold the Bun module plugins back until installServerRuntimePlugins() runs, so
267
- * whatever the process still has to import of its own code loads natively.
268
- */
269
- export function deferServerRuntimePlugins(): void {
270
- pluginsDeferred = true;
271
- }
272
-
273
- /** Arm the plugins deferServerRuntimePlugins() held back. Idempotent, cheap. */
274
- export function installServerRuntimePlugins(): void {
275
- if (!pluginsDeferred) return;
276
- pluginsDeferred = false;
277
- registerResolvePlugin();
278
- registerNodeModulesLoadPlugin();
279
- for (const root of deferredPluginRoots) registerLoadPlugin(root);
280
- deferredPluginRoots.clear();
281
- }
76
+ } from './vendor'
77
+ import {
78
+ aliasSpecifierFilter,
79
+ coreAliases,
80
+ firstAliasForSpecifier,
81
+ hashBundleSpecifier,
82
+ packageJsxLoaders,
83
+ runtimeAliasBuildPlugin,
84
+ serverAssetPlugin,
85
+ serverBundleConditions,
86
+ serverBundleRequireConditions,
87
+ serverIgnoredAssetFilter,
88
+ serverRequireAlias,
89
+ vendorLoadsNatively,
90
+ type ServerBundleTarget,
91
+ } from './loader'
92
+
93
+ const require = createRequire(import.meta.url)
282
94
 
283
95
  export function clearServerRuntimeCaches() {
284
- clearVendorPipeline();
285
- clearVendorNativeCaches();
286
- vendorEntryResolutions.clear();
287
- clearVendorPackageResolutions();
288
- vendorLayerRecords.clear();
289
- vendorSharedEntries.clear();
290
- vendorSharedLoaded.clear();
291
- vendorReuseVerdicts.clear();
292
- vendorReuseLoaded.clear();
293
- vendorReuseProbes.clear();
294
- vendorLayerProbeMemo.clear();
96
+ clearVendorPipeline()
97
+ clearVendorNativeCaches()
98
+ vendorEntryResolutions.clear()
99
+ clearVendorPackageResolutions()
100
+ vendorLayerRecords.clear()
101
+ vendorSharedEntries.clear()
102
+ vendorSharedLoaded.clear()
103
+ vendorReuseVerdicts.clear()
104
+ vendorReuseLoaded.clear()
105
+ vendorReuseProbes.clear()
106
+ vendorLayerProbeMemo.clear()
295
107
  // The marker memo tracks files under `.pnext`; a wipe takes them with it.
296
- esmDistPackages.clear();
297
- clearProvidedEntryResolutions();
298
- clearNodeModulesResolutionCache();
299
- clearNodeModulesFsCache();
300
- }
301
-
302
- function resolveRuntimeTarget(target: string) {
303
- return path.isAbsolute(target) ? target : require.resolve(target);
304
- }
305
-
306
- function aliasesForConfig(config: ResolvedConfig) {
307
- let aliases = aliasCache.get(config);
308
- if (!aliases) {
309
- aliases = new Map<string, string>();
310
- for (const [specifier, target] of Object.entries(coreAliases(config, 'server'))) {
311
- aliases.set(specifier, resolveRuntimeTarget(target));
312
- }
313
- aliasCache.set(config, aliases);
314
- }
315
- return aliases;
316
- }
317
-
318
- function coreAliases(config: ResolvedConfig, target: CompatAliasTarget): Record<string, string> {
319
- return {
320
- ...frameworkRuntimeAliasEntries(),
321
- ...pnextAliases(target),
322
- ...getImportAliasExtensions().aliases(config, target),
323
- };
324
- }
325
-
326
- const aliasSpecifierFilter =
327
- /^(?:@wular\/pnext\/cache|preact(?:\/|$)|react(?:-dom)?(?:\/|$)|react-compiler-runtime$|next(?:\/|$)|server-only$|client-only$|@vercel\/og$)/;
328
-
329
- function registerResolvePlugin() {
330
- if (resolveRegistered) return;
331
- resolveRegistered = true;
332
- Bun.plugin({
333
- name: 'pnext-server-runtime-resolve',
334
- setup(plugin) {
335
- plugin.onResolve({ filter: aliasSpecifierFilter }, args => {
336
- const runtime = runtimeConfigForFile(args.importer);
337
- const resolved = runtime?.aliases.get(args.path) ?? firstAliasForSpecifier(args.path);
338
- const target = resolved ? serverRequireAlias(args.path, resolved, args.kind) : undefined;
339
- if (target) return { path: target };
340
- const message = (runtime ?? firstRuntimeConfig())?.missingImportError(args.path);
341
- if (message) throw new Error(message);
342
- return undefined;
343
- });
344
- plugin.onResolve({ filter: /^[^./].*/ }, ({ path: specifier, importer }) => {
345
- const runtime = runtimeConfigForFile(importer);
346
- const target = runtime && resolveExternalLoadTarget({
347
- root: runtime.root,
348
- fromFile: importer,
349
- specifier,
350
- target: 'server',
351
- });
352
- if (!target) return undefined;
353
- return { path: target };
354
- });
355
- },
356
- });
357
- }
358
-
359
- function registerLoadPlugin(root: string) {
360
- if (registeredLoadRoots.has(root)) return;
361
- registeredLoadRoots.add(root);
362
- // eslint-disable-next-line turbo/no-undeclared-env-vars
363
- if (process.env.PNEXT_DEBUG_LOADPLUGIN) {
364
- console.error(`[loadplugin] register root=${root} extras=${currentLoadExtras().join(',')}`);
365
- }
366
- loadPluginExtras.set(root, currentLoadExtras());
367
- Bun.plugin({
368
- name: `pnext-server-runtime-load-${hashRoot(root)}`,
369
- setup(plugin) {
370
- plugin.onLoad({ filter: rootFilter(root) }, async ({ path: file }) => ({
371
- contents: await transformSource(file),
372
- loader: 'js',
373
- }));
374
- },
375
- });
376
- }
377
-
378
- // The load plugin's filter regex latches the page extensions known at registration time. A root registered
379
- // BEFORE compat adds `mdx`/`md` (boot order shifts under load) would let those modules fall through to Bun's
380
- // file loader, whose default export is the file path and renders as a bogus JSX tag - so a late registration
381
- // extends every latched root with an extras-only plugin.
382
- const loadPluginExtras = new Map<string, string[]>();
383
-
384
- function currentLoadExtras(): string[] {
385
- return extraLoadableExtensions().filter(ext => /^[a-z0-9]+$/i.test(ext));
386
- }
387
-
388
- onLoadableExtensionsChanged(() => {
389
- for (const [root, seen] of loadPluginExtras) {
390
- const fresh = currentLoadExtras().filter(ext => !seen.includes(ext));
391
- if (fresh.length === 0) continue;
392
- loadPluginExtras.set(root, [...seen, ...fresh]);
393
- const exts = fresh.map(escapeRegex).join('|');
394
- const filter = new RegExp(
395
- `^${escapeRegex(root)}(?!.*\\/node_modules\\/)(?:/.*)?\\.(?:${exts})$`,
396
- );
397
- Bun.plugin({
398
- name: `pnext-server-runtime-load-extras-${hashRoot(root)}-${fresh.join('-')}`,
399
- setup(plugin) {
400
- plugin.onLoad({ filter }, async ({ path: file }) => ({
401
- contents: await transformSource(file),
402
- loader: 'js',
403
- }));
404
- },
405
- });
406
- }
407
- });
408
-
409
- function registerNodeModulesLoadPlugin() {
410
- if (nodeModulesLoadRegistered) return;
411
- nodeModulesLoadRegistered = true;
412
- Bun.plugin({
413
- name: 'pnext-server-node-modules-load',
414
- setup(plugin) {
415
- plugin.onLoad(
416
- {
417
- filter:
418
- /\/node_modules\/.*(?:\.mjs|\.esm\.js|\/(?:es|esm|dist\/esm|build\/modern)\/.*\.js)$/,
419
- },
420
- async ({ path: file }) => {
421
- const code = await transformNodeModuleSource(file);
422
- return { contents: code, loader: 'js' };
423
- },
424
- );
425
- },
426
- });
427
- }
428
-
429
- function runtimeConfigForFile(file: string | undefined) {
430
- if (!file) return undefined;
431
- const key = path.resolve(file);
432
- if (runtimeConfigLookupCache.has(key)) {
433
- return runtimeConfigLookupCache.get(key) ?? undefined;
434
- }
435
- for (const config of runtimeConfigs.values()) {
436
- for (const root of config.roots) {
437
- if (isInside(root, file)) {
438
- runtimeConfigLookupCache.set(key, config);
439
- return config;
440
- }
441
- }
442
- }
443
- runtimeConfigLookupCache.set(key, null);
444
- return undefined;
108
+ esmDistPackages.clear()
109
+ clearProvidedEntryResolutions()
110
+ clearNodeModulesResolutionCache()
111
+ clearNodeModulesFsCache()
445
112
  }
446
113
 
447
- function sourceRootForFile(config: ResolvedConfig, file: string) {
448
- const dir = path.dirname(file);
449
- if (isInside(config.appPath, dir)) return config.appPath;
450
- return packageRootsForConfig(config).find(root => isInside(root, dir)) ?? dir;
451
- }
452
-
453
- function packageRootsForConfig(config: ResolvedConfig) {
454
- let roots = packageRootCache.get(config);
455
- if (!roots) {
456
- roots = workspacePackageRoots(config.workspaceRoot);
457
- packageRootCache.set(config, roots);
458
- }
459
- return roots;
460
- }
461
-
462
- function firstRuntimeConfig() {
463
- for (const config of runtimeConfigs.values()) return config;
464
- return undefined;
465
- }
466
-
467
- function firstAliasForSpecifier(specifier: string) {
468
- if (firstAliasCache.has(specifier)) return firstAliasCache.get(specifier) ?? undefined;
469
- for (const config of runtimeConfigs.values()) {
470
- const target = config.aliases.get(specifier);
471
- if (target) {
472
- firstAliasCache.set(specifier, target);
473
- return target;
474
- }
475
- }
476
- firstAliasCache.set(specifier, null);
477
- return undefined;
478
- }
479
-
480
- async function transformSource(file: string) {
481
- const fileStat = await stat(file);
482
- const cached = transformCache.get(file);
483
- if (cached?.mtimeMs === fileStat.mtimeMs && cached.size === fileStat.size) {
484
- return cached.code;
485
- }
486
- // The in-memory cache is empty on every restart, so a restarted server
487
- // re-transforms the whole materialized module set on the request path.
488
- const diskCache = transformCacheFile(file, fileStat);
489
- if (diskCache) {
490
- const code = await readFile(diskCache, 'utf8').catch(() => undefined);
491
- if (code !== undefined) {
492
- transformCache.set(file, { mtimeMs: fileStat.mtimeMs, size: fileStat.size, code });
493
- return code;
494
- }
495
- }
496
-
497
- const runtime = runtimeConfigForFile(file);
498
- let raw = await readFile(file, 'utf8');
499
- // Raw markdown compiles through the MDX pipeline first; materialized
500
- // cache/server copies were already compiled in place (dev/imports) and would
501
- // be corrupted by a second pass.
502
- if (/\.mdx?$/.test(file) && !file.includes(`${path.sep}cache${path.sep}server${path.sep}`)) {
503
- const { compileMdx } = await import('../compat/mdx/compile');
504
- raw = (await compileMdx(raw, file, runtime?.root ?? path.dirname(file))).code;
505
- }
506
- const source = await inlineModuleScopeRequires(
507
- rewriteServerSource(raw, file, {
508
- root: runtime?.root,
509
- }),
510
- file,
511
- runtime?.root,
512
- );
513
- const result = await transform(source, {
514
- loader: esbuildLoader(file),
515
- jsx: 'automatic',
516
- jsxImportSource: 'preact',
517
- sourcefile: file,
518
- target: 'es2022',
519
- ...serverDefineOptions(),
520
- });
521
- transformCache.set(file, {
522
- mtimeMs: fileStat.mtimeMs,
523
- size: fileStat.size,
524
- code: result.code,
525
- });
526
- if (diskCache) {
527
- await mkdir(path.dirname(diskCache), { recursive: true }).catch(() => undefined);
528
- void writeFileAtomic(diskCache, result.code).catch(() => undefined);
529
- }
530
- return result.code;
531
- }
532
-
533
- // eslint-disable-next-line turbo/no-undeclared-env-vars
534
- const transformDiskCacheDisabled = process.env.PNEXT_TRANSFORM_DISK_CACHE === '0';
535
- const materializedSegment = `${path.sep}cache${path.sep}server${path.sep}`;
536
-
537
- /**
538
- * Where a transform of `file` is remembered across restarts. Only for the
539
- * materialized `cache/server` modules: they are hash-named build output under
540
- * our own cache root, so the key (path + mtime + size + the define/extension
541
- * inputs of the transform) is complete, and the whole tree is discarded when
542
- * a build or a config change rewrites it.
543
- */
544
- function transformCacheFile(file: string, fileStat: { mtimeMs: number; size: number }) {
545
- if (transformDiskCacheDisabled) return undefined;
546
- const at = file.lastIndexOf(materializedSegment);
547
- if (at === -1) return undefined;
548
- const cacheRoot = file.slice(0, at + materializedSegment.length);
549
- const key = `${file}\0${fileStat.mtimeMs}\0${fileStat.size}\0${currentLoadExtras().join(',')}\0${JSON.stringify(serverDefineOptions())}`;
550
- return path.join(cacheRoot, 'transform', `${hashBundleSpecifier(key)}.js`);
551
- }
552
-
553
- // A synchronous `require('./x')` of an app source module fails at runtime with `require() async
554
- // module ... is unsupported`: the load plugin's onLoad hook makes every app `[jt]sx?` module an
555
- // ASYNC Bun module, and Bun cannot `require` one. Bun does not route relative `require-call`s
556
- // through onResolve plugins (only bare specifiers reach them), so the alias trick cannot intercept
557
- // them at resolve time. Instead each such require is rewritten to a sibling
558
- // `<x>.pnext-require.cjs` compiled to CommonJS: a real `.cjs` on disk is deliberately excluded by
559
- // rootFilter, so Bun loads it natively and synchronously.
560
- //
561
- // Only requires that resolve to a rootFilter-matched app module are rewritten (a
562
- // `.cjs`/`.json`/node_modules require already loads synchronously). The sidecar is a distinct module
563
- // instance from the module's ESM copy - acceptable here, since the require path was an outright
564
- // crash before and such modules are typically leaf value modules.
565
- //
566
- // Both the source form and the BUILT form are matched: the server bundle keeps such a require
567
- // unbundled and emits esbuild's `__require("file:///abs/path/...")` shim call, which loads the very
568
- // same async app module at runtime.
569
- const REQUIRE_CALL = /\b(?:__)?require\(\s*(['"])((?:\.\.?\/|\/|file:\/\/)[^'"]+)\1\s*\)/g;
570
-
571
- async function inlineModuleScopeRequires(
572
- source: string,
573
- file: string,
574
- root: string | undefined,
575
- ): Promise<string> {
576
- if (!root || !source.includes('require(')) return source;
577
- const filter = rootFilter(root);
578
- const sidecars = new Map<string, string>();
579
- const pending: Promise<void>[] = [];
580
- const seen = new Set<string>();
581
- for (const match of source.matchAll(REQUIRE_CALL)) {
582
- const specifier = match[2]!;
583
- if (seen.has(specifier)) continue;
584
- seen.add(specifier);
585
- const target = requireCallTarget(root, file, specifier);
586
- if (!target || !filter.test(target)) continue;
587
- pending.push(
588
- requireCjsSidecar(target).then(sidecar => {
589
- if (sidecar) sidecars.set(specifier, sidecar);
590
- }),
591
- );
592
- }
593
- if (pending.length === 0) return source;
594
- await Promise.all(pending);
595
- if (sidecars.size === 0) return source;
596
- return source.replace(REQUIRE_CALL, (whole, quote: string, specifier: string) => {
597
- const sidecar = sidecars.get(specifier);
598
- return sidecar ? `require(${quote}${sidecar}${quote})` : whole;
599
- });
600
- }
601
-
602
- /** The module a rewritable `require(...)` call resolves to, if any. */
603
- function requireCallTarget(root: string, file: string, specifier: string): string | undefined {
604
- if (specifier.startsWith('file://')) {
605
- try {
606
- return resolveImport(root, file, fileURLToPath(specifier));
607
- } catch {
608
- return undefined;
609
- }
610
- }
611
- return resolveImport(root, file, specifier);
612
- }
613
-
614
- // Compile an app module to a sibling `.pnext-require.cjs` (CommonJS), reusing a
615
- // mtime/size-cached copy. Returns the sidecar path, or undefined on any failure
616
- // (the original require is then left as-is). The module runs through the same
617
- // server source pipeline as a normal load so the CJS copy behaves like the ESM
618
- // one; nested module-scope requires inside it are NOT recursively inlined.
619
- async function requireCjsSidecar(target: string): Promise<string | undefined> {
620
- try {
621
- const sidecar = requireCjsSidecarPath(target);
622
- const fileStat = await stat(target);
623
- const cached = requireSidecarCache.get(target);
624
- if (cached?.mtimeMs === fileStat.mtimeMs && cached.size === fileStat.size) return sidecar;
625
- const runtime = runtimeConfigForFile(target);
626
- const source = rewriteServerSource(await readFile(target, 'utf8'), target, {
627
- root: runtime?.root,
628
- });
629
- const result = await transform(source, {
630
- loader: esbuildLoader(target),
631
- jsx: 'automatic',
632
- jsxImportSource: 'preact',
633
- sourcefile: target,
634
- target: 'es2022',
635
- format: 'cjs',
636
- ...serverDefineOptions(),
637
- });
638
- await writeFile(sidecar, result.code);
639
- requireSidecarCache.set(target, { mtimeMs: fileStat.mtimeMs, size: fileStat.size });
640
- return sidecar;
641
- } catch {
642
- return undefined;
643
- }
644
- }
645
-
646
- function requireCjsSidecarPath(target: string): string {
647
- return `${target.replace(/\.[^./\\]+$/, '')}.pnext-require.cjs`;
648
- }
649
-
650
- async function transformNodeModuleSource(file: string) {
651
- const fileStat = await stat(file);
652
- const cached = nodeModuleTransformCache.get(file);
653
- if (cached?.mtimeMs === fileStat.mtimeMs && cached.size === fileStat.size) {
654
- return cached.code;
655
- }
656
- // The in-memory cache is empty on every restart, so a restarted server
657
- // re-BUNDLES each of these packages on the request path.
658
- const record = nodeModuleBundleRecordFile(file, fileStat);
659
- const reused = record ? await readNodeModuleBundle(record) : undefined;
660
- if (reused !== undefined) {
661
- nodeModuleTransformCache.set(file, { mtimeMs: fileStat.mtimeMs, size: fileStat.size, code: reused });
662
- return reused;
663
- }
664
-
665
- const { code, inputs } = await bundleNodeModuleSource(file);
666
- nodeModuleTransformCache.set(file, {
667
- mtimeMs: fileStat.mtimeMs,
668
- size: fileStat.size,
669
- code,
670
- });
671
- if (record) void writeNodeModuleBundle(record, inputs, code);
672
- return code;
673
- }
674
-
675
- /**
676
- * Where a bundle of `file` is remembered across restarts. The entry's own stat is in the name, but
677
- * the build inlines the package's relative graph too, so the record carries every esbuild input and
678
- * is only reused while all of them still stat the same. A record that cannot be validated is a miss.
679
- */
680
- function nodeModuleBundleRecordFile(file: string, fileStat: { mtimeMs: number; size: number }) {
681
- if (transformDiskCacheDisabled) return undefined;
682
- const outPath = (runtimeConfigForFile(file) ?? firstRuntimeConfig())?.resolved.outPath;
683
- if (!outPath) return undefined;
684
- const key = `${file}\0${fileStat.mtimeMs}\0${fileStat.size}\0${currentLoadExtras().join(',')}\0${JSON.stringify(serverDefineOptions())}`;
685
- return path.join(cacheRoot(outPath), 'node-module-bundle', `${hashBundleSpecifier(key)}.json`);
686
- }
687
-
688
- async function bundleNodeModuleSource(file: string) {
689
- const resolved = runtimeConfigForFile(file)?.resolved;
690
- const extensionPlugins = resolved ? getBundlerExtensions().serverEsbuildPlugins(resolved) : [];
691
- const result = await build({
692
- // The marker entry only resolves when the extension chain is present.
693
- entryPoints: [
694
- (resolved
695
- ? getBundlerExtensions().serverBundleEntry(file, path.dirname(file), file)
696
- : undefined) ?? file,
697
- ],
698
- bundle: true,
699
- write: false,
700
- format: 'esm',
701
- platform: 'neutral',
702
- target: 'es2022',
703
- loader: packageJsxLoaders,
704
- jsx: 'automatic',
705
- jsxImportSource: 'preact',
706
- packages: 'external',
707
- logLevel: 'silent',
708
- metafile: true,
709
- ...serverDefineOptions(),
710
- plugins: [
711
- serverAssetPlugin(),
712
- ...extensionPlugins,
713
- runtimeAliasBuildPlugin(undefined, 'server', { bundleRequireAliases: true }),
714
- ],
715
- });
716
- const output = result.outputFiles[0];
717
- if (!output) throw new Error(`Failed to bundle ${file}`);
718
- // Packages can ship 'use cache' functions too (Next allows it); everything
719
- // else in the source chain was already settled by the bundle above (imports
720
- // resolved by the plugins, constants inlined by esbuild `define`).
721
- return {
722
- code: applyBundledSourceTransforms(output.text, file),
723
- inputs: Object.keys(result.metafile.inputs).map(input => path.resolve(input)),
724
- };
725
- }
726
-
727
- /**
728
- * The one vendor-compile entry point. Every demand - a module compile's import scan, an esbuild
729
- * resolve callback, a transitive dependency of another vendor bundle - arrives here and is scheduled
730
- * by the pipeline in `./vendor`. `nested` marks demands raised from inside a running vendor build;
731
- * see `withVendorSlot` for why they must not queue.
732
- */
733
114
  export async function externalServerPackageHref(
734
115
  config: ResolvedConfig,
735
116
  specifier: string,
@@ -738,9 +119,16 @@ export async function externalServerPackageHref(
738
119
  conditionTarget: ServerBundleTarget = target,
739
120
  nested = false,
740
121
  ) {
741
- let plan = vendorBuildPlan(config, specifier, target, resolveDir, conditionTarget);
742
- const aliased = await crossLayerReusePlan(config, specifier, resolveDir, target, conditionTarget, plan);
743
- if (aliased) plan = aliased;
122
+ let plan = vendorBuildPlan(config, specifier, target, resolveDir, conditionTarget)
123
+ const aliased = await crossLayerReusePlan(
124
+ config,
125
+ specifier,
126
+ resolveDir,
127
+ target,
128
+ conditionTarget,
129
+ plan,
130
+ )
131
+ if (aliased) plan = aliased
744
132
  if (vendorTraceEnabled()) {
745
133
  vendorTraceRow({
746
134
  kind: 'request',
@@ -750,17 +138,17 @@ export async function externalServerPackageHref(
750
138
  conditionTarget,
751
139
  memHit: vendorBundleMemHit(plan.key),
752
140
  atMs: performance.now(),
753
- });
141
+ })
754
142
  }
755
- let file = await vendorBundle(plan, nested);
143
+ let file = await vendorBundle(plan, nested)
756
144
  // A `pnext build` wiping the output directory under a running server
757
145
  // deletes vendor bundles; re-write instead of handing out a dead path.
758
146
  if (!cachedExistsSync(file) || !(await compiledModuleUsable(file))) {
759
- dropVendorBundle(plan.key, file);
760
- if (plan.group) dropVendorGroup(plan.group.key);
761
- file = await vendorBundle(plan, nested);
147
+ dropVendorBundle(plan.key, file)
148
+ if (plan.group) dropVendorGroup(plan.group.key)
149
+ file = await vendorBundle(plan, nested)
762
150
  }
763
- return pathToFileHref(file);
151
+ return pathToFileHref(file)
764
152
  }
765
153
 
766
154
  /**
@@ -774,11 +162,11 @@ function vendorArtifactKey(
774
162
  conditionTarget: ServerBundleTarget,
775
163
  source: string,
776
164
  ) {
777
- return `${config.outPath}\0${target}\0${conditionTarget}\0${source}`;
165
+ return `${config.outPath}\0${target}\0${conditionTarget}\0${source}`
778
166
  }
779
167
 
780
168
  function vendorArtifactFile(config: ResolvedConfig, key: string) {
781
- return path.join(config.outPath, 'cache', 'server', 'vendor', `${hashBundleSpecifier(key)}.mjs`);
169
+ return path.join(config.outPath, 'cache', 'server', 'vendor', `${hashBundleSpecifier(key)}.mjs`)
782
170
  }
783
171
 
784
172
  /**
@@ -793,23 +181,23 @@ function resolveVendorEntry(
793
181
  resolveDir: string,
794
182
  conditionTarget: ServerBundleTarget,
795
183
  ) {
796
- if (path.isAbsolute(specifier)) return path.resolve(specifier);
184
+ if (path.isAbsolute(specifier)) return path.resolve(specifier)
797
185
  // Every demand builds a plan before dedup can see it, so this resolve runs
798
186
  // once per DEMAND, not once per artifact — and the same specifier arrives
799
187
  // from hundreds of importers. The answer is pure in its inputs.
800
- const key = `${config.root}\0${conditionTarget}\0${resolveDir}\0${specifier}`;
801
- if (vendorEntryResolutions.has(key)) return vendorEntryResolutions.get(key);
188
+ const key = `${config.root}\0${conditionTarget}\0${resolveDir}\0${specifier}`
189
+ if (vendorEntryResolutions.has(key)) return vendorEntryResolutions.get(key)
802
190
  const entry = resolveVendorPackageSpecifier(
803
191
  config.root,
804
192
  path.join(resolveDir, 'pnext-resolve.ts'),
805
193
  specifier,
806
194
  [...serverBundleConditions(conditionTarget), 'import'],
807
- );
808
- vendorEntryResolutions.set(key, entry);
809
- return entry;
195
+ )
196
+ vendorEntryResolutions.set(key, entry)
197
+ return entry
810
198
  }
811
199
 
812
- const vendorEntryResolutions = new Map<string, string | undefined>();
200
+ const vendorEntryResolutions = new Map<string, string | undefined>()
813
201
 
814
202
  function vendorBuildPlan(
815
203
  config: ResolvedConfig,
@@ -818,7 +206,7 @@ function vendorBuildPlan(
818
206
  resolveDir: string,
819
207
  conditionTarget: ServerBundleTarget,
820
208
  ): VendorBuildPlan {
821
- const entry = resolveVendorEntry(config, specifier, resolveDir, conditionTarget);
209
+ const entry = resolveVendorEntry(config, specifier, resolveDir, conditionTarget)
822
210
  // An unresolvable specifier has no artifact identity to key on; keep the old
823
211
  // importer-scoped key so the demand still compiles (and still dedups per dir).
824
212
  const key = vendorArtifactKey(
@@ -826,8 +214,8 @@ function vendorBuildPlan(
826
214
  target,
827
215
  conditionTarget,
828
216
  entry ?? `${path.resolve(resolveDir)}\0${specifier}`,
829
- );
830
- const file = vendorArtifactFile(config, key);
217
+ )
218
+ const file = vendorArtifactFile(config, key)
831
219
  return {
832
220
  key,
833
221
  file,
@@ -846,7 +234,7 @@ function vendorBuildPlan(
846
234
  group: entry
847
235
  ? vendorGroupPlan(config, { specifier, entry, file }, target, resolveDir, conditionTarget)
848
236
  : undefined,
849
- };
237
+ }
850
238
  }
851
239
 
852
240
  /**
@@ -856,12 +244,12 @@ function vendorBuildPlan(
856
244
  * liveness check rebuilds every bundle a second time.
857
245
  */
858
246
  async function writeVendorArtifact(file: string, code: string) {
859
- const id = await publishVendorContent(file, code);
247
+ const id = await publishVendorContent(file, code)
860
248
  if (id === undefined) {
861
- await writeFileAtomic(file, code);
862
- noteDevArtifactWritten(file);
249
+ await writeFileAtomic(file, code)
250
+ noteDevArtifactWritten(file)
863
251
  }
864
- return id;
252
+ return id
865
253
  }
866
254
 
867
255
  // --------------------------------------------------------------------------
@@ -869,79 +257,82 @@ async function writeVendorArtifact(file: string, code: string) {
869
257
  // --------------------------------------------------------------------------
870
258
 
871
259
  // eslint-disable-next-line turbo/no-undeclared-env-vars
872
- const vendorContentDisabled = () => process.env.PNEXT_VENDOR_CONTENT === '0';
260
+ const vendorContentDisabled = () => process.env.PNEXT_VENDOR_CONTENT === '0'
873
261
 
874
262
  function vendorContentFile(vendorDir: string, id: string) {
875
- return path.join(vendorDir, `${id}.content.mjs`);
263
+ return path.join(vendorDir, `${id}.content.mjs`)
876
264
  }
877
265
 
878
266
  /** Entries only: a chunk reaches its entry by relative path, so it stays put. */
879
267
  async function publishVendorContent(file: string, code: string) {
880
- const vendorDir = path.dirname(file);
881
- if (vendorContentDisabled() || path.basename(vendorDir) !== 'vendor') return undefined;
882
- const id = hashBundleSpecifier(canonicalVendorCode(code));
883
- const content = vendorContentFile(vendorDir, id);
884
- if (!cachedExistsSync(content)) await writeFileAtomic(content, code);
885
- noteDevArtifactWritten(content);
886
- await linkVendorArtifact(file, content);
887
- noteVendorContentId(file, id);
888
- noteDevArtifactWritten(file);
889
- return id;
268
+ const vendorDir = path.dirname(file)
269
+ if (vendorContentDisabled() || path.basename(vendorDir) !== 'vendor') return undefined
270
+ const id = hashBundleSpecifier(canonicalVendorCode(code))
271
+ const content = vendorContentFile(vendorDir, id)
272
+ if (!cachedExistsSync(content)) await writeFileAtomic(content, code)
273
+ noteDevArtifactWritten(content)
274
+ await linkVendorArtifact(file, content)
275
+ noteVendorContentId(file, id)
276
+ noteDevArtifactWritten(file)
277
+ return id
890
278
  }
891
279
 
892
280
  /** The layer-keyed path becomes an alias to the shared bytes. */
893
281
  async function linkVendorArtifact(file: string, content: string) {
894
- await rm(file, { force: true }).catch(() => undefined);
282
+ await rm(file, { force: true }).catch(() => undefined)
895
283
  try {
896
- await symlink(path.basename(content), file);
284
+ await symlink(path.basename(content), file)
897
285
  } catch {
898
- await copyFile(content, file);
286
+ await copyFile(content, file)
899
287
  }
900
288
  }
901
289
 
902
290
  /** What one layer's build of an entry saw, for the cross-layer comparison. */
903
291
  interface VendorLayerRecord {
904
- inputs: string;
905
- externals: string;
906
- blocked: boolean;
907
- id: string;
292
+ inputs: string
293
+ externals: string
294
+ blocked: boolean
295
+ id: string
908
296
  }
909
297
 
910
298
  // Keyed by output path exactly as `vendorArtifactKey` is: a verdict holds for
911
299
  // ONE app's conditions, aliases and defines, and several run in one process.
912
- const vendorLayerRecords = new Map<string, Map<string, VendorLayerRecord>>();
300
+ const vendorLayerRecords = new Map<string, Map<string, VendorLayerRecord>>()
913
301
  /** Entries proven layer-independent -> the content id both layers resolve to. */
914
- const vendorSharedEntries = new Map<string, Map<string, string>>();
915
- const vendorSharedLoaded = new Set<string>();
302
+ const vendorSharedEntries = new Map<string, Map<string, string>>()
303
+ const vendorSharedLoaded = new Set<string>()
916
304
 
917
305
  function vendorSharedFor(config: ResolvedConfig) {
918
- let shared = vendorSharedEntries.get(config.outPath);
306
+ let shared = vendorSharedEntries.get(config.outPath)
919
307
  if (!shared) {
920
- shared = new Map();
921
- vendorSharedEntries.set(config.outPath, shared);
308
+ shared = new Map()
309
+ vendorSharedEntries.set(config.outPath, shared)
922
310
  }
923
- return shared;
311
+ return shared
924
312
  }
925
313
 
926
314
  /** Only these two layers were ever the duplicated half. */
927
315
  function vendorLayerKey(target: CompatAliasTarget, conditionTarget: ServerBundleTarget) {
928
- if (target === 'server' && conditionTarget === 'server') return 'server';
929
- if (target === 'client' && conditionTarget === 'client') return 'client';
930
- return undefined;
316
+ if (target === 'server' && conditionTarget === 'server') return 'server'
317
+ if (target === 'client' && conditionTarget === 'client') return 'client'
318
+ return undefined
931
319
  }
932
320
 
933
321
  function vendorSharedFile(config: ResolvedConfig) {
934
- return path.join(config.outPath, 'cache', 'server', 'vendor', 'layer-shared.json');
322
+ return path.join(config.outPath, 'cache', 'server', 'vendor', 'layer-shared.json')
935
323
  }
936
324
 
937
325
  /** Verdicts from earlier runs, so reuse lands on the first demand of this one. */
938
326
  function loadVendorSharedEntries(config: ResolvedConfig) {
939
- if (vendorSharedLoaded.has(config.outPath)) return;
940
- vendorSharedLoaded.add(config.outPath);
327
+ if (vendorSharedLoaded.has(config.outPath)) return
328
+ vendorSharedLoaded.add(config.outPath)
941
329
  try {
942
- const stored = JSON.parse(readFileSync(vendorSharedFile(config), 'utf8')) as Record<string, string>;
943
- const shared = vendorSharedFor(config);
944
- for (const [entry, id] of Object.entries(stored)) shared.set(entry, id);
330
+ const stored = JSON.parse(readFileSync(vendorSharedFile(config), 'utf8')) as Record<
331
+ string,
332
+ string
333
+ >
334
+ const shared = vendorSharedFor(config)
335
+ for (const [entry, id] of Object.entries(stored)) shared.set(entry, id)
945
336
  } catch {
946
337
  // No verdicts yet; they are written as builds settle.
947
338
  }
@@ -951,7 +342,7 @@ function persistVendorSharedEntries(config: ResolvedConfig) {
951
342
  void writeFileAtomic(
952
343
  vendorSharedFile(config),
953
344
  JSON.stringify(Object.fromEntries(vendorSharedFor(config))),
954
- ).catch(() => undefined);
345
+ ).catch(() => undefined)
955
346
  }
956
347
 
957
348
  /** Offline verdict: externals compare as content ids, so this is a fixpoint. */
@@ -964,21 +355,19 @@ function recordVendorLayerBuild(
964
355
  code: string,
965
356
  id: string | undefined,
966
357
  ) {
967
- const layer = entry && id && metafile && vendorLayerKey(target, conditionTarget);
968
- if (!layer || !entry || !id || !metafile) return;
969
- loadVendorSharedEntries(config);
970
- const externals = new Set<string>();
971
- let blocked = /['"]use (?:client|server|cache)['"]/.test(code);
358
+ const layer = entry && id && metafile && vendorLayerKey(target, conditionTarget)
359
+ if (!layer || !entry || !id || !metafile) return
360
+ loadVendorSharedEntries(config)
361
+ const externals = new Set<string>()
362
+ let blocked = /['"]use (?:client|server|cache)['"]/.test(code)
972
363
  for (const meta of Object.values(metafile.inputs)) {
973
364
  for (const reference of meta.imports) {
974
- if (!reference.external) continue;
975
- const target_ = reference.path.startsWith('file:')
976
- ? fileURLToPath(reference.path)
977
- : undefined;
365
+ if (!reference.external) continue
366
+ const target_ = reference.path.startsWith('file:') ? fileURLToPath(reference.path) : undefined
978
367
  // A shim outside the vendor tree is this layer's own alias target: the
979
368
  // artifact is layer-specific whatever else matches.
980
- if (target_ && path.basename(path.dirname(target_)) !== 'vendor') blocked = true;
981
- externals.add(target_ ? (vendorContentId(target_) ?? target_) : reference.path);
369
+ if (target_ && path.basename(path.dirname(target_)) !== 'vendor') blocked = true
370
+ externals.add(target_ ? (vendorContentId(target_) ?? target_) : reference.path)
982
371
  }
983
372
  }
984
373
  const record: VendorLayerRecord = {
@@ -986,20 +375,20 @@ function recordVendorLayerBuild(
986
375
  externals: [...externals].sort().join('\n'),
987
376
  blocked,
988
377
  id,
989
- };
990
- const recordKey = `${config.outPath}\0${entry}`;
991
- let byLayer = vendorLayerRecords.get(recordKey);
378
+ }
379
+ const recordKey = `${config.outPath}\0${entry}`
380
+ let byLayer = vendorLayerRecords.get(recordKey)
992
381
  if (!byLayer) {
993
- byLayer = new Map();
994
- vendorLayerRecords.set(recordKey, byLayer);
382
+ byLayer = new Map()
383
+ vendorLayerRecords.set(recordKey, byLayer)
995
384
  }
996
- byLayer.set(layer, record);
997
- const server = byLayer.get('server');
998
- const client = byLayer.get('client');
999
- if (!server || !client || server.blocked || client.blocked) return;
1000
- if (server.inputs !== client.inputs || server.externals !== client.externals) return;
1001
- vendorSharedFor(config).set(entry, server.id);
1002
- persistVendorSharedEntries(config);
385
+ byLayer.set(layer, record)
386
+ const server = byLayer.get('server')
387
+ const client = byLayer.get('client')
388
+ if (!server || !client || server.blocked || client.blocked) return
389
+ if (server.inputs !== client.inputs || server.externals !== client.externals) return
390
+ vendorSharedFor(config).set(entry, server.id)
391
+ persistVendorSharedEntries(config)
1003
392
  }
1004
393
 
1005
394
  /** A proven-shared entry's content file, or undefined to compile as usual. */
@@ -1009,12 +398,13 @@ function reusableVendorContent(
1009
398
  target: CompatAliasTarget,
1010
399
  conditionTarget: ServerBundleTarget,
1011
400
  ) {
1012
- if (vendorContentDisabled() || !entry || !vendorLayerKey(target, conditionTarget)) return undefined;
1013
- loadVendorSharedEntries(config);
1014
- const id = vendorSharedFor(config).get(entry);
1015
- if (!id) return undefined;
1016
- const content = vendorContentFile(path.join(config.outPath, 'cache', 'server', 'vendor'), id);
1017
- return existsSync(content) ? content : undefined;
401
+ if (vendorContentDisabled() || !entry || !vendorLayerKey(target, conditionTarget))
402
+ return undefined
403
+ loadVendorSharedEntries(config)
404
+ const id = vendorSharedFor(config).get(entry)
405
+ if (!id) return undefined
406
+ const content = vendorContentFile(path.join(config.outPath, 'cache', 'server', 'vendor'), id)
407
+ return existsSync(content) ? content : undefined
1018
408
  }
1019
409
 
1020
410
  // --------------------------------------------------------------------------
@@ -1022,11 +412,11 @@ function reusableVendorContent(
1022
412
  // --------------------------------------------------------------------------
1023
413
 
1024
414
  // eslint-disable-next-line turbo/no-undeclared-env-vars
1025
- const vendorReuseEnabled = () => process.env.PNEXT_VENDOR_REUSE !== '0';
415
+ const vendorReuseEnabled = () => process.env.PNEXT_VENDOR_REUSE !== '0'
1026
416
  // eslint-disable-next-line turbo/no-undeclared-env-vars
1027
- const vendorReuseExtEnabled = () => process.env.PNEXT_VENDOR_REUSE_EXT !== '0';
417
+ const vendorReuseExtEnabled = () => process.env.PNEXT_VENDOR_REUSE_EXT !== '0'
1028
418
 
1029
- /** Alias targets that differ by layer (compat/index.ts); prefix-matches subpaths. */
419
+ /** Alias targets that differ by layer (compat/aliases.ts); prefix-matches subpaths. */
1030
420
  const VENDOR_LAYER_ALIASES = new Set([
1031
421
  'react',
1032
422
  'react-dom',
@@ -1036,50 +426,50 @@ const VENDOR_LAYER_ALIASES = new Set([
1036
426
  'next/server',
1037
427
  'server-only',
1038
428
  'client-only',
1039
- ]);
429
+ ])
1040
430
 
1041
431
  function isVendorLayerAlias(external: string) {
1042
432
  return (
1043
433
  VENDOR_LAYER_ALIASES.has(external) ||
1044
434
  VENDOR_LAYER_ALIASES.has(external.split('/').slice(0, 2).join('/'))
1045
- );
435
+ )
1046
436
  }
1047
437
 
1048
438
  interface VendorReuseVerdict {
1049
- stat: string;
1050
- conds: string;
1051
- reusable: boolean;
1052
- reason?: string;
439
+ stat: string
440
+ conds: string
441
+ reusable: boolean
442
+ reason?: string
1053
443
  }
1054
444
 
1055
- const vendorReuseVerdicts = new Map<string, Map<string, VendorReuseVerdict>>();
1056
- const vendorReuseLoaded = new Set<string>();
1057
- const vendorReuseProbes = new Map<string, Promise<boolean>>();
445
+ const vendorReuseVerdicts = new Map<string, Map<string, VendorReuseVerdict>>()
446
+ const vendorReuseLoaded = new Set<string>()
447
+ const vendorReuseProbes = new Map<string, Promise<boolean>>()
1058
448
 
1059
449
  function vendorReuseFile(config: ResolvedConfig) {
1060
- return path.join(config.outPath, 'cache', 'server', 'vendor', 'reuse-verdicts.json');
450
+ return path.join(config.outPath, 'cache', 'server', 'vendor', 'reuse-verdicts.json')
1061
451
  }
1062
452
 
1063
453
  function vendorReuseFor(config: ResolvedConfig) {
1064
- let verdicts = vendorReuseVerdicts.get(config.outPath);
454
+ let verdicts = vendorReuseVerdicts.get(config.outPath)
1065
455
  if (!verdicts) {
1066
- verdicts = new Map();
1067
- vendorReuseVerdicts.set(config.outPath, verdicts);
456
+ verdicts = new Map()
457
+ vendorReuseVerdicts.set(config.outPath, verdicts)
1068
458
  }
1069
- return verdicts;
459
+ return verdicts
1070
460
  }
1071
461
 
1072
462
  /** Verdicts persist per checkout: probes are paid once, restarts read the store. */
1073
463
  function loadVendorReuseVerdicts(config: ResolvedConfig) {
1074
- if (vendorReuseLoaded.has(config.outPath)) return;
1075
- vendorReuseLoaded.add(config.outPath);
464
+ if (vendorReuseLoaded.has(config.outPath)) return
465
+ vendorReuseLoaded.add(config.outPath)
1076
466
  try {
1077
467
  const stored = JSON.parse(readFileSync(vendorReuseFile(config), 'utf8')) as Record<
1078
468
  string,
1079
469
  VendorReuseVerdict
1080
- >;
1081
- const verdicts = vendorReuseFor(config);
1082
- for (const [key, verdict] of Object.entries(stored)) verdicts.set(key, verdict);
470
+ >
471
+ const verdicts = vendorReuseFor(config)
472
+ for (const [key, verdict] of Object.entries(stored)) verdicts.set(key, verdict)
1083
473
  } catch {
1084
474
  // No store yet; verdicts are written as probes settle.
1085
475
  }
@@ -1089,24 +479,24 @@ function persistVendorReuseVerdicts(config: ResolvedConfig) {
1089
479
  void writeFileAtomic(
1090
480
  vendorReuseFile(config),
1091
481
  JSON.stringify(Object.fromEntries(vendorReuseFor(config))),
1092
- ).catch(() => undefined);
482
+ ).catch(() => undefined)
1093
483
  }
1094
484
 
1095
485
  /** Probe results shared across verdict pairs — node_modules bytes are immutable per process. */
1096
- const vendorLayerProbeMemo = new Map<string, Promise<VendorLayerProbe>>();
486
+ const vendorLayerProbeMemo = new Map<string, Promise<VendorLayerProbe>>()
1097
487
 
1098
- type VendorLayerProbe = Awaited<ReturnType<typeof probeVendorLayer>>;
488
+ type VendorLayerProbe = Awaited<ReturnType<typeof probeVendorLayer>>
1099
489
 
1100
490
  function probeVendorLayerMemo(config: ResolvedConfig, entry: string, conditions: string[]) {
1101
- if (!vendorReuseExtEnabled()) return probeVendorLayer(config, entry, conditions);
1102
- const key = `${config.outPath}\0${entry}\0${conditions.join(',')}`;
1103
- let probe = vendorLayerProbeMemo.get(key);
491
+ if (!vendorReuseExtEnabled()) return probeVendorLayer(config, entry, conditions)
492
+ const key = `${config.outPath}\0${entry}\0${conditions.join(',')}`
493
+ let probe = vendorLayerProbeMemo.get(key)
1104
494
  if (!probe) {
1105
- probe = probeVendorLayer(config, entry, conditions);
1106
- probe.catch(() => vendorLayerProbeMemo.delete(key));
1107
- vendorLayerProbeMemo.set(key, probe);
495
+ probe = probeVendorLayer(config, entry, conditions)
496
+ probe.catch(() => vendorLayerProbeMemo.delete(key))
497
+ vendorLayerProbeMemo.set(key, probe)
1108
498
  }
1109
- return probe;
499
+ return probe
1110
500
  }
1111
501
 
1112
502
  /** Metafile-only probe of one layer's view of an entry (never writes, no slots). */
@@ -1127,10 +517,10 @@ async function probeVendorLayer(config: ResolvedConfig, entry: string, condition
1127
517
  jsxImportSource: 'preact',
1128
518
  logLevel: 'silent',
1129
519
  packages: 'external',
1130
- });
1131
- const externals = new Set<string>();
520
+ })
521
+ const externals = new Set<string>()
1132
522
  for (const meta of Object.values(result.metafile.inputs)) {
1133
- for (const reference of meta.imports) if (reference.external) externals.add(reference.path);
523
+ for (const reference of meta.imports) if (reference.external) externals.add(reference.path)
1134
524
  }
1135
525
  return {
1136
526
  inputs: Object.keys(result.metafile.inputs).sort().join('\n'),
@@ -1138,7 +528,7 @@ async function probeVendorLayer(config: ResolvedConfig, entry: string, condition
1138
528
  directive: /['"]use (?:client|server|cache)['"]/.test(
1139
529
  result.outputFiles.map(file => file.text).join(''),
1140
530
  ),
1141
- };
531
+ }
1142
532
  }
1143
533
 
1144
534
  async function computeVendorReuseVerdict(
@@ -1147,28 +537,35 @@ async function computeVendorReuseVerdict(
1147
537
  clientEntry: string,
1148
538
  otherTarget: ServerBundleTarget = 'client',
1149
539
  ): Promise<{ reusable: boolean; reason?: string }> {
1150
- const tp = performance.now();
540
+ const tp = performance.now()
1151
541
  const done = (v: { reusable: boolean; reason?: string }) => {
1152
- heavyProfRow({ k: 'reuse-probe', entry: serverEntry, other: otherTarget, ms: performance.now() - tp, ...v });
1153
- return v;
1154
- };
542
+ heavyProfRow({
543
+ k: 'reuse-probe',
544
+ entry: serverEntry,
545
+ other: otherTarget,
546
+ ms: performance.now() - tp,
547
+ ...v,
548
+ })
549
+ return v
550
+ }
1155
551
  // A finished server build already proved itself layer-specific: skip both probes.
1156
552
  if (vendorReuseExtEnabled()) {
1157
- const recorded = vendorLayerRecords.get(`${config.outPath}\0${serverEntry}`)?.get('server');
1158
- if (recorded?.blocked) return done({ reusable: false, reason: 'recorded-blocked' });
553
+ const recorded = vendorLayerRecords.get(`${config.outPath}\0${serverEntry}`)?.get('server')
554
+ if (recorded?.blocked) return done({ reusable: false, reason: 'recorded-blocked' })
1159
555
  }
1160
556
  const [server, client] = await Promise.all([
1161
557
  probeVendorLayerMemo(config, serverEntry, serverBundleConditions('server')),
1162
558
  probeVendorLayerMemo(config, clientEntry, serverBundleConditions(otherTarget)),
1163
- ]);
1164
- if (server.inputs !== client.inputs) return done({ reusable: false, reason: 'inputs-differ' });
559
+ ])
560
+ if (server.inputs !== client.inputs) return done({ reusable: false, reason: 'inputs-differ' })
1165
561
  if (server.externals.join('\n') !== client.externals.join('\n')) {
1166
- return done({ reusable: false, reason: 'externals-differ' });
562
+ return done({ reusable: false, reason: 'externals-differ' })
1167
563
  }
1168
- const aliasHit = server.externals.filter(isVendorLayerAlias);
1169
- if (aliasHit.length > 0) return done({ reusable: false, reason: `layer-alias: ${aliasHit.join(',')}` });
1170
- if (server.directive || client.directive) return done({ reusable: false, reason: 'directive' });
1171
- return done({ reusable: true });
564
+ const aliasHit = server.externals.filter(isVendorLayerAlias)
565
+ if (aliasHit.length > 0)
566
+ return done({ reusable: false, reason: `layer-alias: ${aliasHit.join(',')}` })
567
+ if (server.directive || client.directive) return done({ reusable: false, reason: 'directive' })
568
+ return done({ reusable: true })
1172
569
  }
1173
570
 
1174
571
  /** Whether the client demand may resolve to the server-layer artifact. */
@@ -1178,21 +575,27 @@ function crossLayerReuseVerdict(
1178
575
  clientEntry: string,
1179
576
  otherTarget: ServerBundleTarget = 'client',
1180
577
  ): Promise<boolean> {
1181
- loadVendorReuseVerdicts(config);
578
+ loadVendorReuseVerdicts(config)
1182
579
  const key =
1183
580
  otherTarget === 'client'
1184
581
  ? `${serverEntry}\0${clientEntry}`
1185
- : `${otherTarget}:${serverEntry}\0${clientEntry}`;
1186
- const stats = [statSync(serverEntry, { throwIfNoEntry: false }), statSync(clientEntry, { throwIfNoEntry: false })];
1187
- if (stats.some(entryStat => !entryStat)) return Promise.resolve(false);
1188
- const stat_ = stats.map(entryStat => `${entryStat!.mtimeMs}:${entryStat!.size}`).join('|');
1189
- const conds = JSON.stringify([serverBundleConditions('server'), serverBundleConditions(otherTarget)]);
1190
- const stored = vendorReuseFor(config).get(key);
582
+ : `${otherTarget}:${serverEntry}\0${clientEntry}`
583
+ const stats = [
584
+ statSync(serverEntry, { throwIfNoEntry: false }),
585
+ statSync(clientEntry, { throwIfNoEntry: false }),
586
+ ]
587
+ if (stats.some(entryStat => !entryStat)) return Promise.resolve(false)
588
+ const stat_ = stats.map(entryStat => `${entryStat!.mtimeMs}:${entryStat!.size}`).join('|')
589
+ const conds = JSON.stringify([
590
+ serverBundleConditions('server'),
591
+ serverBundleConditions(otherTarget),
592
+ ])
593
+ const stored = vendorReuseFor(config).get(key)
1191
594
  if (stored?.stat === stat_ && stored.conds === conds) {
1192
- return Promise.resolve(stored.reusable);
595
+ return Promise.resolve(stored.reusable)
1193
596
  }
1194
- const probeKey = `${config.outPath}\0${key}`;
1195
- let probe = vendorReuseProbes.get(probeKey);
597
+ const probeKey = `${config.outPath}\0${key}`
598
+ let probe = vendorReuseProbes.get(probeKey)
1196
599
  if (!probe) {
1197
600
  probe = computeVendorReuseVerdict(config, serverEntry, clientEntry, otherTarget)
1198
601
  .catch(error => ({
@@ -1200,14 +603,14 @@ function crossLayerReuseVerdict(
1200
603
  reason: `probe-failed: ${(error instanceof Error ? error.message : String(error)).slice(0, 160)}`,
1201
604
  }))
1202
605
  .then(verdict => {
1203
- vendorReuseFor(config).set(key, { stat: stat_, conds, ...verdict });
1204
- persistVendorReuseVerdicts(config);
1205
- return verdict.reusable;
606
+ vendorReuseFor(config).set(key, { stat: stat_, conds, ...verdict })
607
+ persistVendorReuseVerdicts(config)
608
+ return verdict.reusable
1206
609
  })
1207
- .finally(() => vendorReuseProbes.delete(probeKey));
1208
- vendorReuseProbes.set(probeKey, probe);
610
+ .finally(() => vendorReuseProbes.delete(probeKey))
611
+ vendorReuseProbes.set(probeKey, probe)
1209
612
  }
1210
- return probe;
613
+ return probe
1211
614
  }
1212
615
 
1213
616
  /**
@@ -1224,27 +627,29 @@ async function crossLayerReusePlan(
1224
627
  conditionTarget: ServerBundleTarget,
1225
628
  clientPlan: VendorBuildPlan,
1226
629
  ): Promise<VendorBuildPlan | undefined> {
1227
- if (!vendorReuseEnabled() || vendorContentDisabled()) return undefined;
630
+ if (!vendorReuseEnabled() || vendorContentDisabled()) return undefined
1228
631
  // An edge demand proven condition-independent lands on the server plan —
1229
632
  // one build serves both, and the server artifact keeps client reuse alive.
1230
633
  if (target === 'server' && conditionTarget === 'edge' && vendorReuseExtEnabled()) {
1231
- if (vendorBundleMemHit(clientPlan.key) || existsSync(clientPlan.file)) return undefined;
1232
- const edgeEntry = resolveVendorEntry(config, specifier, resolveDir, 'edge');
1233
- const serverEntry = resolveVendorEntry(config, specifier, resolveDir, 'server');
1234
- if (!edgeEntry || !serverEntry || edgeEntry !== serverEntry) return undefined;
1235
- const reusable = await crossLayerReuseVerdict(config, serverEntry, edgeEntry, 'edge').catch(() => false);
1236
- return reusable ? vendorBuildPlan(config, specifier, 'server', resolveDir, 'server') : undefined;
1237
- }
1238
- if (vendorLayerKey(target, conditionTarget) !== 'client') return undefined;
634
+ if (vendorBundleMemHit(clientPlan.key) || existsSync(clientPlan.file)) return undefined
635
+ const edgeEntry = resolveVendorEntry(config, specifier, resolveDir, 'edge')
636
+ const serverEntry = resolveVendorEntry(config, specifier, resolveDir, 'server')
637
+ if (!edgeEntry || !serverEntry || edgeEntry !== serverEntry) return undefined
638
+ const reusable = await crossLayerReuseVerdict(config, serverEntry, edgeEntry, 'edge').catch(
639
+ () => false,
640
+ )
641
+ return reusable ? vendorBuildPlan(config, specifier, 'server', resolveDir, 'server') : undefined
642
+ }
643
+ if (vendorLayerKey(target, conditionTarget) !== 'client') return undefined
1239
644
  // An in-flight or on-disk client artifact already settles cheaper than a probe.
1240
- if (vendorBundleMemHit(clientPlan.key) || existsSync(clientPlan.file)) return undefined;
1241
- const clientEntry = resolveVendorEntry(config, specifier, resolveDir, conditionTarget);
1242
- const serverEntry = resolveVendorEntry(config, specifier, resolveDir, 'server');
1243
- if (!clientEntry || !serverEntry) return undefined;
1244
- const serverPlan = vendorBuildPlan(config, specifier, 'server', resolveDir, 'server');
1245
- if (!vendorBundleMemHit(serverPlan.key) && !existsSync(serverPlan.file)) return undefined;
1246
- const reusable = await crossLayerReuseVerdict(config, serverEntry, clientEntry).catch(() => false);
1247
- return reusable ? serverPlan : undefined;
645
+ if (vendorBundleMemHit(clientPlan.key) || existsSync(clientPlan.file)) return undefined
646
+ const clientEntry = resolveVendorEntry(config, specifier, resolveDir, conditionTarget)
647
+ const serverEntry = resolveVendorEntry(config, specifier, resolveDir, 'server')
648
+ if (!clientEntry || !serverEntry) return undefined
649
+ const serverPlan = vendorBuildPlan(config, specifier, 'server', resolveDir, 'server')
650
+ if (!vendorBundleMemHit(serverPlan.key) && !existsSync(serverPlan.file)) return undefined
651
+ const reusable = await crossLayerReuseVerdict(config, serverEntry, clientEntry).catch(() => false)
652
+ return reusable ? serverPlan : undefined
1248
653
  }
1249
654
 
1250
655
  /** Cache hit and no-compile fast paths, in front of every build. */
@@ -1260,23 +665,23 @@ async function prepareVendorArtifact(
1260
665
  // Vendor bundles are keyed by resolved package path (version-specific under
1261
666
  // pnpm), so an existing file is already current. Reuse it across dev restarts
1262
667
  // instead of re-bundling the whole dependency graph.
1263
- if (existsSync(file) && (await compiledModuleUsable(file))) return file;
1264
- await mkdir(path.dirname(file), { recursive: true });
668
+ if (existsSync(file) && (await compiledModuleUsable(file))) return file
669
+ await mkdir(path.dirname(file), { recursive: true })
1265
670
  // An earlier build proved this entry layer-independent: the other layer's
1266
671
  // content IS this layer's artifact, so the demand settles without compiling.
1267
- const shared = reusableVendorContent(config, entry, target, conditionTarget);
672
+ const shared = reusableVendorContent(config, entry, target, conditionTarget)
1268
673
  if (shared && (await compiledModuleUsable(shared))) {
1269
- await linkVendorArtifact(file, shared);
1270
- noteDevArtifactWritten(file);
1271
- return shared;
674
+ await linkVendorArtifact(file, shared)
675
+ noteDevArtifactWritten(file)
676
+ return shared
1272
677
  }
1273
678
  // A package that already ships a browser-ready ESM dist is copied, not compiled - the bundler would
1274
679
  // only reproduce it. Sits in FRONT of the pass, never replaces it: anything the predicate rejects
1275
680
  // falls through unchanged, and the copy is resolved exactly as the bundler would have. The copies
1276
681
  // load natively now (rootFilter skips the vendor tree), so an app that configures `compiler.define`
1277
682
  // must take the bundling path instead - that is where the defines are applied.
1278
- if (vendorLoadsNatively() && serverDefineOptions().define) return undefined;
1279
- const distDir = `${file.replace(/\.mjs$/, '')}.dist`;
683
+ if (vendorLoadsNatively() && serverDefineOptions().define) return undefined
684
+ const distDir = `${file.replace(/\.mjs$/, '')}.dist`
1280
685
  const distEntry = await copyBrowserReadyEsmDist(
1281
686
  resolveVendorPackageSpecifier(
1282
687
  config.root,
@@ -1285,44 +690,30 @@ async function prepareVendorArtifact(
1285
690
  serverBundleConditions(conditionTarget),
1286
691
  ) ?? '',
1287
692
  distDir,
1288
- );
693
+ )
1289
694
  if (distEntry) {
1290
695
  // Native loading means the `.js` copies need their own type marker: an app
1291
696
  // without `"type": "module"` would otherwise have Bun read them as
1292
697
  // CommonJS and choke on their `export` syntax.
1293
- await markEsmDistPackage(distDir);
1294
- noteDevArtifactWritten(distEntry);
698
+ await markEsmDistPackage(distDir)
699
+ noteDevArtifactWritten(distEntry)
1295
700
  }
1296
- return distEntry;
701
+ return distEntry
1297
702
  }
1298
703
 
1299
- /**
1300
- * Bisect switch for the vendor-tree load-plugin exclusion. Deliberately NOT `PNEXT_VENDOR_NATIVE` -
1301
- * that name already gates the plugin-free bundler, and sharing it would arm that opt-in path by
1302
- * accident whenever this one was bisected.
1303
- */
1304
- function vendorLoadsNatively() {
1305
- // eslint-disable-next-line turbo/no-undeclared-env-vars
1306
- return process.env.PNEXT_VENDOR_DIST_NATIVE !== '0';
1307
- }
1308
-
1309
- // --------------------------------------------------------------------------
1310
- // pre-planned vendor policy (PNEXT_VENDOR_PREPLAN=1 opt-in, server layer only)
1311
- // --------------------------------------------------------------------------
1312
-
1313
704
  // eslint-disable-next-line turbo/no-undeclared-env-vars
1314
- const vendorPreplanEnabled = () => process.env.PNEXT_VENDOR_PREPLAN === '1';
705
+ const vendorPreplanEnabled = () => process.env.PNEXT_VENDOR_PREPLAN === '1'
1315
706
 
1316
707
  interface VendorPreplanVerdict {
1317
- stat: string;
1318
- conds: string;
1319
- safe: boolean;
1320
- reason?: string;
708
+ stat: string
709
+ conds: string
710
+ safe: boolean
711
+ reason?: string
1321
712
  }
1322
713
 
1323
- const vendorPreplanVerdicts = new Map<string, Map<string, VendorPreplanVerdict>>();
1324
- const vendorPreplanLoaded = new Set<string>();
1325
- const vendorPreplanProbes = new Map<string, Promise<VendorPreplanVerdict>>();
714
+ const vendorPreplanVerdicts = new Map<string, Map<string, VendorPreplanVerdict>>()
715
+ const vendorPreplanLoaded = new Set<string>()
716
+ const vendorPreplanProbes = new Map<string, Promise<VendorPreplanVerdict>>()
1326
717
 
1327
718
  const vendorPreplanStats = {
1328
719
  preplanRefs: 0,
@@ -1332,49 +723,49 @@ const vendorPreplanStats = {
1332
723
  packagesRouted: 0,
1333
724
  packagesRejected: 0,
1334
725
  reasons: [] as string[],
1335
- };
726
+ }
1336
727
 
1337
728
  export function vendorPreplanReport() {
1338
- return { ...vendorPreplanStats, reasons: vendorPreplanStats.reasons.slice(0, 60) };
729
+ return { ...vendorPreplanStats, reasons: vendorPreplanStats.reasons.slice(0, 60) }
1339
730
  }
1340
731
 
1341
732
  // eslint-disable-next-line turbo/no-undeclared-env-vars
1342
- const vendorPreplanDebug = () => process.env.PNEXT_VENDOR_PREPLAN_DEBUG === '1';
1343
- let preplanReportFlush: ReturnType<typeof setTimeout> | undefined;
733
+ const vendorPreplanDebug = () => process.env.PNEXT_VENDOR_PREPLAN_DEBUG === '1'
734
+ let preplanReportFlush: ReturnType<typeof setTimeout> | undefined
1344
735
  function schedulePreplanReport() {
1345
- if (!vendorPreplanDebug()) return;
1346
- if (preplanReportFlush) clearTimeout(preplanReportFlush);
736
+ if (!vendorPreplanDebug()) return
737
+ if (preplanReportFlush) clearTimeout(preplanReportFlush)
1347
738
  preplanReportFlush = setTimeout(() => {
1348
- console.error(`pnext vendor preplan ${JSON.stringify(vendorPreplanReport())}`);
1349
- console.error(`pnext vendor native ${JSON.stringify(vendorNativeReport())}`);
1350
- }, 3000);
1351
- preplanReportFlush.unref?.();
739
+ console.error(`pnext vendor preplan ${JSON.stringify(vendorPreplanReport())}`)
740
+ console.error(`pnext vendor native ${JSON.stringify(vendorNativeReport())}`)
741
+ }, 3000)
742
+ preplanReportFlush.unref?.()
1352
743
  }
1353
744
 
1354
745
  function vendorPreplanFile(config: ResolvedConfig) {
1355
- return path.join(config.outPath, 'cache', 'server', 'vendor', 'preplan-verdicts.json');
746
+ return path.join(config.outPath, 'cache', 'server', 'vendor', 'preplan-verdicts.json')
1356
747
  }
1357
748
 
1358
749
  function vendorPreplanFor(config: ResolvedConfig) {
1359
- let verdicts = vendorPreplanVerdicts.get(config.outPath);
750
+ let verdicts = vendorPreplanVerdicts.get(config.outPath)
1360
751
  if (!verdicts) {
1361
- verdicts = new Map();
1362
- vendorPreplanVerdicts.set(config.outPath, verdicts);
752
+ verdicts = new Map()
753
+ vendorPreplanVerdicts.set(config.outPath, verdicts)
1363
754
  }
1364
- return verdicts;
755
+ return verdicts
1365
756
  }
1366
757
 
1367
758
  /** Probe verdicts persist per checkout, reuse-verdict style. */
1368
759
  function loadVendorPreplanVerdicts(config: ResolvedConfig) {
1369
- if (vendorPreplanLoaded.has(config.outPath)) return;
1370
- vendorPreplanLoaded.add(config.outPath);
760
+ if (vendorPreplanLoaded.has(config.outPath)) return
761
+ vendorPreplanLoaded.add(config.outPath)
1371
762
  try {
1372
763
  const stored = JSON.parse(readFileSync(vendorPreplanFile(config), 'utf8')) as Record<
1373
764
  string,
1374
765
  VendorPreplanVerdict
1375
- >;
1376
- const verdicts = vendorPreplanFor(config);
1377
- for (const [key, verdict] of Object.entries(stored)) verdicts.set(key, verdict);
766
+ >
767
+ const verdicts = vendorPreplanFor(config)
768
+ for (const [key, verdict] of Object.entries(stored)) verdicts.set(key, verdict)
1378
769
  } catch {
1379
770
  // No store yet; verdicts are written as probes settle.
1380
771
  }
@@ -1384,7 +775,7 @@ function persistVendorPreplanVerdicts(config: ResolvedConfig) {
1384
775
  void writeFileAtomic(
1385
776
  vendorPreplanFile(config),
1386
777
  JSON.stringify(Object.fromEntries(vendorPreplanFor(config))),
1387
- ).catch(() => undefined);
778
+ ).catch(() => undefined)
1388
779
  }
1389
780
 
1390
781
  /** Does this entry's graph change shape under `react-server` (or carry directives)? */
@@ -1393,15 +784,15 @@ function vendorPreplanEntryVerdict(
1393
784
  entry: string,
1394
785
  conditions: string[],
1395
786
  ): Promise<VendorPreplanVerdict> | VendorPreplanVerdict {
1396
- loadVendorPreplanVerdicts(config);
1397
- const entryStat = statSync(entry, { throwIfNoEntry: false });
1398
- if (!entryStat) return { stat: '', conds: '', safe: false, reason: 'entry missing' };
1399
- const stat_ = `${entryStat.mtimeMs}:${entryStat.size}`;
1400
- const conds = JSON.stringify(conditions);
1401
- const stored = vendorPreplanFor(config).get(entry);
1402
- if (stored?.stat === stat_ && stored.conds === conds) return stored;
1403
- const probeKey = `${config.outPath}\0${entry}\0${conds}`;
1404
- let probe = vendorPreplanProbes.get(probeKey);
787
+ loadVendorPreplanVerdicts(config)
788
+ const entryStat = statSync(entry, { throwIfNoEntry: false })
789
+ if (!entryStat) return { stat: '', conds: '', safe: false, reason: 'entry missing' }
790
+ const stat_ = `${entryStat.mtimeMs}:${entryStat.size}`
791
+ const conds = JSON.stringify(conditions)
792
+ const stored = vendorPreplanFor(config).get(entry)
793
+ if (stored?.stat === stat_ && stored.conds === conds) return stored
794
+ const probeKey = `${config.outPath}\0${entry}\0${conds}`
795
+ let probe = vendorPreplanProbes.get(probeKey)
1405
796
  if (!probe) {
1406
797
  probe = computeVendorPreplanVerdict(config, entry, conditions, stat_, conds)
1407
798
  .catch(error => ({
@@ -1411,14 +802,14 @@ function vendorPreplanEntryVerdict(
1411
802
  reason: `probe-failed: ${(error instanceof Error ? error.message : String(error)).slice(0, 160)}`,
1412
803
  }))
1413
804
  .then(verdict => {
1414
- vendorPreplanFor(config).set(entry, verdict);
1415
- persistVendorPreplanVerdicts(config);
1416
- return verdict;
805
+ vendorPreplanFor(config).set(entry, verdict)
806
+ persistVendorPreplanVerdicts(config)
807
+ return verdict
1417
808
  })
1418
- .finally(() => vendorPreplanProbes.delete(probeKey));
1419
- vendorPreplanProbes.set(probeKey, probe);
809
+ .finally(() => vendorPreplanProbes.delete(probeKey))
810
+ vendorPreplanProbes.set(probeKey, probe)
1420
811
  }
1421
- return probe;
812
+ return probe
1422
813
  }
1423
814
 
1424
815
  async function computeVendorPreplanVerdict(
@@ -1430,18 +821,22 @@ async function computeVendorPreplanVerdict(
1430
821
  ): Promise<VendorPreplanVerdict> {
1431
822
  const [rsc, plain] = await Promise.all([
1432
823
  probeVendorLayer(config, entry, conditions),
1433
- probeVendorLayer(config, entry, conditions.filter(condition => condition !== 'react-server')),
1434
- ]);
824
+ probeVendorLayer(
825
+ config,
826
+ entry,
827
+ conditions.filter(condition => condition !== 'react-server'),
828
+ ),
829
+ ])
1435
830
  if (rsc.inputs !== plain.inputs) {
1436
- return { stat: stat_, conds, safe: false, reason: 'react-server inputs differ' };
831
+ return { stat: stat_, conds, safe: false, reason: 'react-server inputs differ' }
1437
832
  }
1438
833
  if (rsc.externals.join('\n') !== plain.externals.join('\n')) {
1439
- return { stat: stat_, conds, safe: false, reason: 'react-server externals differ' };
834
+ return { stat: stat_, conds, safe: false, reason: 'react-server externals differ' }
1440
835
  }
1441
836
  if (rsc.directive || plain.directive) {
1442
- return { stat: stat_, conds, safe: false, reason: 'directive' };
837
+ return { stat: stat_, conds, safe: false, reason: 'directive' }
1443
838
  }
1444
- return { stat: stat_, conds, safe: true };
839
+ return { stat: stat_, conds, safe: true }
1445
840
  }
1446
841
 
1447
842
  /**
@@ -1457,37 +852,37 @@ async function vendorPreplanPackageSafe(
1457
852
  target: CompatAliasTarget,
1458
853
  conditionTarget: ServerBundleTarget,
1459
854
  ): Promise<boolean> {
1460
- if (!vendorPreplanEnabled() || target !== 'server' || conditionTarget !== 'server') return false;
855
+ if (!vendorPreplanEnabled() || target !== 'server' || conditionTarget !== 'server') return false
1461
856
  const reject = (reason: string) => {
1462
- vendorPreplanStats.packagesRejected += 1;
1463
- vendorPreplanStats.reasons.push(`${packageName ?? '?'}: ${reason}`);
1464
- schedulePreplanReport();
1465
- return false;
1466
- };
1467
- if (!packageName || !entry || !path.isAbsolute(entry)) return reject('unresolved entry');
1468
- if (/\.[cm]?tsx?$/.test(entry)) return reject('ts entry');
1469
- if (!isEsmModuleEntry(entry)) return reject('cjs entry');
1470
- if (getExternalPackagePolicy().transpile(packageName)) return reject('transpilePackages');
1471
- const verdict = await vendorPreplanEntryVerdict(config, entry, serverBundleConditions('server'));
1472
- if (!verdict.safe) return reject(verdict.reason ?? 'react-server divergence');
1473
- vendorPreplanStats.packagesRouted += 1;
1474
- schedulePreplanReport();
1475
- return true;
857
+ vendorPreplanStats.packagesRejected += 1
858
+ vendorPreplanStats.reasons.push(`${packageName ?? '?'}: ${reason}`)
859
+ schedulePreplanReport()
860
+ return false
861
+ }
862
+ if (!packageName || !entry || !path.isAbsolute(entry)) return reject('unresolved entry')
863
+ if (/\.[cm]?tsx?$/.test(entry)) return reject('ts entry')
864
+ if (!isEsmModuleEntry(entry)) return reject('cjs entry')
865
+ if (getExternalPackagePolicy().transpile(packageName)) return reject('transpilePackages')
866
+ const verdict = await vendorPreplanEntryVerdict(config, entry, serverBundleConditions('server'))
867
+ if (!verdict.safe) return reject(verdict.reason ?? 'react-server divergence')
868
+ vendorPreplanStats.packagesRouted += 1
869
+ schedulePreplanReport()
870
+ return true
1476
871
  }
1477
872
 
1478
873
  /** Mirror of `externalServerPackageHref`'s liveness recheck, minus the await(er). */
1479
874
  function enqueuePreplanBuild(plan: VendorBuildPlan) {
1480
- vendorPreplanStats.enqueued += 1;
875
+ vendorPreplanStats.enqueued += 1
1481
876
  trackPreplanBuild(
1482
877
  (async () => {
1483
- let file = await vendorBundle(plan, false);
878
+ let file = await vendorBundle(plan, false)
1484
879
  if (!cachedExistsSync(file) || !(await compiledModuleUsable(file))) {
1485
- dropVendorBundle(plan.key, file);
1486
- if (plan.group) dropVendorGroup(plan.group.key);
1487
- file = await vendorBundle(plan, false);
880
+ dropVendorBundle(plan.key, file)
881
+ if (plan.group) dropVendorGroup(plan.group.key)
882
+ file = await vendorBundle(plan, false)
1488
883
  }
1489
884
  })(),
1490
- );
885
+ )
1491
886
  }
1492
887
 
1493
888
  /**
@@ -1504,20 +899,20 @@ async function preplanDependencyDecision(
1504
899
  specifier: string,
1505
900
  resolveDir: string,
1506
901
  ): Promise<OnResolveResult | undefined> {
1507
- const depName = packageNameFromSpecifier(specifier);
1508
- if (!depName || !isPackageSpecifier(specifier)) return undefined;
1509
- if (builtinModules.includes(depName) || isBunBuiltin(depName)) return undefined;
1510
- if (getExternalPackagePolicy().external(depName)) return undefined;
1511
- if (getExternalPackagePolicy().transpile(depName)) return undefined;
1512
- const entry = resolveVendorEntry(config, specifier, resolveDir, conditionTarget);
1513
- if (!entry || /\.[cm]?tsx?$/.test(entry) || !isEsmModuleEntry(entry)) return undefined;
1514
- const plan = vendorBuildPlan(config, specifier, target, resolveDir, conditionTarget);
902
+ const depName = packageNameFromSpecifier(specifier)
903
+ if (!depName || !isPackageSpecifier(specifier)) return undefined
904
+ if (builtinModules.includes(depName) || isBunBuiltin(depName)) return undefined
905
+ if (getExternalPackagePolicy().external(depName)) return undefined
906
+ if (getExternalPackagePolicy().transpile(depName)) return undefined
907
+ const entry = resolveVendorEntry(config, specifier, resolveDir, conditionTarget)
908
+ if (!entry || /\.[cm]?tsx?$/.test(entry) || !isEsmModuleEntry(entry)) return undefined
909
+ const plan = vendorBuildPlan(config, specifier, target, resolveDir, conditionTarget)
1515
910
  if (cachedExistsSync(plan.file)) {
1516
- if (!(await compiledModuleUsable(plan.file))) return undefined;
911
+ if (!(await compiledModuleUsable(plan.file))) return undefined
1517
912
  } else {
1518
913
  // Both prepare fast paths settle on a DIFFERENT file than `plan.file`, so
1519
914
  // their demands keep the awaited path (which is nearly free for them).
1520
- if (reusableVendorContent(config, entry, target, conditionTarget)) return undefined;
915
+ if (reusableVendorContent(config, entry, target, conditionTarget)) return undefined
1521
916
  if (!(vendorLoadsNatively() && serverDefineOptions().define)) {
1522
917
  const distEntry = await copyBrowserReadyEsmDist(
1523
918
  resolveVendorPackageSpecifier(
@@ -1527,22 +922,22 @@ async function preplanDependencyDecision(
1527
922
  serverBundleConditions(conditionTarget),
1528
923
  ) ?? '',
1529
924
  `${plan.file.replace(/\.mjs$/, '')}.dist`,
1530
- );
1531
- if (distEntry) return undefined;
925
+ )
926
+ if (distEntry) return undefined
1532
927
  }
1533
928
  }
1534
- enqueuePreplanBuild(plan);
1535
- return { path: pathToFileHref(plan.file), external: true };
929
+ enqueuePreplanBuild(plan)
930
+ return { path: pathToFileHref(plan.file), external: true }
1536
931
  }
1537
932
 
1538
- const esmDistPackages = new Set<string>();
933
+ const esmDistPackages = new Set<string>()
1539
934
 
1540
935
  async function markEsmDistPackage(distDir: string) {
1541
- if (esmDistPackages.has(distDir)) return;
1542
- esmDistPackages.add(distDir);
1543
- const marker = path.join(distDir, 'package.json');
1544
- if (existsSync(marker)) return;
1545
- await writeFile(marker, '{"type":"module"}').catch(() => undefined);
936
+ if (esmDistPackages.has(distDir)) return
937
+ esmDistPackages.add(distDir)
938
+ const marker = path.join(distDir, 'package.json')
939
+ if (existsSync(marker)) return
940
+ await writeFile(marker, '{"type":"module"}').catch(() => undefined)
1546
941
  }
1547
942
 
1548
943
  /**
@@ -1554,7 +949,7 @@ async function markEsmDistPackage(distDir: string) {
1554
949
  */
1555
950
  // eslint-disable-next-line @typescript-eslint/require-await
1556
951
  export async function compiledModuleUsable(file: string): Promise<boolean> {
1557
- return devArtifactUsable(file);
952
+ return devArtifactUsable(file)
1558
953
  }
1559
954
 
1560
955
  /**
@@ -1573,9 +968,9 @@ function vendorGroupPlan(
1573
968
  resolveDir: string,
1574
969
  conditionTarget: ServerBundleTarget,
1575
970
  ): VendorGroupPlan | undefined {
1576
- const packageName = packageNameFromSpecifier(member.specifier);
1577
- const packageRoot = packageName && vendorPackageRoot(member.entry, packageName);
1578
- if (!packageName || !packageRoot || !isEsmModuleEntry(member.entry)) return undefined;
971
+ const packageName = packageNameFromSpecifier(member.specifier)
972
+ const packageRoot = packageName && vendorPackageRoot(member.entry, packageName)
973
+ if (!packageName || !packageRoot || !isEsmModuleEntry(member.entry)) return undefined
1579
974
  return {
1580
975
  key: `${config.outPath}\0${target}\0${conditionTarget}\0${packageRoot}`,
1581
976
  member,
@@ -1590,7 +985,7 @@ function vendorGroupPlan(
1590
985
  conditionTarget,
1591
986
  nested,
1592
987
  ),
1593
- };
988
+ }
1594
989
  }
1595
990
 
1596
991
  /**
@@ -1598,10 +993,10 @@ function vendorGroupPlan(
1598
993
  * so a nested (non-hoisted) copy never shares a group with the hoisted one.
1599
994
  */
1600
995
  function vendorPackageRoot(entry: string, packageName: string) {
1601
- const marker = `${path.sep}node_modules${path.sep}`;
1602
- const index = entry.lastIndexOf(marker);
1603
- if (index < 0) return undefined;
1604
- return path.join(entry.slice(0, index + marker.length), packageName);
996
+ const marker = `${path.sep}node_modules${path.sep}`
997
+ const index = entry.lastIndexOf(marker)
998
+ if (index < 0) return undefined
999
+ return path.join(entry.slice(0, index + marker.length), packageName)
1605
1000
  }
1606
1001
 
1607
1002
  /**
@@ -1619,8 +1014,8 @@ async function buildVendorGroup(
1619
1014
  conditionTarget: ServerBundleTarget,
1620
1015
  nested: boolean,
1621
1016
  ) {
1622
- const built = members.filter(member => !existsSync(member.file));
1623
- if (built.length === 0) return;
1017
+ const built = members.filter(member => !existsSync(member.file))
1018
+ if (built.length === 0) return
1624
1019
  // Only a sibling whose artifact is ON DISK can be externalized — one still
1625
1020
  // compiling (a solo build this round did not wait for) must be inlined, or
1626
1021
  // this bundle would import a file that is not there yet.
@@ -1628,26 +1023,26 @@ async function buildVendorGroup(
1628
1023
  [...external, ...members]
1629
1024
  .filter(member => !built.includes(member) && existsSync(member.file))
1630
1025
  .map(member => [member.specifier, pathToFileHref(member.file)] as const),
1631
- );
1632
- const outdir = path.dirname(built[0]!.file);
1633
- const context = { nestedExternalFiles: [] as string[] };
1634
- const entryFiles = new Set(built.map(member => member.file));
1026
+ )
1027
+ const outdir = path.dirname(built[0]!.file)
1028
+ const context = { nestedExternalFiles: [] as string[] }
1029
+ const entryFiles = new Set(built.map(member => member.file))
1635
1030
  // The plugin-chain fallback builds from bare specifiers; hand the interop
1636
1031
  // plugin each member's already-resolved entry.
1637
- for (const member of built) provideEntryResolution(member.specifier, resolveDir, member.entry);
1032
+ for (const member of built) provideEntryResolution(member.specifier, resolveDir, member.entry)
1638
1033
  const entryPoints = built.map(member => ({
1639
1034
  in:
1640
1035
  getBundlerExtensions().serverBundleEntry(member.specifier, resolveDir, member.entry) ??
1641
1036
  member.specifier,
1642
1037
  out: path.basename(member.file, '.mjs'),
1643
- }));
1038
+ }))
1644
1039
  const trace: VendorBuildTrace | undefined = vendorTraceEnabled()
1645
1040
  ? { plugins: [], entry: built[0]!.entry }
1646
- : undefined;
1647
- const traceSeq = trace ? nextVendorTraceSeq() : 0;
1648
- const traceStart = performance.now();
1041
+ : undefined
1042
+ const traceSeq = trace ? nextVendorTraceSeq() : 0
1043
+ const traceStart = performance.now()
1649
1044
  const traceRow = (outputs: { text: string }[] | undefined, error?: unknown) => {
1650
- if (!trace) return;
1045
+ if (!trace) return
1651
1046
  traceVendorBuild({
1652
1047
  seq: traceSeq,
1653
1048
  specifier: packageName,
@@ -1660,11 +1055,11 @@ async function buildVendorGroup(
1660
1055
  trace,
1661
1056
  ok: !error,
1662
1057
  ...(error
1663
- // eslint-disable-next-line @typescript-eslint/no-base-to-string
1664
- ? { error: (error instanceof Error ? error.message : String(error)).slice(0, 300) }
1058
+ ? // eslint-disable-next-line @typescript-eslint/no-base-to-string
1059
+ { error: (error instanceof Error ? error.message : String(error)).slice(0, 300) }
1665
1060
  : {}),
1666
- });
1667
- };
1061
+ })
1062
+ }
1668
1063
  const preplan =
1669
1064
  vendorPreplanEnabled() &&
1670
1065
  (
@@ -1673,8 +1068,8 @@ async function buildVendorGroup(
1673
1068
  vendorPreplanPackageSafe(config, packageName, member.entry, target, conditionTarget),
1674
1069
  ),
1675
1070
  )
1676
- ).every(Boolean);
1677
- let outputFiles;
1071
+ ).every(Boolean)
1072
+ let outputFiles
1678
1073
  try {
1679
1074
  outputFiles = await profileRuntimeStep(
1680
1075
  `vendor bundle ${packageName} [${target}/${conditionTarget}] (${built.length} entries)`,
@@ -1715,27 +1110,24 @@ async function buildVendorGroup(
1715
1110
  trace,
1716
1111
  preplan,
1717
1112
  )),
1718
- );
1113
+ )
1719
1114
  } catch (error) {
1720
- traceRow(undefined, error);
1721
- throw error;
1115
+ traceRow(undefined, error)
1116
+ throw error
1722
1117
  }
1723
- traceRow(outputFiles);
1724
- await mkdir(path.join(outdir, 'chunks'), { recursive: true });
1118
+ traceRow(outputFiles)
1119
+ await mkdir(path.join(outdir, 'chunks'), { recursive: true })
1725
1120
  // Chunks first: an entry must never be readable before what it imports is.
1726
1121
  for (const output of [...outputFiles].sort(
1727
1122
  (a, b) => Number(entryFiles.has(a.path)) - Number(entryFiles.has(b.path)),
1728
1123
  )) {
1729
- const bundled = entryFiles.has(output.path)
1730
- ? addCommonJsNamedExports(output.text)
1731
- : output.text;
1124
+ const bundled = entryFiles.has(output.path) ? addCommonJsNamedExports(output.text) : output.text
1732
1125
  // Packages can ship 'use cache' functions too (Next allows it); client
1733
1126
  // bundles keep the source untouched.
1734
- const code =
1735
- target === 'server' ? applyBundledSourceTransforms(bundled, output.path) : bundled;
1736
- await writeVendorArtifact(output.path, code);
1127
+ const code = target === 'server' ? applyBundledSourceTransforms(bundled, output.path) : bundled
1128
+ await writeVendorArtifact(output.path, code)
1737
1129
  }
1738
- await emitNestedExternalTraceCopies(built[0]!.file, context.nestedExternalFiles);
1130
+ await emitNestedExternalTraceCopies(built[0]!.file, context.nestedExternalFiles)
1739
1131
  }
1740
1132
 
1741
1133
  // vendor plugin-callback attribution (PNEXT_VENDOR_PROFILE=1, dormant otherwise). Every
@@ -1743,32 +1135,32 @@ async function buildVendorGroup(
1743
1135
  // answers them; counts separate "our callbacks are slow" from "we answer many questions".
1744
1136
 
1745
1137
  // eslint-disable-next-line turbo/no-undeclared-env-vars
1746
- const vendorProfileEnabled = process.env.PNEXT_VENDOR_PROFILE === '1';
1747
- const vendorProfileTotals = new Map<string, number>();
1138
+ const vendorProfileEnabled = process.env.PNEXT_VENDOR_PROFILE === '1'
1139
+ const vendorProfileTotals = new Map<string, number>()
1748
1140
 
1749
1141
  function vendorProfileCount(key: string, by = 1) {
1750
- vendorProfileTotals.set(key, (vendorProfileTotals.get(key) ?? 0) + by);
1142
+ vendorProfileTotals.set(key, (vendorProfileTotals.get(key) ?? 0) + by)
1751
1143
  }
1752
1144
 
1753
1145
  export function vendorProfileReport() {
1754
1146
  return Object.fromEntries(
1755
1147
  [...vendorProfileTotals].sort((a, b) => b[1] - a[1]).map(([k, v]) => [k, Math.round(v)]),
1756
- );
1148
+ )
1757
1149
  }
1758
1150
 
1759
1151
  // Printed once the callbacks go quiet, so one page's whole plugin chain lands
1760
1152
  // in a single line instead of interleaving with the build it is measuring.
1761
- let vendorProfileFlush: ReturnType<typeof setTimeout> | undefined;
1153
+ let vendorProfileFlush: ReturnType<typeof setTimeout> | undefined
1762
1154
  function scheduleVendorProfileReport() {
1763
- if (vendorProfileFlush) clearTimeout(vendorProfileFlush);
1155
+ if (vendorProfileFlush) clearTimeout(vendorProfileFlush)
1764
1156
  vendorProfileFlush = setTimeout(() => {
1765
- console.log(`pnext vendor profile ${JSON.stringify(vendorProfileReport())}`);
1766
- }, 3000);
1767
- vendorProfileFlush.unref?.();
1157
+ console.log(`pnext vendor profile ${JSON.stringify(vendorProfileReport())}`)
1158
+ }, 3000)
1159
+ vendorProfileFlush.unref?.()
1768
1160
  }
1769
1161
 
1770
1162
  function profiledVendorPlugins(plugins: Plugin[]): Plugin[] {
1771
- if (!vendorProfileEnabled) return plugins;
1163
+ if (!vendorProfileEnabled) return plugins
1772
1164
  return plugins.map(plugin => ({
1773
1165
  name: plugin.name,
1774
1166
  setup(build) {
@@ -1776,22 +1168,22 @@ function profiledVendorPlugins(plugins: Plugin[]): Plugin[] {
1776
1168
  (kind: 'resolve' | 'load', register: (options: any, cb: any) => void) =>
1777
1169
  (options: any, callback: any) =>
1778
1170
  register(options, async (args: any) => {
1779
- vendorProfileCount(`${plugin.name}:${kind}#`);
1780
- const start = performance.now();
1171
+ vendorProfileCount(`${plugin.name}:${kind}#`)
1172
+ const start = performance.now()
1781
1173
  try {
1782
- return await (callback as (a: unknown) => unknown)(args);
1174
+ return await (callback as (a: unknown) => unknown)(args)
1783
1175
  } finally {
1784
- vendorProfileCount(`${plugin.name}:${kind}ms`, performance.now() - start);
1785
- scheduleVendorProfileReport();
1176
+ vendorProfileCount(`${plugin.name}:${kind}ms`, performance.now() - start)
1177
+ scheduleVendorProfileReport()
1786
1178
  }
1787
- });
1179
+ })
1788
1180
  return plugin.setup({
1789
1181
  ...build,
1790
1182
  onResolve: wrap('resolve', build.onResolve.bind(build)),
1791
1183
  onLoad: wrap('load', build.onLoad.bind(build)),
1792
- });
1184
+ })
1793
1185
  },
1794
- }));
1186
+ }))
1795
1187
  }
1796
1188
 
1797
1189
  /** The plugin chain, for whatever the plugin-free build could not express. */
@@ -1808,7 +1200,7 @@ async function buildVendorGroupWithPlugins(
1808
1200
  trace?: VendorBuildTrace,
1809
1201
  preplan = false,
1810
1202
  ) {
1811
- const pluginStart = performance.now();
1203
+ const pluginStart = performance.now()
1812
1204
  const groupPlugins = profiledVendorPlugins([
1813
1205
  serverAssetPlugin(config),
1814
1206
  ...getBundlerExtensions().serverEsbuildPlugins(
@@ -1822,8 +1214,8 @@ async function buildVendorGroupWithPlugins(
1822
1214
  vendorGroupSiblingPlugin(siblings),
1823
1215
  externalPackageDependencyPlugin(config, packageName, target, conditionTarget, context, preplan),
1824
1216
  externalBuiltinBuildPlugin(),
1825
- ]);
1826
- if (trace) trace.plugins = groupPlugins.map(plugin => plugin.name);
1217
+ ])
1218
+ if (trace) trace.plugins = groupPlugins.map(plugin => plugin.name)
1827
1219
  const result = await build({
1828
1220
  entryPoints,
1829
1221
  absWorkingDir: resolveDir,
@@ -1846,11 +1238,11 @@ async function buildVendorGroupWithPlugins(
1846
1238
  ...(trace ? { metafile: true as const } : {}),
1847
1239
  ...serverDefineOptions(),
1848
1240
  plugins: groupPlugins,
1849
- });
1850
- vendorNativeStats.pluginBuilds += 1;
1851
- vendorNativeStats.pluginBuildMs += performance.now() - pluginStart;
1852
- if (trace) trace.metafile = result.metafile;
1853
- return result.outputFiles.map(output => ({ path: output.path, text: output.text }));
1241
+ })
1242
+ vendorNativeStats.pluginBuilds += 1
1243
+ vendorNativeStats.pluginBuildMs += performance.now() - pluginStart
1244
+ if (trace) trace.metafile = result.metafile
1245
+ return result.outputFiles.map(output => ({ path: output.path, text: output.text }))
1854
1246
  }
1855
1247
 
1856
1248
  /** Siblings already compiled stay out of the graph, as plain externals. */
@@ -1858,14 +1250,14 @@ function vendorGroupSiblingPlugin(siblings: ReadonlyMap<string, string>): Plugin
1858
1250
  return {
1859
1251
  name: 'pnext-vendor-group-siblings',
1860
1252
  setup(build) {
1861
- if (siblings.size === 0) return;
1253
+ if (siblings.size === 0) return
1862
1254
  build.onResolve({ filter: /^[^./].*/ }, args => {
1863
- if (args.kind === 'entry-point') return undefined;
1864
- const sibling = siblings.get(args.path);
1865
- return sibling ? { path: sibling, external: true } : undefined;
1866
- });
1255
+ if (args.kind === 'entry-point') return undefined
1256
+ const sibling = siblings.get(args.path)
1257
+ return sibling ? { path: sibling, external: true } : undefined
1258
+ })
1867
1259
  },
1868
- };
1260
+ }
1869
1261
  }
1870
1262
 
1871
1263
  // per-file client boundaries inside a package.
@@ -1890,15 +1282,15 @@ function vendorGroupSiblingPlugin(siblings: ReadonlyMap<string, string>): Plugin
1890
1282
  const vendorClientBoundaryLoads = new WeakMap<
1891
1283
  ResolvedConfig,
1892
1284
  (file: string) => Promise<OnLoadResult | undefined>
1893
- >();
1285
+ >()
1894
1286
 
1895
1287
  function vendorClientBoundaryLoad(config: ResolvedConfig) {
1896
- let load = vendorClientBoundaryLoads.get(config);
1897
- if (load) return load;
1898
- const memo = new Map<string, Promise<OnLoadResult | undefined>>();
1288
+ let load = vendorClientBoundaryLoads.get(config)
1289
+ if (load) return load
1290
+ const memo = new Map<string, Promise<OnLoadResult | undefined>>()
1899
1291
  const scan = async (file: string): Promise<OnLoadResult | undefined> => {
1900
- const source = await readFile(file, 'utf8').catch(() => undefined);
1901
- if (source === undefined || !hasUseClientDirective(source)) return undefined;
1292
+ const source = await readFile(file, 'utf8').catch(() => undefined)
1293
+ if (source === undefined || !hasUseClientDirective(source)) return undefined
1902
1294
  return {
1903
1295
  contents: clientReferenceModuleSource(
1904
1296
  file,
@@ -1907,21 +1299,21 @@ function vendorClientBoundaryLoad(config: ResolvedConfig) {
1907
1299
  ),
1908
1300
  loader: 'js',
1909
1301
  resolveDir: path.dirname(file),
1910
- };
1911
- };
1302
+ }
1303
+ }
1912
1304
  // Only node_modules answers are cached: immutable for the process life, the
1913
1305
  // same assumption the vendor artifact cache makes. First-party files re-read.
1914
1306
  load = file => {
1915
- if (!file.includes(`${path.sep}node_modules${path.sep}`)) return scan(file);
1916
- let pending = memo.get(file);
1307
+ if (!file.includes(`${path.sep}node_modules${path.sep}`)) return scan(file)
1308
+ let pending = memo.get(file)
1917
1309
  if (!pending) {
1918
- pending = scan(file);
1919
- memo.set(file, pending);
1310
+ pending = scan(file)
1311
+ memo.set(file, pending)
1920
1312
  }
1921
- return pending;
1922
- };
1923
- vendorClientBoundaryLoads.set(config, load);
1924
- return load;
1313
+ return pending
1314
+ }
1315
+ vendorClientBoundaryLoads.set(config, load)
1316
+ return load
1925
1317
  }
1926
1318
 
1927
1319
  /**
@@ -1942,12 +1334,12 @@ function vendorPluginOptions(
1942
1334
  !isPagesBundleTarget(conditionTarget) &&
1943
1335
  reactCompatEnabled(config)
1944
1336
  ? vendorClientBoundaryLoad(config)
1945
- : undefined;
1946
- return { vendorClientBoundary: boundary, realPathEntries: true };
1337
+ : undefined
1338
+ return { vendorClientBoundary: boundary, realPathEntries: true }
1947
1339
  }
1948
1340
 
1949
1341
  function isPagesBundleTarget(target: ServerBundleTarget) {
1950
- return target === 'pages' || target === 'pages-api' || target === 'pages-api-edge';
1342
+ return target === 'pages' || target === 'pages-api' || target === 'pages-api-edge'
1951
1343
  }
1952
1344
 
1953
1345
  // plugin-free vendor builds.
@@ -1984,7 +1376,7 @@ function isPagesBundleTarget(target: ServerBundleTarget) {
1984
1376
  */
1985
1377
  function vendorNativeEnabled() {
1986
1378
  // eslint-disable-next-line turbo/no-undeclared-env-vars
1987
- return process.env.PNEXT_VENDOR_NATIVE === '1';
1379
+ return process.env.PNEXT_VENDOR_NATIVE === '1'
1988
1380
  }
1989
1381
 
1990
1382
  /**
@@ -1993,7 +1385,7 @@ function vendorNativeEnabled() {
1993
1385
  * an asset needing real contents), not of the demand, so a retry pays for the
1994
1386
  * discarded build again and lands in the same place.
1995
1387
  */
1996
- const vendorNativeBailed = new Set<string>();
1388
+ const vendorNativeBailed = new Set<string>()
1997
1389
 
1998
1390
  /** How the vendor builds of this process split between the two paths. */
1999
1391
  const vendorNativeStats = {
@@ -2007,10 +1399,10 @@ const vendorNativeStats = {
2007
1399
  planMs: 0,
2008
1400
  verifyMs: 0,
2009
1401
  reasons: [] as string[],
2010
- };
1402
+ }
2011
1403
 
2012
1404
  export function vendorNativeReport() {
2013
- return { ...vendorNativeStats, reasons: vendorNativeStats.reasons.slice(0, 40) };
1405
+ return { ...vendorNativeStats, reasons: vendorNativeStats.reasons.slice(0, 40) }
2014
1406
  }
2015
1407
 
2016
1408
  /**
@@ -2023,44 +1415,44 @@ const vendorEmptyLoaders = {
2023
1415
  '.scss': 'empty',
2024
1416
  '.sass': 'empty',
2025
1417
  '.less': 'empty',
2026
- } satisfies Record<string, Loader>;
1418
+ } satisfies Record<string, Loader>
2027
1419
 
2028
1420
  /** Discovery is a fixpoint; past this a package is not worth another try. */
2029
- const MAX_VENDOR_NATIVE_ROUNDS = 2;
1421
+ const MAX_VENDOR_NATIVE_ROUNDS = 2
2030
1422
 
2031
1423
  interface VendorNativeRequest {
2032
- config: ResolvedConfig;
2033
- target: CompatAliasTarget;
2034
- conditionTarget: ServerBundleTarget;
2035
- resolveDir: string;
1424
+ config: ResolvedConfig
1425
+ target: CompatAliasTarget
1426
+ conditionTarget: ServerBundleTarget
1427
+ resolveDir: string
2036
1428
  /** Cache key for the bail set — the artifact this build is producing. */
2037
- bailKey: string;
1429
+ bailKey: string
2038
1430
  /** The package the entries belong to; its own subpaths are inlined. */
2039
- packageName: string | undefined;
2040
- entryPoints: { in: string; out: string }[] | string[];
2041
- outdir?: string;
2042
- splitting: boolean;
1431
+ packageName: string | undefined
1432
+ entryPoints: { in: string; out: string }[] | string[]
1433
+ outdir?: string
1434
+ splitting: boolean
2043
1435
  /** Demanded siblings already on disk (group builds only). */
2044
- siblings?: ReadonlyMap<string, string>;
1436
+ siblings?: ReadonlyMap<string, string>
2045
1437
  /** Entries THIS build emits, by resolved entry file: artifacts-to-be. */
2046
- ownEntries?: ReadonlyMap<string, string>;
1438
+ ownEntries?: ReadonlyMap<string, string>
2047
1439
  /** Raised from inside another build: it may never wait on anything shared. */
2048
- nested: boolean;
1440
+ nested: boolean
2049
1441
  /** Preplan-safe package: native build with pure ref decisions, no verify. */
2050
- preplan?: boolean;
1442
+ preplan?: boolean
2051
1443
  /** Entry outputs, by emitted path; everything else is a shared chunk. */
2052
- isEntry: (outputPath: string) => boolean;
1444
+ isEntry: (outputPath: string) => boolean
2053
1445
  /** Where a solo build's single output lands, for the verification gate. */
2054
- soloFile?: string;
2055
- context: { nestedExternalFiles: string[] };
1446
+ soloFile?: string
1447
+ context: { nestedExternalFiles: string[] }
2056
1448
  /** Trace-only: filled with the producing pass's plugins/metafile. */
2057
- trace?: VendorBuildTrace;
1449
+ trace?: VendorBuildTrace
2058
1450
  }
2059
1451
 
2060
1452
  /** A repointed reference: the specifier to emit, and the file it must exist at. */
2061
1453
  interface VendorRefTarget {
2062
- specifier: string;
2063
- file: string;
1454
+ specifier: string
1455
+ file: string
2064
1456
  }
2065
1457
 
2066
1458
  /**
@@ -2068,45 +1460,42 @@ interface VendorRefTarget {
2068
1460
  * verified one is a bail, so a caller can treat undefined as "nothing happened" - no artifact is written here.
2069
1461
  */
2070
1462
  async function buildVendorNative(request: VendorNativeRequest) {
2071
- vendorNativeStats.attempts += 1;
2072
- if (
2073
- (!vendorNativeEnabled() && !request.preplan) ||
2074
- vendorNativeBailed.has(request.bailKey)
2075
- ) {
2076
- return undefined;
1463
+ vendorNativeStats.attempts += 1
1464
+ if ((!vendorNativeEnabled() && !request.preplan) || vendorNativeBailed.has(request.bailKey)) {
1465
+ return undefined
2077
1466
  }
2078
1467
  // A bail is remembered so the next demand does not pay for the same
2079
1468
  // discarded build — unless the cause was the fixpoint running out of rounds,
2080
1469
  // which the package shape this build just recorded has already moved on from.
2081
1470
  const bail = (why: string, permanent = true) => {
2082
- if (permanent) vendorNativeBailed.add(request.bailKey);
2083
- vendorNativeStats.bailed += 1;
2084
- vendorNativeStats.reasons.push(`${request.packageName ?? '?'}: ${why}`);
2085
- return undefined;
2086
- };
1471
+ if (permanent) vendorNativeBailed.add(request.bailKey)
1472
+ vendorNativeStats.bailed += 1
1473
+ vendorNativeStats.reasons.push(`${request.packageName ?? '?'}: ${why}`)
1474
+ return undefined
1475
+ }
2087
1476
 
2088
- const conditions = serverBundleConditions(request.conditionTarget);
1477
+ const conditions = serverBundleConditions(request.conditionTarget)
2089
1478
  // What a package has to keep INSIDE its bundle is a property of the package,
2090
1479
  // not of one subpath's build, so it is discovered once and reused by every
2091
1480
  // later build of it — the fixpoint the §4e prototype ran app-wide, amortized
2092
1481
  // per package here.
2093
- const shape = vendorPackageShape(request);
1482
+ const shape = vendorPackageShape(request)
2094
1483
  // Only the FIRST build of a package discovers its shape; the rest wait for that answer instead of racing to
2095
1484
  // the same rounds. Off the slot, because a waiter that keeps its worker is exactly the inversion this is
2096
1485
  // here to fix. A nested build never waits - its own parent is holding a slot for it, and a cycle through two
2097
1486
  // packages would otherwise have nowhere to go.
2098
1487
  if (!request.nested && shape.discovery) {
2099
- await outsideVendorSlot(() => shape.discovery!);
1488
+ await outsideVendorSlot(() => shape.discovery!)
2100
1489
  }
2101
- const pioneer = !shape.discovery;
2102
- let settleDiscovery: (() => void) | undefined;
2103
- if (pioneer) shape.discovery = new Promise<void>(resolve => (settleDiscovery = resolve));
1490
+ const pioneer = !shape.discovery
1491
+ let settleDiscovery: (() => void) | undefined
1492
+ if (pioneer) shape.discovery = new Promise<void>(resolve => (settleDiscovery = resolve))
2104
1493
  try {
2105
- return await runVendorNativeRounds(request, shape, conditions, bail);
1494
+ return await runVendorNativeRounds(request, shape, conditions, bail)
2106
1495
  } finally {
2107
1496
  if (pioneer) {
2108
- shape.discovery = undefined;
2109
- settleDiscovery?.();
1497
+ shape.discovery = undefined
1498
+ settleDiscovery?.()
2110
1499
  }
2111
1500
  }
2112
1501
  }
@@ -2117,29 +1506,28 @@ async function runVendorNativeRounds(
2117
1506
  conditions: string[],
2118
1507
  bail: (why: string, permanent?: boolean) => undefined,
2119
1508
  ) {
2120
- const { config, resolveDir } = request;
1509
+ const { config, resolveDir } = request
2121
1510
 
2122
1511
  for (let round = 1; round <= MAX_VENDOR_NATIVE_ROUNDS; round += 1) {
2123
- let result;
2124
- vendorNativeStats.rounds += 1;
2125
- const buildStart = performance.now();
1512
+ let result
1513
+ vendorNativeStats.rounds += 1
1514
+ const buildStart = performance.now()
2126
1515
  const nativePlugins = getBundlerExtensions().serverEsbuildPlugins(
2127
1516
  config,
2128
1517
  vendorPluginOptions(config, request.target, request.conditionTarget),
2129
- );
2130
- if (request.trace) request.trace.plugins = nativePlugins.map(plugin => plugin.name);
1518
+ )
1519
+ if (request.trace) request.trace.plugins = nativePlugins.map(plugin => plugin.name)
2131
1520
  try {
2132
1521
  result = await build({
2133
- entryPoints: (request.entryPoints as (string | { in: string; out: string })[]).map(
2134
- entry =>
2135
- typeof entry === 'string'
2136
- ? (getBundlerExtensions().serverBundleEntry(entry, resolveDir, entry) ?? entry)
2137
- : {
2138
- ...entry,
2139
- in:
2140
- getBundlerExtensions().serverBundleEntry(entry.in, resolveDir, entry.in) ??
2141
- entry.in,
2142
- },
1522
+ entryPoints: (request.entryPoints as (string | { in: string; out: string })[]).map(entry =>
1523
+ typeof entry === 'string'
1524
+ ? (getBundlerExtensions().serverBundleEntry(entry, resolveDir, entry) ?? entry)
1525
+ : {
1526
+ ...entry,
1527
+ in:
1528
+ getBundlerExtensions().serverBundleEntry(entry.in, resolveDir, entry.in) ??
1529
+ entry.in,
1530
+ },
2143
1531
  ) as string[],
2144
1532
  absWorkingDir: resolveDir,
2145
1533
  bundle: true,
@@ -2164,32 +1552,32 @@ async function runVendorNativeRounds(
2164
1552
  packages: 'external',
2165
1553
  ...(shape.inline.size > 0 ? { alias: Object.fromEntries(shape.inline) } : {}),
2166
1554
  plugins: nativePlugins,
2167
- });
1555
+ })
2168
1556
  } catch (error) {
2169
- vendorNativeStats.buildMs += performance.now() - buildStart;
2170
- return bail((error instanceof Error ? error.message : String(error)).slice(0, 200));
1557
+ vendorNativeStats.buildMs += performance.now() - buildStart
1558
+ return bail((error instanceof Error ? error.message : String(error)).slice(0, 200))
2171
1559
  }
2172
- vendorNativeStats.buildMs += performance.now() - buildStart;
1560
+ vendorNativeStats.buildMs += performance.now() - buildStart
2173
1561
 
2174
- const inputs = Object.keys(result.metafile.inputs);
1562
+ const inputs = Object.keys(result.metafile.inputs)
2175
1563
  // An asset whose stub carries content, or one a configured loader rule
2176
1564
  // claims, is not what `loader: empty` produced. Hand it to the plugins.
2177
1565
  for (const input of inputs) {
2178
- const file = path.resolve(resolveDir, input);
2179
- if (!serverIgnoredAssetFilter.test(file)) continue;
2180
- if (getAssetExtensions().hasLoaderRuleFor(file)) return bail(`loader rule for ${input}`);
1566
+ const file = path.resolve(resolveDir, input)
1567
+ if (!serverIgnoredAssetFilter.test(file)) continue
1568
+ if (getAssetExtensions().hasLoaderRuleFor(file)) return bail(`loader rule for ${input}`)
2181
1569
  if (getCssExtensions().loadCssModuleForClient(file) !== undefined) {
2182
- return bail(`css module ${input}`);
1570
+ return bail(`css module ${input}`)
2183
1571
  }
2184
1572
  }
2185
1573
  // Preplan class is ESM-only across the GRAPH, not just the entry.
2186
1574
  if (request.preplan) {
2187
1575
  for (const input of inputs) {
2188
- const file = path.resolve(resolveDir, input);
2189
- if (serverIgnoredAssetFilter.test(file) || !cachedExistsSync(file)) continue;
2190
- if (/\.[cm]?tsx?$/.test(file)) return bail(`ts input ${input}`);
2191
- if (file.endsWith('.cjs') ||(/\.jsx?$/.test(file) && !isEsmModuleEntry(file))) {
2192
- return bail(`cjs input ${input}`);
1576
+ const file = path.resolve(resolveDir, input)
1577
+ if (serverIgnoredAssetFilter.test(file) || !cachedExistsSync(file)) continue
1578
+ if (/\.[cm]?tsx?$/.test(file)) return bail(`ts input ${input}`)
1579
+ if (file.endsWith('.cjs') || (/\.jsx?$/.test(file) && !isEsmModuleEntry(file))) {
1580
+ return bail(`cjs input ${input}`)
2193
1581
  }
2194
1582
  }
2195
1583
  }
@@ -2198,60 +1586,61 @@ async function runVendorNativeRounds(
2198
1586
  // the worker has already handed its output over. Nothing else in the
2199
1587
  // pipeline can do this — the plugin chain's discovery runs inside a resolve
2200
1588
  // callback, with the bundler still open behind it.
2201
- const planStart = performance.now();
1589
+ const planStart = performance.now()
2202
1590
  const plan = await outsideVendorSlot(() =>
2203
1591
  planVendorNativeRefs(request, result.metafile, conditions),
2204
- );
2205
- vendorNativeStats.planMs += performance.now() - planStart;
2206
- if (!plan) return bail('unrepresentable reference');
1592
+ )
1593
+ vendorNativeStats.planMs += performance.now() - planStart
1594
+ if (!plan) return bail('unrepresentable reference')
2207
1595
  if (plan.inline.size > 0) {
2208
1596
  // Record before deciding: even a bail teaches the package's shape, so the
2209
1597
  // next build of it starts where this one gave up.
2210
- let grew = false;
1598
+ let grew = false
2211
1599
  for (const [specifier, file] of plan.inline) {
2212
- if (shape.inline.has(specifier)) continue;
2213
- shape.inline.set(specifier, file);
2214
- grew = true;
1600
+ if (shape.inline.has(specifier)) continue
1601
+ shape.inline.set(specifier, file)
1602
+ grew = true
2215
1603
  }
2216
- if (!grew) return bail('inline set stopped converging');
2217
- if (round === MAX_VENDOR_NATIVE_ROUNDS) return bail('inlining unsettled', false);
2218
- continue;
1604
+ if (!grew) return bail('inline set stopped converging')
1605
+ if (round === MAX_VENDOR_NATIVE_ROUNDS) return bail('inlining unsettled', false)
1606
+ continue
2219
1607
  }
2220
1608
 
2221
1609
  const outputs = result.outputFiles.map(output => ({
2222
1610
  path: request.soloFile ?? output.path,
2223
- text: rewriteEmittedRefs(output.text, ref =>
2224
- (ref.require ? plan.requires : plan.imports).get(ref.specifier)?.specifier,
1611
+ text: rewriteEmittedRefs(
1612
+ output.text,
1613
+ ref => (ref.require ? plan.requires : plan.imports).get(ref.specifier)?.specifier,
2225
1614
  ),
2226
- }));
1615
+ }))
2227
1616
  const repointed = new Map(
2228
1617
  [...plan.imports.values(), ...plan.requires.values()].map(t => [t.specifier, t.file]),
2229
- );
1618
+ )
2230
1619
  // Preplan mode: the drain barrier + byte-eq gates stand in for the
2231
1620
  // re-bundle verification loop — its cost is one of the two this composes out.
2232
1621
  if (!request.preplan) {
2233
- const verifyStart = performance.now();
1622
+ const verifyStart = performance.now()
2234
1623
  const chunks = new Map(
2235
1624
  outputs.filter(output => !request.isEntry(output.path)).map(o => [o.path, o.text]),
2236
- );
1625
+ )
2237
1626
  for (const output of outputs) {
2238
- if (!request.isEntry(output.path)) continue;
2239
- const failure = await verifyVendorArtifact(output.path, output.text, repointed, chunks);
1627
+ if (!request.isEntry(output.path)) continue
1628
+ const failure = await verifyVendorArtifact(output.path, output.text, repointed, chunks)
2240
1629
  if (failure) {
2241
- vendorNativeStats.verifyMs += performance.now() - verifyStart;
2242
- return bail(`verification: ${failure}`);
1630
+ vendorNativeStats.verifyMs += performance.now() - verifyStart
1631
+ return bail(`verification: ${failure}`)
2243
1632
  }
2244
1633
  }
2245
- vendorNativeStats.verifyMs += performance.now() - verifyStart;
1634
+ vendorNativeStats.verifyMs += performance.now() - verifyStart
2246
1635
  } else {
2247
- vendorPreplanStats.composedBuilds += 1;
2248
- schedulePreplanReport();
1636
+ vendorPreplanStats.composedBuilds += 1
1637
+ schedulePreplanReport()
2249
1638
  }
2250
- vendorNativeStats.native += 1;
2251
- if (request.trace) request.trace.metafile = result.metafile;
2252
- return outputs;
1639
+ vendorNativeStats.native += 1
1640
+ if (request.trace) request.trace.metafile = result.metafile
1641
+ return outputs
2253
1642
  }
2254
- return undefined;
1643
+ return undefined
2255
1644
  }
2256
1645
 
2257
1646
  /**
@@ -2262,38 +1651,38 @@ async function runVendorNativeRounds(
2262
1651
  */
2263
1652
  interface VendorPackageShape {
2264
1653
  /** Specifier -> file it must be bundled from, as an esbuild `alias` entry. */
2265
- inline: Map<string, string>;
1654
+ inline: Map<string, string>
2266
1655
  /** Set while one build is discovering it; every other build awaits this. */
2267
- discovery?: Promise<void>;
1656
+ discovery?: Promise<void>
2268
1657
  }
2269
1658
 
2270
- const vendorPackageShapes = new Map<string, VendorPackageShape>();
1659
+ const vendorPackageShapes = new Map<string, VendorPackageShape>()
2271
1660
 
2272
1661
  function vendorPackageShape(request: VendorNativeRequest): VendorPackageShape {
2273
- const entry = (request.entryPoints as { in?: string }[])[0];
2274
- const first = typeof entry === 'string' ? entry : entry?.in;
1662
+ const entry = (request.entryPoints as { in?: string }[])[0]
1663
+ const first = typeof entry === 'string' ? entry : entry?.in
2275
1664
  // A workspace package resolves to its real source path, which has no
2276
1665
  // `node_modules` segment to key on — walk up to its manifest instead.
2277
1666
  const root =
2278
1667
  (first && request.packageName && vendorPackageRoot(first, request.packageName)) ??
2279
- (first && nearestManifestDir(path.dirname(first)));
2280
- if (!root) return { inline: new Map() };
2281
- const key = `${root}\0${request.target}\0${request.conditionTarget}`;
2282
- let shape = vendorPackageShapes.get(key);
1668
+ (first && nearestManifestDir(path.dirname(first)))
1669
+ if (!root) return { inline: new Map() }
1670
+ const key = `${root}\0${request.target}\0${request.conditionTarget}`
1671
+ let shape = vendorPackageShapes.get(key)
2283
1672
  if (!shape) {
2284
- shape = { inline: new Map() };
2285
- vendorPackageShapes.set(key, shape);
1673
+ shape = { inline: new Map() }
1674
+ vendorPackageShapes.set(key, shape)
2286
1675
  }
2287
- return shape;
1676
+ return shape
2288
1677
  }
2289
1678
 
2290
1679
  function nearestManifestDir(from: string) {
2291
- let dir = from;
1680
+ let dir = from
2292
1681
  while (true) {
2293
- if (existsSync(path.join(dir, 'package.json'))) return dir;
2294
- const parent = path.dirname(dir);
2295
- if (parent === dir) return undefined;
2296
- dir = parent;
1682
+ if (existsSync(path.join(dir, 'package.json'))) return dir
1683
+ const parent = path.dirname(dir)
1684
+ if (parent === dir) return undefined
1685
+ dir = parent
2297
1686
  }
2298
1687
  }
2299
1688
 
@@ -2307,41 +1696,41 @@ async function planVendorNativeRefs(
2307
1696
  metafile: Metafile,
2308
1697
  conditions: string[],
2309
1698
  ) {
2310
- const imports = new Map<string, VendorRefTarget>();
2311
- const requires = new Map<string, VendorRefTarget>();
2312
- const inline = new Map<string, string>();
2313
- const settled = new Set<string>();
1699
+ const imports = new Map<string, VendorRefTarget>()
1700
+ const requires = new Map<string, VendorRefTarget>()
1701
+ const inline = new Map<string, string>()
1702
+ const settled = new Set<string>()
2314
1703
 
2315
1704
  for (const [input, meta] of Object.entries(metafile.inputs)) {
2316
1705
  // Decisions are a property of the DIRECTORY an import sits in, never of the
2317
1706
  // file — so a package's several hundred modules all asking for `react`
2318
1707
  // decide once, and the answer is reused across builds.
2319
- const importerFile = path.resolve(request.resolveDir, input);
2320
- const importerDir = path.dirname(importerFile);
1708
+ const importerFile = path.resolve(request.resolveDir, input)
1709
+ const importerDir = path.dirname(importerFile)
2321
1710
  for (const reference of meta.imports) {
2322
- if (!reference.external) continue;
2323
- const specifier = reference.path;
2324
- const isRequire = reference.kind === 'require-call';
2325
- const key = `${isRequire ? 'r' : 'i'}\0${specifier}\0${importerDir}`;
2326
- if (settled.has(key)) continue;
2327
- settled.add(key);
1711
+ if (!reference.external) continue
1712
+ const specifier = reference.path
1713
+ const isRequire = reference.kind === 'require-call'
1714
+ const key = `${isRequire ? 'r' : 'i'}\0${specifier}\0${importerDir}`
1715
+ if (settled.has(key)) continue
1716
+ settled.add(key)
2328
1717
 
2329
1718
  // Same package. A subpath THIS build already emits is repointed at that artifact; a sibling on disk at
2330
1719
  // its own. Everything else becomes its own vendor demand rather than being inlined here - inlining is
2331
1720
  // what makes a package's shared graph get re-parsed once per subpath, the cost this whole pipeline
2332
1721
  // exists to remove. Request-scoped, so it sits in front of the shared decision cache.
2333
1722
  if (packageNameFromSpecifier(specifier) === request.packageName) {
2334
- const sibling = request.siblings?.get(specifier);
1723
+ const sibling = request.siblings?.get(specifier)
2335
1724
  if (sibling) {
2336
1725
  if (!record(imports, specifier, { specifier: sibling, file: fileURLToPath(sibling) })) {
2337
- return undefined;
1726
+ return undefined
2338
1727
  }
2339
- continue;
1728
+ continue
2340
1729
  }
2341
- const own = ownEntryArtifact(request, specifier, importerDir, conditions);
1730
+ const own = ownEntryArtifact(request, specifier, importerDir, conditions)
2342
1731
  if (own) {
2343
- if (!record(imports, specifier, own)) return undefined;
2344
- continue;
1732
+ if (!record(imports, specifier, own)) return undefined
1733
+ continue
2345
1734
  }
2346
1735
  // A subpath nothing demanded: it comes back inside, as the plugin build
2347
1736
  // would have had it. Demanding it instead can close a cycle back onto
@@ -2351,26 +1740,26 @@ async function planVendorNativeRefs(
2351
1740
  path.join(importerDir, 'pnext-resolve.ts'),
2352
1741
  specifier,
2353
1742
  conditions,
2354
- );
2355
- if (!resolved) return undefined;
2356
- inline.set(specifier, resolved);
2357
- continue;
1743
+ )
1744
+ if (!resolved) return undefined
1745
+ inline.set(specifier, resolved)
1746
+ continue
2358
1747
  }
2359
1748
 
2360
1749
  const decision = await (request.preplan
2361
1750
  ? preplanVendorRef(request, specifier, importerFile, isRequire, conditions)
2362
- : decideVendorRef(request, specifier, importerFile, isRequire, conditions));
2363
- if (!decision) return undefined;
2364
- if (decision.kind === 'keep') continue;
1751
+ : decideVendorRef(request, specifier, importerFile, isRequire, conditions))
1752
+ if (!decision) return undefined
1753
+ if (decision.kind === 'keep') continue
2365
1754
  if (decision.kind === 'inline') {
2366
- inline.set(specifier, decision.file);
2367
- continue;
1755
+ inline.set(specifier, decision.file)
1756
+ continue
2368
1757
  }
2369
- if (decision.nested) request.context.nestedExternalFiles.push(decision.nested);
2370
- if (!record(isRequire ? requires : imports, specifier, decision.target)) return undefined;
1758
+ if (decision.nested) request.context.nestedExternalFiles.push(decision.nested)
1759
+ if (!record(isRequire ? requires : imports, specifier, decision.target)) return undefined
2371
1760
  }
2372
1761
  }
2373
- return { imports, requires, inline };
1762
+ return { imports, requires, inline }
2374
1763
  }
2375
1764
 
2376
1765
  /**
@@ -2384,22 +1773,22 @@ function ownEntryArtifact(
2384
1773
  importerDir: string,
2385
1774
  conditions: string[],
2386
1775
  ): VendorRefTarget | undefined {
2387
- if (!request.ownEntries?.size) return undefined;
1776
+ if (!request.ownEntries?.size) return undefined
2388
1777
  const entry = resolveVendorPackageSpecifier(
2389
1778
  request.config.root,
2390
1779
  path.join(importerDir, 'pnext-resolve.ts'),
2391
1780
  specifier,
2392
1781
  conditions,
2393
- );
2394
- const artifact = entry && request.ownEntries.get(entry);
2395
- return artifact ? { specifier: pathToFileHref(artifact), file: artifact } : undefined;
1782
+ )
1783
+ const artifact = entry && request.ownEntries.get(entry)
1784
+ return artifact ? { specifier: pathToFileHref(artifact), file: artifact } : undefined
2396
1785
  }
2397
1786
 
2398
1787
  type VendorRefDecision =
2399
1788
  | { kind: 'keep' }
2400
1789
  /** Must be bundled: `file` is what an alias entry points the specifier at. */
2401
1790
  | { kind: 'inline'; file: string }
2402
- | { kind: 'repoint'; target: VendorRefTarget; nested?: string };
1791
+ | { kind: 'repoint'; target: VendorRefTarget; nested?: string }
2403
1792
 
2404
1793
  /**
2405
1794
  * Decisions are pure in (layer, specifier, importing directory), and the same
@@ -2407,13 +1796,13 @@ type VendorRefDecision =
2407
1796
  * build that meets the reference again. Without this the post-pass re-runs the
2408
1797
  * resolver work the plugin chain at least only did once per bundle.
2409
1798
  */
2410
- const vendorRefDecisions = new Map<string, Promise<VendorRefDecision | undefined>>();
1799
+ const vendorRefDecisions = new Map<string, Promise<VendorRefDecision | undefined>>()
2411
1800
 
2412
1801
  /** Everything the plugin-free path learned about packages, forgotten. */
2413
1802
  function clearVendorNativeCaches() {
2414
- vendorRefDecisions.clear();
2415
- vendorNativeBailed.clear();
2416
- vendorPackageShapes.clear();
1803
+ vendorRefDecisions.clear()
1804
+ vendorNativeBailed.clear()
1805
+ vendorPackageShapes.clear()
2417
1806
  }
2418
1807
 
2419
1808
  /**
@@ -2428,14 +1817,14 @@ function preplanVendorRef(
2428
1817
  isRequire: boolean,
2429
1818
  conditions: string[],
2430
1819
  ) {
2431
- const importerDir = path.dirname(importerFile);
2432
- const key = `p\0${request.target}\0${request.conditionTarget}\0${isRequire ? 'r' : 'i'}\0${specifier}\0${importerDir}`;
2433
- let decision = vendorRefDecisions.get(key);
1820
+ const importerDir = path.dirname(importerFile)
1821
+ const key = `p\0${request.target}\0${request.conditionTarget}\0${isRequire ? 'r' : 'i'}\0${specifier}\0${importerDir}`
1822
+ let decision = vendorRefDecisions.get(key)
2434
1823
  if (!decision) {
2435
- decision = preplanVendorRefUncached(request, specifier, importerFile, isRequire, conditions);
2436
- vendorRefDecisions.set(key, decision);
1824
+ decision = preplanVendorRefUncached(request, specifier, importerFile, isRequire, conditions)
1825
+ vendorRefDecisions.set(key, decision)
2437
1826
  }
2438
- return decision;
1827
+ return decision
2439
1828
  }
2440
1829
 
2441
1830
  async function preplanVendorRefUncached(
@@ -2445,18 +1834,18 @@ async function preplanVendorRefUncached(
2445
1834
  isRequire: boolean,
2446
1835
  conditions: string[],
2447
1836
  ): Promise<VendorRefDecision | undefined> {
2448
- const { config, target } = request;
2449
- const importerDir = path.dirname(importerFile);
2450
- const keep = { kind: 'keep' } as const;
2451
- const depName = packageNameFromSpecifier(specifier);
2452
- if (depName && (builtinModules.includes(depName) || isBunBuiltin(depName))) return keep;
1837
+ const { config, target } = request
1838
+ const importerDir = path.dirname(importerFile)
1839
+ const keep = { kind: 'keep' } as const
1840
+ const depName = packageNameFromSpecifier(specifier)
1841
+ if (depName && (builtinModules.includes(depName) || isBunBuiltin(depName))) return keep
2453
1842
  // Require-of-external is the async-interop shape that 500ed; not this class.
2454
- if (isRequire) return undefined;
1843
+ if (isRequire) return undefined
2455
1844
  if (aliasSpecifierFilter.test(specifier)) {
2456
- const resolved = coreAliases(config, target)[specifier] ?? firstAliasForSpecifier(specifier);
2457
- if (!resolved) return undefined;
2458
- if (!path.isAbsolute(resolved)) return keep;
2459
- return { kind: 'repoint', target: { specifier: pathToFileHref(resolved), file: resolved } };
1845
+ const resolved = coreAliases(config, target)[specifier] ?? firstAliasForSpecifier(specifier)
1846
+ if (!resolved) return undefined
1847
+ if (!path.isAbsolute(resolved)) return keep
1848
+ return { kind: 'repoint', target: { specifier: pathToFileHref(resolved), file: resolved } }
2460
1849
  }
2461
1850
  if (serverIgnoredAssetFilter.test(specifier)) {
2462
1851
  const asset = resolveVendorPackageSpecifier(
@@ -2464,24 +1853,24 @@ async function preplanVendorRefUncached(
2464
1853
  path.join(importerDir, 'pnext-resolve.ts'),
2465
1854
  specifier,
2466
1855
  conditions,
2467
- );
2468
- return asset ? { kind: 'inline', file: asset } : undefined;
1856
+ )
1857
+ return asset ? { kind: 'inline', file: asset } : undefined
2469
1858
  }
2470
- if (!depName || !isPackageSpecifier(specifier)) return undefined;
1859
+ if (!depName || !isPackageSpecifier(specifier)) return undefined
2471
1860
  if (getExternalPackagePolicy().external(depName)) {
2472
- const fromImporter = resolveNestedPackageFromImporter(importerFile, specifier, conditions);
1861
+ const fromImporter = resolveNestedPackageFromImporter(importerFile, specifier, conditions)
2473
1862
  const fromRoot = resolveVendorPackageSpecifier(
2474
1863
  config.root,
2475
1864
  path.join(config.root, 'pnext-resolve.ts'),
2476
1865
  specifier,
2477
1866
  conditions,
2478
- );
2479
- if (!fromImporter || fromImporter === fromRoot) return keep;
1867
+ )
1868
+ if (!fromImporter || fromImporter === fromRoot) return keep
2480
1869
  return {
2481
1870
  kind: 'repoint',
2482
1871
  target: { specifier: fromImporter, file: fromImporter },
2483
1872
  nested: fromImporter,
2484
- };
1873
+ }
2485
1874
  }
2486
1875
  const planned = await preplanDependencyDecision(
2487
1876
  config,
@@ -2489,12 +1878,12 @@ async function preplanVendorRefUncached(
2489
1878
  request.conditionTarget,
2490
1879
  specifier,
2491
1880
  importerDir,
2492
- );
2493
- if (!planned?.path) return undefined;
1881
+ )
1882
+ if (!planned?.path) return undefined
2494
1883
  return {
2495
1884
  kind: 'repoint',
2496
1885
  target: { specifier: planned.path, file: fileURLToPath(planned.path) },
2497
- };
1886
+ }
2498
1887
  }
2499
1888
 
2500
1889
  function decideVendorRef(
@@ -2504,14 +1893,14 @@ function decideVendorRef(
2504
1893
  isRequire: boolean,
2505
1894
  conditions: string[],
2506
1895
  ) {
2507
- const importerDir = path.dirname(importerFile);
2508
- const key = `${request.target}\0${request.conditionTarget}\0${isRequire ? 'r' : 'i'}\0${specifier}\0${importerDir}`;
2509
- let decision = vendorRefDecisions.get(key);
1896
+ const importerDir = path.dirname(importerFile)
1897
+ const key = `${request.target}\0${request.conditionTarget}\0${isRequire ? 'r' : 'i'}\0${specifier}\0${importerDir}`
1898
+ let decision = vendorRefDecisions.get(key)
2510
1899
  if (!decision) {
2511
- decision = decideVendorRefUncached(request, specifier, importerFile, isRequire, conditions);
2512
- vendorRefDecisions.set(key, decision);
1900
+ decision = decideVendorRefUncached(request, specifier, importerFile, isRequire, conditions)
1901
+ vendorRefDecisions.set(key, decision)
2513
1902
  }
2514
- return decision;
1903
+ return decision
2515
1904
  }
2516
1905
 
2517
1906
  async function decideVendorRefUncached(
@@ -2521,30 +1910,30 @@ async function decideVendorRefUncached(
2521
1910
  isRequire: boolean,
2522
1911
  conditions: string[],
2523
1912
  ): Promise<VendorRefDecision | undefined> {
2524
- const { config, target, conditionTarget } = request;
2525
- const importerDir = path.dirname(importerFile);
2526
- const keep = { kind: 'keep' } as const;
2527
- const depName = packageNameFromSpecifier(specifier);
2528
- if (depName && (builtinModules.includes(depName) || isBunBuiltin(depName))) return keep;
1913
+ const { config, target, conditionTarget } = request
1914
+ const importerDir = path.dirname(importerFile)
1915
+ const keep = { kind: 'keep' } as const
1916
+ const depName = packageNameFromSpecifier(specifier)
1917
+ if (depName && (builtinModules.includes(depName) || isBunBuiltin(depName))) return keep
2529
1918
 
2530
1919
  // Framework aliases. A require-call reads the react-server layer — a package
2531
1920
  // probing `'useState' in require('react')` must see false — so the two kinds
2532
1921
  // of reference to one specifier are repointed separately.
2533
1922
  if (aliasSpecifierFilter.test(specifier)) {
2534
- const resolved = coreAliases(config, target)[specifier] ?? firstAliasForSpecifier(specifier);
2535
- if (!resolved) return undefined;
1923
+ const resolved = coreAliases(config, target)[specifier] ?? firstAliasForSpecifier(specifier)
1924
+ if (!resolved) return undefined
2536
1925
  if (isRequire) {
2537
1926
  // A CommonJS shim swap (`next/navigation` -> navigation.cjs) is the one
2538
1927
  // alias the plugin BUNDLES; leave that build to the plugin.
2539
- if (serverRequireAlias(specifier, resolved, 'require-call') !== resolved) return undefined;
1928
+ if (serverRequireAlias(specifier, resolved, 'require-call') !== resolved) return undefined
2540
1929
  const layerAliases =
2541
- target === 'server' ? getImportAliasExtensions().reactServerLayerAliases(config) : undefined;
2542
- const layer = layerAliases?.[specifier] ?? resolved;
2543
- const file = path.isAbsolute(layer) ? layer : require.resolve(layer);
2544
- return { kind: 'repoint', target: { specifier: file, file } };
1930
+ target === 'server' ? getImportAliasExtensions().reactServerLayerAliases(config) : undefined
1931
+ const layer = layerAliases?.[specifier] ?? resolved
1932
+ const file = path.isAbsolute(layer) ? layer : require.resolve(layer)
1933
+ return { kind: 'repoint', target: { specifier: file, file } }
2545
1934
  }
2546
- if (!path.isAbsolute(resolved)) return keep;
2547
- return { kind: 'repoint', target: { specifier: pathToFileHref(resolved), file: resolved } };
1935
+ if (!path.isAbsolute(resolved)) return keep
1936
+ return { kind: 'repoint', target: { specifier: pathToFileHref(resolved), file: resolved } }
2548
1937
  }
2549
1938
 
2550
1939
  // An asset the bundler externalized (a cross-package CSS import) has to come
@@ -2555,27 +1944,27 @@ async function decideVendorRefUncached(
2555
1944
  path.join(importerDir, 'pnext-resolve.ts'),
2556
1945
  specifier,
2557
1946
  conditions,
2558
- );
2559
- return asset ? { kind: 'inline', file: asset } : undefined;
1947
+ )
1948
+ return asset ? { kind: 'inline', file: asset } : undefined
2560
1949
  }
2561
- if (!depName || !isPackageSpecifier(specifier)) return undefined;
1950
+ if (!depName || !isPackageSpecifier(specifier)) return undefined
2562
1951
 
2563
1952
  // B7: a serverExternalPackage stays external, pinned to the importing
2564
1953
  // package's own copy when that differs from the hoisted root version.
2565
1954
  if (getExternalPackagePolicy().external(depName)) {
2566
- const fromImporter = resolveNestedPackageFromImporter(importerFile, specifier, conditions);
1955
+ const fromImporter = resolveNestedPackageFromImporter(importerFile, specifier, conditions)
2567
1956
  const fromRoot = resolveVendorPackageSpecifier(
2568
1957
  config.root,
2569
1958
  path.join(config.root, 'pnext-resolve.ts'),
2570
1959
  specifier,
2571
1960
  conditions,
2572
- );
2573
- if (!fromImporter || fromImporter === fromRoot) return keep;
1961
+ )
1962
+ if (!fromImporter || fromImporter === fromRoot) return keep
2574
1963
  return {
2575
1964
  kind: 'repoint',
2576
1965
  target: { specifier: fromImporter, file: fromImporter },
2577
1966
  nested: fromImporter,
2578
- };
1967
+ }
2579
1968
  }
2580
1969
 
2581
1970
  const resolvedEntry = resolveVendorPackageSpecifier(
@@ -2583,15 +1972,15 @@ async function decideVendorRefUncached(
2583
1972
  path.join(importerDir, 'pnext-resolve.ts'),
2584
1973
  specifier,
2585
1974
  conditions,
2586
- );
1975
+ )
2587
1976
  // A CommonJS dependency loses its named exports when externalized, so it is
2588
1977
  // inlined unless a verified facade can republish them.
2589
- const commonJs = Boolean(resolvedEntry) && !isEsmModuleEntry(resolvedEntry!);
1978
+ const commonJs = Boolean(resolvedEntry) && !isEsmModuleEntry(resolvedEntry!)
2590
1979
  if (commonJs && !(await cjsEntryMayHaveNamedExports(resolvedEntry!))) {
2591
- return { kind: 'inline', file: resolvedEntry! };
1980
+ return { kind: 'inline', file: resolvedEntry! }
2592
1981
  }
2593
- let vendored: string;
2594
- const tv = performance.now();
1982
+ let vendored: string
1983
+ const tv = performance.now()
2595
1984
  try {
2596
1985
  vendored = await externalServerPackageHref(
2597
1986
  config,
@@ -2601,10 +1990,10 @@ async function decideVendorRefUncached(
2601
1990
  conditionTarget,
2602
1991
  // Raised from inside a running build: must not queue behind it.
2603
1992
  true,
2604
- );
1993
+ )
2605
1994
  } catch (error) {
2606
- if (isResolveFailure(error)) return keep;
2607
- return undefined;
1995
+ if (isResolveFailure(error)) return keep
1996
+ return undefined
2608
1997
  }
2609
1998
  if (vendorTraceEnabled()) {
2610
1999
  vendorTraceRow({
@@ -2614,23 +2003,23 @@ async function decideVendorRefUncached(
2614
2003
  fromCondition: conditionTarget,
2615
2004
  to: specifier,
2616
2005
  toResolveDir: importerDir,
2617
- });
2006
+ })
2618
2007
  }
2619
2008
  if (commonJs) {
2620
- heavyProfRow({ k: 'dep-build-wait', site: 'ref', spec: specifier, ms: performance.now() - tv });
2621
- const tf = performance.now();
2622
- const facade = await cjsNamedExportFacade(fileURLToPath(vendored), vendored);
2623
- heavyProfRow({ k: 'facade-wait', site: 'ref', spec: specifier, ms: performance.now() - tf });
2624
- if (!facade) return { kind: 'inline', file: resolvedEntry! };
2009
+ heavyProfRow({ k: 'dep-build-wait', site: 'ref', spec: specifier, ms: performance.now() - tv })
2010
+ const tf = performance.now()
2011
+ const facade = await cjsNamedExportFacade(fileURLToPath(vendored), vendored)
2012
+ heavyProfRow({ k: 'facade-wait', site: 'ref', spec: specifier, ms: performance.now() - tf })
2013
+ if (!facade) return { kind: 'inline', file: resolvedEntry! }
2625
2014
  return {
2626
2015
  kind: 'repoint',
2627
2016
  target: { specifier: pathToFileHref(facade), file: facade },
2628
- };
2017
+ }
2629
2018
  }
2630
2019
  return {
2631
2020
  kind: 'repoint',
2632
2021
  target: { specifier: vendored, file: fileURLToPath(vendored) },
2633
- };
2022
+ }
2634
2023
  }
2635
2024
 
2636
2025
  /**
@@ -2638,15 +2027,11 @@ async function decideVendorRefUncached(
2638
2027
  * DIFFERENTLY (a pinned nested version for one of them) cannot be rewritten in place - that is a bail, not a
2639
2028
  * choice.
2640
2029
  */
2641
- function record(
2642
- targets: Map<string, VendorRefTarget>,
2643
- specifier: string,
2644
- target: VendorRefTarget,
2645
- ) {
2646
- const existing = targets.get(specifier);
2647
- if (existing) return existing.specifier === target.specifier;
2648
- targets.set(specifier, target);
2649
- return true;
2030
+ function record(targets: Map<string, VendorRefTarget>, specifier: string, target: VendorRefTarget) {
2031
+ const existing = targets.get(specifier)
2032
+ if (existing) return existing.specifier === target.specifier
2033
+ targets.set(specifier, target)
2034
+ return true
2650
2035
  }
2651
2036
 
2652
2037
  async function writeExternalServerPackageBundle(
@@ -2658,27 +2043,29 @@ async function writeExternalServerPackageBundle(
2658
2043
  conditionTarget: ServerBundleTarget,
2659
2044
  nested: boolean,
2660
2045
  ) {
2661
- const context = { nestedExternalFiles: [] as string[] };
2046
+ const context = { nestedExternalFiles: [] as string[] }
2662
2047
  // The layer is part of the label: the same specifier legitimately compiles
2663
2048
  // once per target, and a profile that cannot tell those apart reads as
2664
2049
  // duplication that is not there.
2665
- const bundled = await profileRuntimeStep(`vendor bundle ${specifier} [${target}/${conditionTarget}]`, () =>
2666
- bundleExternalPackage(
2667
- config,
2668
- specifier,
2669
- target,
2670
- resolveDir,
2671
- conditionTarget,
2672
- context,
2673
- file,
2674
- nested,
2675
- ),
2676
- );
2050
+ const bundled = await profileRuntimeStep(
2051
+ `vendor bundle ${specifier} [${target}/${conditionTarget}]`,
2052
+ () =>
2053
+ bundleExternalPackage(
2054
+ config,
2055
+ specifier,
2056
+ target,
2057
+ resolveDir,
2058
+ conditionTarget,
2059
+ context,
2060
+ file,
2061
+ nested,
2062
+ ),
2063
+ )
2677
2064
  // Packages can ship 'use cache' functions too (Next allows it); client
2678
2065
  // bundles keep the source untouched.
2679
2066
  const code =
2680
- target === 'server' ? applyBundledSourceTransforms(bundled.code, specifier) : bundled.code;
2681
- const id = await writeVendorArtifact(file, code);
2067
+ target === 'server' ? applyBundledSourceTransforms(bundled.code, specifier) : bundled.code
2068
+ const id = await writeVendorArtifact(file, code)
2682
2069
  recordVendorLayerBuild(
2683
2070
  config,
2684
2071
  resolveVendorEntry(config, specifier, resolveDir, conditionTarget),
@@ -2687,39 +2074,39 @@ async function writeExternalServerPackageBundle(
2687
2074
  bundled.metafile,
2688
2075
  code,
2689
2076
  id,
2690
- );
2077
+ )
2691
2078
  // When this vendor bundle pins a transitive external to a nested version distinct from the hoisted root
2692
2079
  // copy, Node/webpack file tracing surfaces that dependency's source as a `.js` artifact in the server
2693
2080
  // output. Emit a `.js` trace copy of each such source so build tooling that scans `**/*.js` - never `.mjs` -
2694
2081
  // observes the bundled transitive too. These are trace artifacts only; nothing imports them.
2695
- await emitNestedExternalTraceCopies(file, context.nestedExternalFiles);
2696
- return file;
2082
+ await emitNestedExternalTraceCopies(file, context.nestedExternalFiles)
2083
+ return file
2697
2084
  }
2698
2085
 
2699
2086
  /** Trace-only side channel: what the esbuild pass that produced the code did. */
2700
2087
  interface VendorBuildTrace {
2701
- plugins: string[];
2702
- entry?: string;
2703
- metafile?: Metafile;
2088
+ plugins: string[]
2089
+ entry?: string
2090
+ metafile?: Metafile
2704
2091
  }
2705
2092
 
2706
2093
  /** Emit one JSONL build row from a finished (or failed) vendor esbuild pass. */
2707
2094
  function traceVendorBuild(row: {
2708
- seq: number;
2709
- specifier: string;
2710
- resolveDir: string;
2711
- target: string;
2712
- conditionTarget: ServerBundleTarget;
2713
- conditions: string[];
2714
- startMs: number;
2715
- outBytes: number;
2716
- trace: VendorBuildTrace;
2717
- ok: boolean;
2718
- error?: string;
2095
+ seq: number
2096
+ specifier: string
2097
+ resolveDir: string
2098
+ target: string
2099
+ conditionTarget: ServerBundleTarget
2100
+ conditions: string[]
2101
+ startMs: number
2102
+ outBytes: number
2103
+ trace: VendorBuildTrace
2104
+ ok: boolean
2105
+ error?: string
2719
2106
  }) {
2720
- const inputs = row.trace.metafile?.inputs ?? {};
2721
- const inputFiles = Object.keys(inputs);
2722
- const endMs = performance.now();
2107
+ const inputs = row.trace.metafile?.inputs ?? {}
2108
+ const inputFiles = Object.keys(inputs)
2109
+ const endMs = performance.now()
2723
2110
  vendorTraceRow({
2724
2111
  seq: row.seq,
2725
2112
  specifier: row.specifier,
@@ -2738,7 +2125,7 @@ function traceVendorBuild(row: {
2738
2125
  plugins: row.trace.plugins,
2739
2126
  ok: row.ok,
2740
2127
  ...(row.error ? { error: row.error } : {}),
2741
- });
2128
+ })
2742
2129
  }
2743
2130
 
2744
2131
  async function bundleExternalPackage(
@@ -2762,11 +2149,11 @@ async function bundleExternalPackage(
2762
2149
  context,
2763
2150
  file,
2764
2151
  nested,
2765
- );
2152
+ )
2766
2153
  }
2767
- const trace: VendorBuildTrace = { plugins: [] };
2768
- const seq = nextVendorTraceSeq();
2769
- const startMs = performance.now();
2154
+ const trace: VendorBuildTrace = { plugins: [] }
2155
+ const seq = nextVendorTraceSeq()
2156
+ const startMs = performance.now()
2770
2157
  const common = {
2771
2158
  seq,
2772
2159
  specifier,
@@ -2776,7 +2163,7 @@ async function bundleExternalPackage(
2776
2163
  conditions: serverBundleConditions(conditionTarget),
2777
2164
  startMs,
2778
2165
  trace,
2779
- };
2166
+ }
2780
2167
  try {
2781
2168
  const result = await bundleExternalPackageImpl(
2782
2169
  config,
@@ -2788,17 +2175,17 @@ async function bundleExternalPackage(
2788
2175
  file,
2789
2176
  nested,
2790
2177
  trace,
2791
- );
2792
- traceVendorBuild({ ...common, outBytes: result.code.length, ok: true });
2793
- return result;
2178
+ )
2179
+ traceVendorBuild({ ...common, outBytes: result.code.length, ok: true })
2180
+ return result
2794
2181
  } catch (error) {
2795
2182
  traceVendorBuild({
2796
2183
  ...common,
2797
2184
  outBytes: 0,
2798
2185
  ok: false,
2799
2186
  error: (error instanceof Error ? error.message : String(error)).slice(0, 300),
2800
- });
2801
- throw error;
2187
+ })
2188
+ throw error
2802
2189
  }
2803
2190
  }
2804
2191
 
@@ -2814,13 +2201,13 @@ async function bundleExternalPackageImpl(
2814
2201
  nested = false,
2815
2202
  trace?: VendorBuildTrace,
2816
2203
  ) {
2817
- const packageName = packageNameFromSpecifier(specifier);
2204
+ const packageName = packageNameFromSpecifier(specifier)
2818
2205
  const resolvedEntry = resolveVendorPackageSpecifier(
2819
2206
  config.root,
2820
2207
  path.join(resolveDir, 'pnext-resolve.ts'),
2821
2208
  specifier,
2822
2209
  serverBundleConditions(conditionTarget),
2823
- );
2210
+ )
2824
2211
  const requiredEntry =
2825
2212
  resolvedEntry &&
2826
2213
  !isEsmModuleFile(resolvedEntry) &&
@@ -2831,18 +2218,18 @@ async function bundleExternalPackageImpl(
2831
2218
  specifier,
2832
2219
  serverBundleRequireConditions(conditionTarget),
2833
2220
  )
2834
- : undefined;
2221
+ : undefined
2835
2222
  if (
2836
2223
  packageName &&
2837
2224
  resolvedEntry?.match(/\.[cm]?tsx?$/) &&
2838
2225
  !getExternalPackagePolicy().transpile(packageName)
2839
2226
  ) {
2840
- const parseFailure = `${path.relative(config.root, resolvedEntry)}\nModule parse failed: Unexpected token`;
2227
+ const parseFailure = `${path.relative(config.root, resolvedEntry)}\nModule parse failed: Unexpected token`
2841
2228
  // Printed directly as well: the digest log path inspects the error's stack,
2842
2229
  // which some runtimes emit without the message - the CLI output must always
2843
2230
  // carry the parse failure (Next's transpilePackages error contract).
2844
- console.error(`⨯ Error: ${parseFailure}`);
2845
- throw new Error(parseFailure);
2231
+ console.error(`⨯ Error: ${parseFailure}`)
2232
+ throw new Error(parseFailure)
2846
2233
  }
2847
2234
  const entry =
2848
2235
  requiredEntry ??
@@ -2852,12 +2239,12 @@ async function bundleExternalPackageImpl(
2852
2239
  specifier,
2853
2240
  serverBundleConditions(conditionTarget),
2854
2241
  ) ??
2855
- specifier;
2242
+ specifier
2856
2243
  // `packages: 'external'` refuses to bundle a BARE entry point, so the
2857
2244
  // plugin-free path needs the resolved file — which is the same file esbuild
2858
2245
  // would have resolved the specifier to.
2859
- const nativeEntry = path.isAbsolute(entry) ? entry : resolvedEntry;
2860
- if (trace) trace.entry = nativeEntry ?? resolvedEntry;
2246
+ const nativeEntry = path.isAbsolute(entry) ? entry : resolvedEntry
2247
+ if (trace) trace.entry = nativeEntry ?? resolvedEntry
2861
2248
  const preplan =
2862
2249
  vendorPreplanEnabled() &&
2863
2250
  (await vendorPreplanPackageSafe(
@@ -2866,7 +2253,7 @@ async function bundleExternalPackageImpl(
2866
2253
  nativeEntry ?? resolvedEntry,
2867
2254
  target,
2868
2255
  conditionTarget,
2869
- ));
2256
+ ))
2870
2257
  const native =
2871
2258
  file && nativeEntry
2872
2259
  ? await buildVendorNative({
@@ -2886,12 +2273,12 @@ async function bundleExternalPackageImpl(
2886
2273
  context: context ?? { nestedExternalFiles: [] },
2887
2274
  trace,
2888
2275
  })
2889
- : undefined;
2890
- if (native?.[0]) return { code: addCommonJsNamedExports(native[0].text), metafile: undefined };
2276
+ : undefined
2277
+ if (native?.[0]) return { code: addCommonJsNamedExports(native[0].text), metafile: undefined }
2891
2278
 
2892
- let bundled: string;
2893
- let metafile: Metafile | undefined;
2894
- const pluginStart = performance.now();
2279
+ let bundled: string
2280
+ let metafile: Metafile | undefined
2281
+ const pluginStart = performance.now()
2895
2282
  const vendorPlugins = profiledVendorPlugins([
2896
2283
  serverAssetPlugin(config),
2897
2284
  ...getBundlerExtensions().serverEsbuildPlugins(
@@ -2906,11 +2293,11 @@ async function bundleExternalPackageImpl(
2906
2293
  }),
2907
2294
  externalPackageDependencyPlugin(config, specifier, target, conditionTarget, context, preplan),
2908
2295
  externalBuiltinBuildPlugin(),
2909
- ]);
2910
- if (trace) trace.plugins = vendorPlugins.map(plugin => plugin.name);
2296
+ ])
2297
+ if (trace) trace.plugins = vendorPlugins.map(plugin => plugin.name)
2911
2298
  // Bare entry: hand the interop plugin the resolution already computed above.
2912
2299
  if (!path.isAbsolute(entry) && resolvedEntry) {
2913
- provideEntryResolution(entry, resolveDir, resolvedEntry);
2300
+ provideEntryResolution(entry, resolveDir, resolvedEntry)
2914
2301
  }
2915
2302
  try {
2916
2303
  const result = await build({
@@ -2937,14 +2324,14 @@ async function bundleExternalPackageImpl(
2937
2324
  metafile: true,
2938
2325
  ...serverDefineOptions(),
2939
2326
  plugins: vendorPlugins,
2940
- });
2941
- const output = result.outputFiles[0];
2942
- if (!output) throw new Error(`Failed to bundle ${specifier}`);
2943
- vendorNativeStats.pluginBuilds += 1;
2944
- vendorNativeStats.pluginBuildMs += performance.now() - pluginStart;
2945
- bundled = output.text;
2946
- metafile = result.metafile;
2947
- if (trace) trace.metafile = result.metafile;
2327
+ })
2328
+ const output = result.outputFiles[0]
2329
+ if (!output) throw new Error(`Failed to bundle ${specifier}`)
2330
+ vendorNativeStats.pluginBuilds += 1
2331
+ vendorNativeStats.pluginBuildMs += performance.now() - pluginStart
2332
+ bundled = output.text
2333
+ metafile = result.metafile
2334
+ if (trace) trace.metafile = result.metafile
2948
2335
  } catch (error) {
2949
2336
  // A subpath the package deliberately withholds from this layer
2950
2337
  // (`"node": null`) is not a broken dependency: browser-only code reaches it
@@ -2960,86 +2347,19 @@ async function bundleExternalPackageImpl(
2960
2347
  serverBundleConditions(conditionTarget),
2961
2348
  )
2962
2349
  ) {
2963
- throw error;
2350
+ throw error
2964
2351
  }
2965
- return { code: unexportedSubpathModule(specifier), metafile: undefined };
2352
+ return { code: unexportedSubpathModule(specifier), metafile: undefined }
2966
2353
  }
2967
- return { code: addCommonJsNamedExports(bundled), metafile };
2354
+ return { code: addCommonJsNamedExports(bundled), metafile }
2968
2355
  }
2969
2356
 
2970
2357
  function unexportedSubpathModule(specifier: string) {
2971
- const packageName = packageNameFromSpecifier(specifier);
2972
- const subpath = packageName && specifier !== packageName ? `.${specifier.slice(packageName.length)}` : '.';
2973
- const message = `Package subpath '${subpath}' is not defined by "exports" in ${packageName ?? specifier}`;
2974
- return `throw Object.assign(new Error(${JSON.stringify(message)}), { code: 'ERR_PACKAGE_PATH_NOT_EXPORTED' });\n`;
2975
- }
2976
-
2977
- function runtimeAliasBuildPlugin(
2978
- config?: ResolvedConfig,
2979
- target: CompatAliasTarget = 'server',
2980
- options: { bundleRequireAliases?: boolean; reactServerLayer?: boolean } = {},
2981
- ): Plugin {
2982
- const aliases = config ? coreAliases(config, target) : undefined;
2983
- // A node_modules package bundled into the RSC (react-server) layer that requires 'react' must observe the
2984
- // react-server subset - no client-hook dispatcher - exactly like the framework's own react-server modules,
2985
- // so a package's `'useState' in require('react')` probe reads false on the server. ESM named imports stay
2986
- // external and resolve at runtime against the full-hooks server shim (kept for pages/api back-compat), so
2987
- // this narrow require-call override never breaks them.
2988
- const reactServerLayerAliases =
2989
- config && options.reactServerLayer
2990
- ? getImportAliasExtensions().reactServerLayerAliases(config)
2991
- : undefined;
2992
- return {
2993
- name: 'pnext-runtime-build-alias',
2994
- setup(build) {
2995
- build.onResolve({ filter: /^\.\/preact$/ }, args => {
2996
- // `./preact` is the compat react shim's private import. It also appears in MIRRORED copies of the
2997
- // shim (build-cache outputs under node_modules paths) where no sibling preact file exists - those
2998
- // must resolve to the single compat instance too. Only a REAL sibling ./preact.* opts out.
2999
- const hasRealSibling = ['.ts', '.tsx', '.js', '.mjs', '.jsx'].some(ext =>
3000
- existsSync(path.join(args.resolveDir, `preact${ext}`)),
3001
- );
3002
- const compatPreact = path.resolve(import.meta.dirname, '..', 'compat', 'react', 'preact.ts');
3003
- if (hasRealSibling && path.resolve(args.resolveDir, 'preact.ts') !== compatPreact)
3004
- return undefined;
3005
- return { path: compatPreact };
3006
- });
3007
- build.onResolve({ filter: /^\.\.\/client\/errors\/primitive-throw$/ }, args => {
3008
- const compatPreact = path.resolve(import.meta.dirname, '..', 'compat', 'react', 'preact.ts');
3009
- if (path.resolve(args.importer) !== compatPreact) return undefined;
3010
- return { path: path.resolve(import.meta.dirname, '..', 'compat', 'client', 'errors', 'primitive-throw.ts') };
3011
- });
3012
- build.onResolve({ filter: aliasSpecifierFilter }, args => {
3013
- const resolved = aliases?.[args.path] ?? firstAliasForSpecifier(args.path);
3014
- if (!resolved) return undefined;
3015
- const requireTarget = serverRequireAlias(args.path, resolved, args.kind);
3016
- if (requireTarget !== resolved) return { path: requireTarget };
3017
- if (options.bundleRequireAliases && args.kind === 'require-call') {
3018
- const layerTarget = serverRequireAlias(
3019
- args.path,
3020
- reactServerLayerAliases?.[args.path] ?? resolved,
3021
- args.kind,
3022
- );
3023
- return { path: path.isAbsolute(layerTarget) ? layerTarget : require.resolve(layerTarget) };
3024
- }
3025
- return {
3026
- path: path.isAbsolute(resolved) ? pathToFileHref(resolved) : resolved,
3027
- external: true,
3028
- };
3029
- });
3030
- },
3031
- };
3032
- }
3033
-
3034
- function serverRequireAlias(specifier: string, target: string, kind: string | undefined): string {
3035
- if (kind !== 'require-call') return target;
3036
- if (specifier === 'next/navigation' && target.endsWith(`${path.sep}navigation.ts`)) {
3037
- return path.join(path.dirname(target), 'navigation.cjs');
3038
- }
3039
- if (specifier === 'next/router' && target.endsWith(`${path.sep}router.ts`)) {
3040
- return path.join(path.dirname(target), 'router.cjs');
3041
- }
3042
- return target;
2358
+ const packageName = packageNameFromSpecifier(specifier)
2359
+ const subpath =
2360
+ packageName && specifier !== packageName ? `.${specifier.slice(packageName.length)}` : '.'
2361
+ const message = `Package subpath '${subpath}' is not defined by "exports" in ${packageName ?? specifier}`
2362
+ return `throw Object.assign(new Error(${JSON.stringify(message)}), { code: 'ERR_PACKAGE_PATH_NOT_EXPORTED' });\n`
3043
2363
  }
3044
2364
 
3045
2365
  function externalPackageDependencyPlugin(
@@ -3050,13 +2370,13 @@ function externalPackageDependencyPlugin(
3050
2370
  context?: { nestedExternalFiles: string[] },
3051
2371
  preplan = false,
3052
2372
  ): Plugin {
3053
- const entryPackageName = packageNameFromSpecifier(entrySpecifier);
2373
+ const entryPackageName = packageNameFromSpecifier(entrySpecifier)
3054
2374
  return {
3055
2375
  name: 'pnext-external-package-dependencies',
3056
2376
  setup(build) {
3057
2377
  build.onResolve({ filter: /^[^./].*/ }, async args => {
3058
- if (!isPackageSpecifier(args.path)) return undefined;
3059
- if (packageNameFromSpecifier(args.path) === entryPackageName) return undefined;
2378
+ if (!isPackageSpecifier(args.path)) return undefined
2379
+ if (packageNameFromSpecifier(args.path) === entryPackageName) return undefined
3060
2380
  if (preplan && args.kind !== 'require-call') {
3061
2381
  const planned = await preplanDependencyDecision(
3062
2382
  config,
@@ -3064,12 +2384,12 @@ function externalPackageDependencyPlugin(
3064
2384
  conditionTarget,
3065
2385
  args.path,
3066
2386
  args.resolveDir,
3067
- );
2387
+ )
3068
2388
  if (planned) {
3069
- vendorPreplanStats.preplanRefs += 1;
3070
- return planned;
2389
+ vendorPreplanStats.preplanRefs += 1
2390
+ return planned
3071
2391
  }
3072
- vendorPreplanStats.fallbackRefs += 1;
2392
+ vendorPreplanStats.fallbackRefs += 1
3073
2393
  }
3074
2394
  // Off the slot for the whole decision: it ends in a nested vendor
3075
2395
  // build, and holding a worker across that is what caps real
@@ -3077,8 +2397,15 @@ function externalPackageDependencyPlugin(
3077
2397
  // behaviour for bisecting). The resolver work in front of it is cheap
3078
2398
  // and off-slot too, exactly as the plugin-free path's post-pass runs it.
3079
2399
  const decision = await yieldVendorSlotWhile(() =>
3080
- externalDependencyDecision(config, target, conditionTarget, args.path, args.resolveDir, args.importer),
3081
- );
2400
+ externalDependencyDecision(
2401
+ config,
2402
+ target,
2403
+ conditionTarget,
2404
+ args.path,
2405
+ args.resolveDir,
2406
+ args.importer,
2407
+ ),
2408
+ )
3082
2409
  if (decision?.vendored && vendorTraceEnabled()) {
3083
2410
  vendorTraceRow({
3084
2411
  kind: 'edge',
@@ -3087,26 +2414,26 @@ function externalPackageDependencyPlugin(
3087
2414
  fromCondition: conditionTarget,
3088
2415
  to: args.path,
3089
2416
  toResolveDir: args.resolveDir,
3090
- });
2417
+ })
3091
2418
  }
3092
- if (decision?.nested && context) context.nestedExternalFiles.push(decision.nested);
2419
+ if (decision?.nested && context) context.nestedExternalFiles.push(decision.nested)
3093
2420
  // eslint-disable-next-line @typescript-eslint/only-throw-error
3094
- if (decision?.error) throw decision.error;
3095
- return decision?.result;
3096
- });
2421
+ if (decision?.error) throw decision.error
2422
+ return decision?.result
2423
+ })
3097
2424
  },
3098
- };
2425
+ }
3099
2426
  }
3100
2427
 
3101
2428
  /** What one external reference resolves to, plus the effects the caller replays. */
3102
2429
  interface ExternalDependencyDecision {
3103
- result: OnResolveResult | undefined;
2430
+ result: OnResolveResult | undefined
3104
2431
  /** A transitive dep pinned to a nested copy, for the trace-copy pass. */
3105
- nested?: string;
2432
+ nested?: string
3106
2433
  /** A build failure, re-thrown per caller rather than cached as a value. */
3107
- error?: unknown;
2434
+ error?: unknown
3108
2435
  /** Trace-only: the dep was deferred to a vendor bundle of its own. */
3109
- vendored?: boolean;
2436
+ vendored?: boolean
3110
2437
  }
3111
2438
 
3112
2439
  /**
@@ -3125,7 +2452,7 @@ async function externalDependencyDecision(
3125
2452
  ): Promise<ExternalDependencyDecision> {
3126
2453
  // B7: a serverExternalPackage transitive dep stays external (Node resolves it
3127
2454
  // from node_modules at runtime) instead of being bundled.
3128
- const depName = packageNameFromSpecifier(specifier);
2455
+ const depName = packageNameFromSpecifier(specifier)
3129
2456
  if (depName && getExternalPackagePolicy().external(depName)) {
3130
2457
  // Transitive-version correctness: the runtime resolves a bare external from the VENDOR bundle's own
3131
2458
  // location, which walks up to the hoisted root copy - wrong when the importing package pulls a distinct
@@ -3134,35 +2461,35 @@ async function externalDependencyDecision(
3134
2461
  // external to that exact resolved file so the correct version loads at runtime. A hoisted dep stays a
3135
2462
  // portable bare external. Bundling the nested copy inline instead regresses to a resolve ERROR for the
3136
2463
  // bundle's other bare externals, so keep it external and only pin the path.
3137
- const conditions = serverBundleConditions(conditionTarget);
2464
+ const conditions = serverBundleConditions(conditionTarget)
3138
2465
  const fromImporter = importer
3139
2466
  ? resolveNestedPackageFromImporter(importer, specifier, conditions)
3140
- : undefined;
2467
+ : undefined
3141
2468
  const fromRoot = resolveVendorPackageSpecifier(
3142
2469
  config.root,
3143
2470
  path.join(config.root, 'pnext-resolve.ts'),
3144
2471
  specifier,
3145
2472
  conditions,
3146
- );
2473
+ )
3147
2474
  if (fromImporter && fromImporter !== fromRoot) {
3148
- return { result: { path: fromImporter, external: true }, nested: fromImporter };
2475
+ return { result: { path: fromImporter, external: true }, nested: fromImporter }
3149
2476
  }
3150
- return { result: { path: specifier, external: true } };
2477
+ return { result: { path: specifier, external: true } }
3151
2478
  }
3152
2479
  const resolvedEntry = resolveVendorPackageSpecifier(
3153
2480
  config.root,
3154
2481
  path.join(resolveDir, 'pnext-resolve.ts'),
3155
2482
  specifier,
3156
2483
  serverBundleConditions(conditionTarget),
3157
- );
2484
+ )
3158
2485
  // A CommonJS dependency loses its named exports when externalized (esbuild emits only `default` for a CJS
3159
2486
  // entry), so it is inlined here instead. `isEsmModuleEntry` first checks CONTENT, not just extension and
3160
2487
  // `package.json#type` - most "CJS" entries are plain-.js ESM and inline for no reason. What is left gets a
3161
2488
  // verified named-export facade when it publishes names, and is inlined only when it does not.
3162
- const commonJs = Boolean(resolvedEntry) && !isEsmModuleEntry(resolvedEntry!);
3163
- if (commonJs && !(await cjsEntryMayHaveNamedExports(resolvedEntry!))) return { result: undefined };
3164
- let resolvedPackage: string;
3165
- const tv = performance.now();
2489
+ const commonJs = Boolean(resolvedEntry) && !isEsmModuleEntry(resolvedEntry!)
2490
+ if (commonJs && !(await cjsEntryMayHaveNamedExports(resolvedEntry!))) return { result: undefined }
2491
+ let resolvedPackage: string
2492
+ const tv = performance.now()
3166
2493
  try {
3167
2494
  resolvedPackage = await externalServerPackageHref(
3168
2495
  config,
@@ -3172,211 +2499,90 @@ async function externalDependencyDecision(
3172
2499
  conditionTarget,
3173
2500
  // Raised from inside a running build: must not queue behind it.
3174
2501
  true,
3175
- );
2502
+ )
3176
2503
  } catch (error) {
3177
- if (isResolveFailure(error)) return { result: { path: specifier, external: true } };
3178
- return { result: undefined, error };
2504
+ if (isResolveFailure(error)) return { result: { path: specifier, external: true } }
2505
+ return { result: undefined, error }
3179
2506
  }
3180
2507
  if (commonJs) {
3181
- heavyProfRow({ k: 'dep-build-wait', site: 'plugin', spec: specifier, ms: performance.now() - tv });
3182
- const tf = performance.now();
3183
- const facade = await cjsNamedExportFacade(fileURLToPath(resolvedPackage), resolvedPackage);
3184
- heavyProfRow({ k: 'facade-wait', site: 'plugin', spec: specifier, ms: performance.now() - tf });
3185
- if (!facade) return { result: undefined };
3186
- return { result: { path: pathToFileHref(facade), external: true }, vendored: true };
2508
+ heavyProfRow({
2509
+ k: 'dep-build-wait',
2510
+ site: 'plugin',
2511
+ spec: specifier,
2512
+ ms: performance.now() - tv,
2513
+ })
2514
+ const tf = performance.now()
2515
+ const facade = await cjsNamedExportFacade(fileURLToPath(resolvedPackage), resolvedPackage)
2516
+ heavyProfRow({ k: 'facade-wait', site: 'plugin', spec: specifier, ms: performance.now() - tf })
2517
+ if (!facade) return { result: undefined }
2518
+ return { result: { path: pathToFileHref(facade), external: true }, vendored: true }
3187
2519
  }
3188
- return { result: { path: resolvedPackage, external: true }, vendored: true };
2520
+ return { result: { path: resolvedPackage, external: true }, vendored: true }
3189
2521
  }
3190
2522
 
3191
2523
  /** `PNEXT_VENDOR_YIELD=0` keeps the slot across a nested dependency's build. */
3192
2524
  function yieldVendorSlotWhile<T>(task: () => Promise<T>) {
3193
2525
  // eslint-disable-next-line turbo/no-undeclared-env-vars
3194
- return process.env.PNEXT_VENDOR_YIELD === '0' ? task() : outsideVendorSlot(task);
2526
+ return process.env.PNEXT_VENDOR_YIELD === '0' ? task() : outsideVendorSlot(task)
3195
2527
  }
3196
2528
 
3197
2529
  function isResolveFailure(error: unknown) {
3198
- const details = error instanceof Error ? `${error.message}\n${error.stack ?? ''}` : String(error);
3199
- return details.includes('Could not resolve');
2530
+ const details = error instanceof Error ? `${error.message}\n${error.stack ?? ''}` : String(error)
2531
+ return details.includes('Could not resolve')
3200
2532
  }
3201
2533
 
3202
2534
  async function profileRuntimeStep<T>(label: string, task: () => Promise<T>) {
3203
2535
  // eslint-disable-next-line turbo/no-undeclared-env-vars
3204
- if (!process.env.PNEXT_DEV_PROFILE) return task();
3205
- const start = performance.now();
2536
+ if (!process.env.PNEXT_DEV_PROFILE) return task()
2537
+ const start = performance.now()
3206
2538
  try {
3207
- return await task();
2539
+ return await task()
3208
2540
  } finally {
3209
- console.log(`dev-profile compat ${label} in ${formatDuration(performance.now() - start)}`);
2541
+ console.log(`dev-profile compat ${label} in ${formatDuration(performance.now() - start)}`)
3210
2542
  }
3211
2543
  }
3212
2544
 
3213
- function formatDuration(durationMs: number) {
3214
- const ms = Math.max(0, durationMs);
3215
- if (ms < 10) return `${ms.toFixed(1)}ms`;
3216
- if (ms < 1000) return `${Math.round(ms)}ms`;
3217
- return `${(ms / 1000).toFixed(ms < 10000 ? 2 : 1)}s`;
3218
- }
3219
-
3220
2545
  function externalBuiltinBuildPlugin(): Plugin {
3221
2546
  return {
3222
2547
  name: 'pnext-external-builtins',
3223
2548
  setup(build) {
3224
2549
  build.onResolve({ filter: /^[^./].*/ }, args => {
3225
- const packageName = packageNameFromSpecifier(args.path);
2550
+ const packageName = packageNameFromSpecifier(args.path)
3226
2551
  if (!packageName || (!builtinModules.includes(packageName) && !isBunBuiltin(packageName))) {
3227
- return undefined;
2552
+ return undefined
3228
2553
  }
3229
- return { path: args.path, external: true };
3230
- });
2554
+ return { path: args.path, external: true }
2555
+ })
3231
2556
  },
3232
- };
2557
+ }
3233
2558
  }
3234
2559
 
3235
2560
  function isBunBuiltin(specifier: string) {
3236
- return specifier === 'bun' || specifier.startsWith('bun:');
3237
- }
3238
-
3239
- function serverAssetPlugin(config?: ResolvedConfig): Plugin {
3240
- return {
3241
- name: 'pnext-empty-server-assets',
3242
- setup(build) {
3243
- build.onResolve({ filter: serverIgnoredAssetFilter }, args => {
3244
- const resolved = resolveAssetPath(args.path, args.resolveDir);
3245
- // A configured `turbopack.rules` loader chain for this extension (e.g.
3246
- // `*.svg`) preempts the generic asset pipeline: defer so the compat
3247
- // loader-rule plugin's onLoad runs the chain against the real file.
3248
- if (getAssetExtensions().hasLoaderRuleFor(resolved)) {
3249
- return undefined;
3250
- }
3251
- return { path: resolved, namespace: 'pnext-empty-server-asset' };
3252
- });
3253
- build.onLoad({ filter: /.*/, namespace: 'pnext-empty-server-asset' }, async args => ({
3254
- contents: getCssExtensions().loadCssModuleForClient(args.path) ?? (isCssFile(args.path)
3255
- ? ''
3256
- : isStaticImageFile(args.path)
3257
- ? await staticImageModuleSource(config, args.path)
3258
- : 'export default "";'),
3259
- loader: 'js',
3260
- }));
3261
- },
3262
- };
3263
- }
3264
-
3265
- /**
3266
- * Whether `rewriteServerSource` can possibly change `source`. One regex scan
3267
- * over the union of the core dynamic tokens and every registered transform's
3268
- * own sniff tokens (see `withSniff`); most modules trigger nothing and skip the
3269
- * whole chain.
3270
- */
3271
- export function needsServerSourceRewrite(source: string) {
3272
- return sourceHasDynamicImport(source) || sourceNeedsServerTransforms(source);
3273
- }
3274
-
3275
- export function rewriteServerSource(
3276
- source: string,
3277
- file: string,
3278
- options: { nextFonts?: boolean; root?: string } = {},
3279
- ) {
3280
- const root = options.root ?? path.dirname(file);
3281
- const rewritten = sourceHasDynamicImport(source)
3282
- ? rewriteDynamicCallTargets(
3283
- rewriteLiteralDynamicCalls(source, file),
3284
- specifier => resolveImport(root, file, specifier),
3285
- file,
3286
- )
3287
- : source;
3288
- // Compat pre-transforms (next/font/root-params) run before generic server
3289
- // transforms (use-cache/action tags). Pure-core apps register no transforms.
3290
- if (options.nextFonts === false) return rewritten;
3291
- if (!sourceNeedsServerTransforms(rewritten)) return rewritten;
3292
- return applyServerSourceTransforms(
3293
- applyServerSourcePreTransforms(rewritten, file, options.root),
3294
- file,
3295
- options.root,
3296
- );
2561
+ return specifier === 'bun' || specifier.startsWith('bun:')
3297
2562
  }
3298
2563
 
3299
2564
  export function runtimeImportTarget(specifier: string, format: 'esm' | 'cjs' = 'esm') {
3300
- if (isRelativeOrAbsoluteSpecifier(specifier)) return specifier;
3301
- const target = firstAliasForSpecifier(specifier);
3302
- if (!target) return specifier;
3303
- return format === 'esm' ? pathToFileHref(target) : target;
2565
+ if (isRelativeOrAbsoluteSpecifier(specifier)) return specifier
2566
+ const target = firstAliasForSpecifier(specifier)
2567
+ if (!target) return specifier
2568
+ return format === 'esm' ? pathToFileHref(target) : target
3304
2569
  }
3305
2570
 
3306
2571
  export function runtimeServerImportTarget(specifier: string) {
3307
- return runtimeImportTarget(specifier, 'esm');
2572
+ return runtimeImportTarget(specifier, 'esm')
3308
2573
  }
3309
2574
 
3310
2575
  function isRelativeOrAbsoluteSpecifier(specifier: string) {
3311
- return specifier.startsWith('.') || specifier.startsWith('/') || specifier.startsWith('file:');
2576
+ return specifier.startsWith('.') || specifier.startsWith('/') || specifier.startsWith('file:')
3312
2577
  }
3313
2578
 
3314
2579
  function isPackageSpecifier(specifier: string) {
3315
- if (isRelativeOrAbsoluteSpecifier(specifier)) return false;
3316
- if (specifier.startsWith('#')) return false;
3317
- if (specifier.startsWith('node:')) return false;
3318
- if (specifier.includes(':')) return false;
3319
- const packageName = packageNameFromSpecifier(specifier);
3320
- return Boolean(packageName && !builtinModules.includes(packageName));
3321
- }
3322
-
3323
- function isInside(root: string, file: string) {
3324
- const relative = path.relative(root, file);
3325
- return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
3326
- }
3327
-
3328
- function rootPaths(root: string) {
3329
- const key = path.resolve(root);
3330
- const cached = rootPathCache.get(key);
3331
- if (cached) return cached;
3332
- const paths = new Set([path.resolve(root)]);
3333
- try {
3334
- paths.add(realpathSync.native(root));
3335
- } catch {
3336
- // The dev cache root is registered before it exists.
3337
- }
3338
- const resolved = [...paths];
3339
- rootPathCache.set(key, resolved);
3340
- return resolved;
3341
- }
3342
-
3343
- function rootFilter(root: string) {
3344
- // Base source extensions plus any extra loadable extensions registered by
3345
- // compat (e.g. mdx/md via pageExtensions). Without these, `.mdx` modules skip
3346
- // the transform load hook and resolve as raw assets (default export = path).
3347
- const extras = currentLoadExtras().map(escapeRegex);
3348
- const exts = ['[jt]sx?', ...extras].join('|');
3349
- // Exclude `next.config.{js,cjs,mjs,ts}`: it is loaded for its exported value (often CommonJS
3350
- // `module.exports = ...`), not as part of the app's server module graph. Routing it through the
3351
- // ES-module source transform drops the CJS exports, silently disabling next.config
3352
- // rewrites/redirects.
3353
- // Exclude the vendor tree under the cache root: the `*.dist/` copies copyBrowserReadyEsmDist
3354
- // publishes are verbatim third-party ESM the copy predicate has already proved needs no server
3355
- // rewrite, but they are `.js`, so each was read and re-transformed on every boot. Bun loads them
3356
- // natively instead. Two clauses because the vendor tree is reachable from two registered roots.
3357
- const vendorGuard = vendorLoadsNatively()
3358
- ? `${root.endsWith(`${path.sep}cache${path.sep}server`) ? '(?!\\/vendor\\/)' : ''}(?!.*\\/cache\\/server\\/vendor\\/)`
3359
- : '';
3360
- return new RegExp(
3361
- `^${escapeRegex(root)}${vendorGuard}(?!.*\\/node_modules\\/)(?!\\/next\\.config\\.(?:js|cjs|mjs|ts)$)(?:/.*)?\\.(?:${exts})$`,
3362
- );
3363
- }
3364
-
3365
- function esbuildLoader(file: string) {
3366
- if (file.endsWith('.tsx')) return 'tsx';
3367
- if (file.endsWith('.ts')) return 'ts';
3368
- // Workspace .js/.jsx may contain JSX (Next allows it); jsx is a js superset.
3369
- return 'jsx';
3370
- }
3371
-
3372
- function hashRoot(value: string) {
3373
- let hash = 5381;
3374
- for (const char of value) hash = ((hash << 5) + hash) ^ char.charCodeAt(0);
3375
- return (hash >>> 0).toString(36);
3376
- }
3377
-
3378
- function hashBundleSpecifier(value: string) {
3379
- return createHash('sha256').update(value).digest('hex').slice(0, 16);
2580
+ if (isRelativeOrAbsoluteSpecifier(specifier)) return false
2581
+ if (specifier.startsWith('#')) return false
2582
+ if (specifier.startsWith('node:')) return false
2583
+ if (specifier.includes(':')) return false
2584
+ const packageName = packageNameFromSpecifier(specifier)
2585
+ return Boolean(packageName && !builtinModules.includes(packageName))
3380
2586
  }
3381
2587
 
3382
2588
  // Trace copies of nested transitive externals (a version distinct from the
@@ -3384,74 +2590,20 @@ function hashBundleSpecifier(value: string) {
3384
2590
  // tracing / server-bundle scans observe the bundled dependency the way
3385
2591
  // webpack's bundled transitive would appear. Trace-only: never imported.
3386
2592
  async function emitNestedExternalTraceCopies(vendorFile: string, sources: readonly string[]) {
3387
- const dir = path.dirname(vendorFile);
2593
+ const dir = path.dirname(vendorFile)
3388
2594
  for (const source of new Set(sources)) {
3389
2595
  try {
3390
- const content = await readFile(source, 'utf8');
3391
- const out = path.join(dir, `${hashBundleSpecifier(source)}.trace.js`);
3392
- if (!existsSync(out)) await writeFileAtomic(out, content);
2596
+ const content = await readFile(source, 'utf8')
2597
+ const out = path.join(dir, `${hashBundleSpecifier(source)}.trace.js`)
2598
+ if (!existsSync(out)) await writeFileAtomic(out, content)
3393
2599
  } catch {
3394
2600
  // A source that cannot be read is simply not traced.
3395
2601
  }
3396
2602
  }
3397
2603
  }
3398
2604
 
3399
-
3400
- const serverIgnoredAssetFilter =
3401
- /\.(?:css|scss|sass|woff2?|ttf|otf|eot|png|jpe?g|gif|webp|avif|svg|ico|bmp)(?:$|[?#])/;
3402
-
3403
- function isCssFile(file: string) {
3404
- return file.endsWith('.css');
3405
- }
3406
-
3407
- function isStaticImageFile(file: string) {
3408
- return /\.(?:png|jpe?g|gif|webp|avif|svg|ico|bmp)(?:$|[?#])/.test(file);
3409
- }
3410
-
3411
- function resolveAssetPath(specifier: string, resolveDir: string) {
3412
- const [sourcePath = '', hash = ''] = specifier.split('#', 2);
3413
- const resolved = path.isAbsolute(sourcePath) ? sourcePath : path.resolve(resolveDir, sourcePath);
3414
- return hash ? `${resolved}#${hash}` : resolved;
3415
- }
3416
-
3417
- async function staticImageModuleSource(config: ResolvedConfig | undefined, file: string) {
3418
- const [sourcePath = ''] = file.split('#', 1);
3419
- const bytes = new Uint8Array(await readFile(sourcePath));
3420
- const emitted: { relative: string; bytes: Uint8Array }[] = [];
3421
- const emit = (relative: string, data: Uint8Array) => {
3422
- emitted.push({ relative, bytes: data });
3423
- return `/${relative}`;
3424
- };
3425
- const compat = await getAssetExtensions().staticAssetModule({ sourcePath, bytes, emit });
3426
- const source = compat ?? coreStaticAssetModule(sourcePath, bytes, emit);
3427
- if (config) {
3428
- for (const asset of emitted) {
3429
- const target = path.join(config.outPath, 'public', ...asset.relative.split('/'));
3430
- await mkdir(path.dirname(target), { recursive: true });
3431
- if (!existsSync(target)) await Bun.write(target, asset.bytes);
3432
- }
3433
- }
3434
- return source;
3435
- }
3436
-
3437
- // Core's generic static-asset module: emit the file under a hashed media URL
3438
- // and export it as the default (a plain URL string) when no compat override is
3439
- // registered. next/image compat replaces this with a full StaticImageData shape.
3440
- function coreStaticAssetModule(
3441
- sourcePath: string,
3442
- bytes: Uint8Array,
3443
- emit: (relative: string, bytes: Uint8Array) => string,
3444
- ): string {
3445
- const ext = path.extname(sourcePath).toLowerCase() || '.bin';
3446
- const hash = createHash('sha256').update(bytes).digest('hex').slice(0, 8);
3447
- const base = path.basename(sourcePath, path.extname(sourcePath)).replace(/[^A-Za-z0-9_-]+/g, '-');
3448
- const relative = getAssetExtensions().staticAssetRelativePath({ sourcePath, hash, base, ext });
3449
- const src = emit(relative, bytes);
3450
- return `const src = ${JSON.stringify(src)};\nexport default src;\nexport { src };\n`;
3451
- }
3452
-
3453
2605
  function packageNameFromSpecifier(specifier: string) {
3454
- const normalized = specifier.startsWith('node:') ? specifier.slice(5) : specifier;
3455
- const parts = normalized.split('/');
3456
- return normalized.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0];
2606
+ const normalized = specifier.startsWith('node:') ? specifier.slice(5) : specifier
2607
+ const parts = normalized.split('/')
2608
+ return normalized.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0]
3457
2609
  }