@wular/pnext 0.1.6 → 0.1.8

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 (337) hide show
  1. package/bin/pnext +9 -2
  2. package/dist/server/_chunks/aliases-pycbtjt1.js +46 -0
  3. package/dist/server/_chunks/analyze-4d9e00g9.js +449 -0
  4. package/dist/server/_chunks/boot-bhemypxz.js +38 -0
  5. package/dist/server/_chunks/build-c991dv65.js +86 -0
  6. package/dist/server/_chunks/build-kh5e3x48.js +3341 -0
  7. package/dist/server/_chunks/build-tier-0a96kmx3.js +2456 -0
  8. package/dist/server/_chunks/compile-040r79gj.js +22 -0
  9. package/dist/server/_chunks/config-0tvz03a6.js +24 -0
  10. package/dist/server/_chunks/config-27rj7830.js +36 -0
  11. package/dist/server/_chunks/create-9rvwshsp.js +135 -0
  12. package/dist/server/_chunks/dev-ex9exynt.js +220 -0
  13. package/dist/server/_chunks/esbuild-yyktfrtt.js +15 -0
  14. package/dist/server/_chunks/generate-myw9hef7.js +22 -0
  15. package/dist/server/_chunks/index-0r5eqfc7.js +279 -0
  16. package/dist/server/_chunks/index-0yjv5v47.js +105 -0
  17. package/dist/server/_chunks/index-11xpbfej.js +17 -0
  18. package/dist/server/_chunks/index-141vcwk6.js +109 -0
  19. package/dist/server/_chunks/index-1pzm1hdy.js +4130 -0
  20. package/dist/server/_chunks/index-29pq0kax.js +590 -0
  21. package/dist/server/_chunks/index-2vz0wzdn.js +59 -0
  22. package/dist/server/_chunks/index-2wfv4kvx.js +182 -0
  23. package/dist/server/_chunks/index-38xpdxwd.js +194 -0
  24. package/dist/server/_chunks/index-3e075kny.js +128 -0
  25. package/dist/server/_chunks/index-3ycf5ckv.js +472 -0
  26. package/dist/server/_chunks/index-4399bvd2.js +18 -0
  27. package/dist/server/_chunks/index-4da76t62.js +297 -0
  28. package/dist/server/_chunks/index-4t92hdxd.js +172 -0
  29. package/dist/server/_chunks/index-4zzebszd.js +10 -0
  30. package/dist/server/_chunks/index-52k54ykj.js +95 -0
  31. package/dist/server/_chunks/index-5hr4062s.js +70 -0
  32. package/dist/server/_chunks/index-68m3mwbd.js +84 -0
  33. package/dist/server/_chunks/index-6b009mt0.js +56 -0
  34. package/dist/server/_chunks/index-6gex3pfa.js +15 -0
  35. package/dist/server/_chunks/index-6kp9r0d9.js +11 -0
  36. package/dist/server/_chunks/index-6q7zvrap.js +134 -0
  37. package/dist/server/_chunks/index-71885zaq.js +284 -0
  38. package/dist/server/_chunks/index-73z4esab.js +38 -0
  39. package/dist/server/_chunks/index-79ngvam8.js +60 -0
  40. package/dist/server/_chunks/index-7wc9n8be.js +25 -0
  41. package/dist/server/_chunks/index-8ct7rqy3.js +198 -0
  42. package/dist/server/_chunks/index-8kcxdfya.js +206 -0
  43. package/dist/server/_chunks/index-9ch50vtq.js +18 -0
  44. package/dist/server/_chunks/index-9f49w76j.js +213 -0
  45. package/dist/server/_chunks/index-9hz3s0m8.js +851 -0
  46. package/dist/server/_chunks/index-akkrvjn6.js +535 -0
  47. package/dist/server/_chunks/index-b6wtkdp4.js +24 -0
  48. package/dist/server/_chunks/index-b7j671dj.js +207 -0
  49. package/dist/server/_chunks/index-bcws5eh2.js +35 -0
  50. package/dist/server/_chunks/index-bzd2ef7t.js +10 -0
  51. package/dist/server/_chunks/index-cjgqs13g.js +65 -0
  52. package/dist/server/_chunks/index-ckkdpht5.js +41 -0
  53. package/dist/server/_chunks/index-cn86z041.js +296 -0
  54. package/dist/server/_chunks/index-cvr4z22m.js +92 -0
  55. package/dist/server/_chunks/index-cyfednwb.js +87 -0
  56. package/dist/server/_chunks/index-d158y67j.js +152 -0
  57. package/dist/server/_chunks/index-d1jsmhhk.js +955 -0
  58. package/dist/server/_chunks/index-dg0f05zs.js +199 -0
  59. package/dist/server/_chunks/index-dgr390q7.js +9438 -0
  60. package/dist/server/_chunks/index-drwv61cc.js +185 -0
  61. package/dist/server/_chunks/index-e3a6yx84.js +18 -0
  62. package/dist/server/_chunks/index-e88tyv35.js +45 -0
  63. package/dist/server/_chunks/index-en7n9f7m.js +22 -0
  64. package/dist/server/_chunks/index-entm8bmz.js +28 -0
  65. package/dist/server/_chunks/index-evhd413j.js +57 -0
  66. package/dist/server/_chunks/index-ezgj0tr1.js +88 -0
  67. package/dist/server/_chunks/index-fjv96re1.js +5295 -0
  68. package/dist/server/_chunks/index-fn1chkkd.js +214 -0
  69. package/dist/server/_chunks/index-fp8r0sqs.js +64 -0
  70. package/dist/server/_chunks/index-gcmj16xy.js +92 -0
  71. package/dist/server/_chunks/index-gkndxkxf.js +736 -0
  72. package/dist/server/_chunks/index-grta8ap3.js +238 -0
  73. package/dist/server/_chunks/index-h2a70tg4.js +7 -0
  74. package/dist/server/_chunks/index-h4xy648y.js +51 -0
  75. package/dist/server/_chunks/index-hdbwtdv2.js +352 -0
  76. package/dist/server/_chunks/index-he6n2bvp.js +1270 -0
  77. package/dist/server/_chunks/index-j0m1jxqa.js +1253 -0
  78. package/dist/server/_chunks/index-j2zrcp1k.js +66 -0
  79. package/dist/server/_chunks/index-j681c48a.js +2530 -0
  80. package/dist/server/_chunks/index-jbj7ehef.js +50 -0
  81. package/dist/server/_chunks/index-jcsjq3d1.js +20 -0
  82. package/dist/server/_chunks/index-jks0x0a1.js +14 -0
  83. package/dist/server/_chunks/index-jn9rbhrx.js +1599 -0
  84. package/dist/server/_chunks/index-jt8e1pje.js +12 -0
  85. package/dist/server/_chunks/index-kdhxpwyq.js +483 -0
  86. package/dist/server/_chunks/index-knp0s912.js +7 -0
  87. package/dist/server/_chunks/index-kqyy1d4f.js +50 -0
  88. package/dist/server/_chunks/index-kresp2n8.js +445 -0
  89. package/dist/server/_chunks/index-m63czgq5.js +590 -0
  90. package/dist/server/_chunks/index-ms9v9tnx.js +31 -0
  91. package/dist/server/_chunks/index-n4hxpng2.js +11 -0
  92. package/dist/server/_chunks/index-n629dy38.js +1071 -0
  93. package/dist/server/_chunks/index-nf3df4jy.js +266 -0
  94. package/dist/server/_chunks/index-nqh3xhdc.js +57 -0
  95. package/dist/server/_chunks/index-pym1cezq.js +39 -0
  96. package/dist/server/_chunks/index-q3r9z8mq.js +213 -0
  97. package/dist/server/_chunks/index-q738kkhj.js +100 -0
  98. package/dist/server/_chunks/index-qcrvt6er.js +112 -0
  99. package/dist/server/_chunks/index-qjj6gddn.js +54 -0
  100. package/dist/server/_chunks/index-qpwsgt3a.js +158 -0
  101. package/dist/server/_chunks/index-r97ptz1q.js +97 -0
  102. package/dist/server/_chunks/index-ras0sdab.js +26 -0
  103. package/dist/server/_chunks/index-rcs322t1.js +19 -0
  104. package/dist/server/_chunks/index-rgj5mx84.js +101 -0
  105. package/dist/server/_chunks/index-rjegyfx4.js +1435 -0
  106. package/dist/server/_chunks/index-rt9qq1va.js +4 -0
  107. package/dist/server/_chunks/index-rthrsed5.js +356 -0
  108. package/dist/server/_chunks/index-sq3kvz3g.js +200 -0
  109. package/dist/server/_chunks/index-stbk3jhn.js +31 -0
  110. package/dist/server/_chunks/index-tf4j19ge.js +130 -0
  111. package/dist/server/_chunks/index-tgcakdxc.js +125 -0
  112. package/dist/server/_chunks/index-tgsfjmep.js +74 -0
  113. package/dist/server/_chunks/index-tq1f96v7.js +51 -0
  114. package/dist/server/_chunks/index-tq3b39a5.js +107 -0
  115. package/dist/server/_chunks/index-typcbcsv.js +819 -0
  116. package/dist/server/_chunks/index-v1ags1na.js +132 -0
  117. package/dist/server/_chunks/index-vmr35jjw.js +170 -0
  118. package/dist/server/_chunks/index-vvq7rqtn.js +329 -0
  119. package/dist/server/_chunks/index-vz0km5qk.js +33 -0
  120. package/dist/server/_chunks/index-w6h8m6vp.js +138 -0
  121. package/dist/server/_chunks/index-web361b8.js +138 -0
  122. package/dist/server/_chunks/index-wm36rj4v.js +311 -0
  123. package/dist/server/_chunks/index-x0y005nh.js +283 -0
  124. package/dist/server/_chunks/index-x2m17v9c.js +6166 -0
  125. package/dist/server/_chunks/index-x3yn4ywm.js +22 -0
  126. package/dist/server/_chunks/index-x9c55pzy.js +871 -0
  127. package/dist/server/_chunks/index-xh1nbk46.js +1299 -0
  128. package/dist/server/_chunks/index-xvj80c3a.js +30 -0
  129. package/dist/server/_chunks/index-xywjg2vc.js +100 -0
  130. package/dist/server/_chunks/index-y4d02znn.js +66 -0
  131. package/dist/server/_chunks/index-y7mzvq2q.js +110 -0
  132. package/dist/server/_chunks/index-ye9hmsvf.js +47 -0
  133. package/dist/server/_chunks/index-yhxkxghe.js +56 -0
  134. package/dist/server/_chunks/index-yrjkqtfj.js +86 -0
  135. package/dist/server/_chunks/index-z88qv16t.js +11 -0
  136. package/dist/server/_chunks/index-zfrwq472.js +154 -0
  137. package/dist/server/_chunks/index-zqpqyg8p.js +666 -0
  138. package/dist/server/_chunks/loader-5en7wpt5.js +80 -0
  139. package/dist/server/_chunks/modules-t92favpj.js +76 -0
  140. package/dist/server/_chunks/pipeline-4t9q43ht.js +836 -0
  141. package/dist/server/_chunks/production-385bw8df.js +55 -0
  142. package/dist/server/_chunks/proxy-config-60gprphe.js +23 -0
  143. package/dist/server/_chunks/proxy-gfhmktbk.js +35 -0
  144. package/dist/server/_chunks/router-xnrva921.js +501 -0
  145. package/dist/server/_chunks/routes-ht868pdc.js +66 -0
  146. package/dist/server/_chunks/run-d40d63ag.js +697 -0
  147. package/dist/server/_chunks/server-2p8fgqq2.js +1839 -0
  148. package/dist/server/_chunks/tsconfig-defaults-bcn34eqb.js +21 -0
  149. package/dist/server/_chunks/typegen-dvtcjtaw.js +37 -0
  150. package/dist/server/api/client-cache.js +14 -0
  151. package/dist/server/cli/index.js +375 -0
  152. package/dist/server/cli/start.js +158 -0
  153. package/dist/server/client/reference.js +16 -0
  154. package/dist/server/compat/next/cache.js +56 -0
  155. package/dist/server/compat/next/canonical-url.js +13 -0
  156. package/dist/server/compat/next/client-cache.js +57 -0
  157. package/dist/server/compat/next/client-navigation.js +232 -0
  158. package/dist/server/compat/next/client-only.js +1 -0
  159. package/dist/server/compat/next/client-script.js +178 -0
  160. package/dist/server/compat/next/client-server.js +34 -0
  161. package/dist/server/compat/next/config-headers.js +34 -0
  162. package/dist/server/compat/next/config-loader.js +25 -0
  163. package/dist/server/compat/next/config.js +10 -0
  164. package/dist/server/compat/next/constants.js +18 -0
  165. package/dist/server/compat/next/custom-server.js +10 -0
  166. package/dist/server/compat/next/dist/client/components/app-router-headers.js +48 -0
  167. package/dist/server/compat/next/dist/server/web/spec-extension/revalidate.js +27 -0
  168. package/dist/server/compat/next/dist/server/web/spec-extension/unstable-cache.js +25 -0
  169. package/dist/server/compat/next/dist/server/web/spec-extension/unstable-no-store.js +25 -0
  170. package/dist/server/compat/next/dynamic.js +21 -0
  171. package/dist/server/compat/next/error.js +112 -0
  172. package/dist/server/compat/next/font/cache.js +16 -0
  173. package/dist/server/compat/next/font/google.js +3 -0
  174. package/dist/server/compat/next/font/index.js +1 -0
  175. package/dist/server/compat/next/font/local.js +24 -0
  176. package/dist/server/compat/next/font/runtime-client.js +33 -0
  177. package/dist/server/compat/next/font/runtime.js +43 -0
  178. package/dist/server/compat/next/font/shared.js +22 -0
  179. package/dist/server/compat/next/form.js +129 -0
  180. package/dist/server/compat/next/head.js +10 -0
  181. package/dist/server/compat/next/headers.js +30 -0
  182. package/dist/server/compat/next/image/client.js +11 -0
  183. package/dist/server/compat/next/image/config.js +18 -0
  184. package/dist/server/compat/next/image/optimizer.js +25 -0
  185. package/dist/server/compat/next/image/patterns.js +12 -0
  186. package/dist/server/compat/next/image/props.js +16 -0
  187. package/dist/server/compat/next/image/shared.js +86 -0
  188. package/dist/server/compat/next/image/static-metadata.js +32 -0
  189. package/dist/server/compat/next/image/validate.js +8 -0
  190. package/dist/server/compat/next/image.js +59 -0
  191. package/dist/server/compat/next/index.js +1 -0
  192. package/dist/server/compat/next/legacy-image.js +78 -0
  193. package/dist/server/compat/next/legacy-request-apis.js +24 -0
  194. package/dist/server/compat/next/link-transform.js +15 -0
  195. package/dist/server/compat/next/link.js +14 -0
  196. package/dist/server/compat/next/navigation.js +191 -0
  197. package/dist/server/compat/next/offline.js +45 -0
  198. package/dist/server/compat/next/og.js +8 -0
  199. package/dist/server/compat/next/preferred-region.js +8 -0
  200. package/dist/server/compat/next/redirects.js +32 -0
  201. package/dist/server/compat/next/resource-hints.js +30 -0
  202. package/dist/server/compat/next/rewrites.js +43 -0
  203. package/dist/server/compat/next/root-params.js +121 -0
  204. package/dist/server/compat/next/route-state.js +23 -0
  205. package/dist/server/compat/next/router.js +103 -0
  206. package/dist/server/compat/next/script.js +37 -0
  207. package/dist/server/compat/next/server-only.js +1 -0
  208. package/dist/server/compat/next/server.js +131 -0
  209. package/dist/server/compat/next/svgr.js +14 -0
  210. package/dist/server/compat/next/telemetry.js +10 -0
  211. package/dist/server/compat/next/usage.js +13 -0
  212. package/dist/server/compat/next/user-agent.js +12 -0
  213. package/dist/server/compat/next/web-vitals.js +35 -0
  214. package/dist/server/compat/react/action-state.js +11 -0
  215. package/dist/server/compat/react/client-lite.js +83 -0
  216. package/dist/server/compat/react/client.js +131 -0
  217. package/dist/server/compat/react/compiler-runtime.js +42 -0
  218. package/dist/server/compat/react/dom-client.js +106 -0
  219. package/dist/server/compat/react/dom-react-server.js +37 -0
  220. package/dist/server/compat/react/dom-server.js +40 -0
  221. package/dist/server/compat/react/dom.js +52 -0
  222. package/dist/server/compat/react/hooks-extra.js +12 -0
  223. package/dist/server/compat/react/internals.js +14 -0
  224. package/dist/server/compat/react/parity.js +11 -0
  225. package/dist/server/compat/react/preact.js +113 -0
  226. package/dist/server/compat/react/react-server.js +133 -0
  227. package/dist/server/compat/react/router-shim.js +8 -0
  228. package/dist/server/compat/react/server-component-use.js +14 -0
  229. package/dist/server/compat/react/server-inserted-html.js +14 -0
  230. package/dist/server/compat/react/server.js +194 -0
  231. package/dist/server/compat/react/style-values.js +10 -0
  232. package/dist/server/compat/react/use.js +10 -0
  233. package/dist/server/compat/react/view-transition.js +12 -0
  234. package/dist/server/generation.json +1 -0
  235. package/package.json +4 -1
  236. package/reference/compat.md +1 -1
  237. package/reference/config.md +2 -2
  238. package/reference/dev.md +1 -1
  239. package/reference/getting-started.md +1 -1
  240. package/reference/rendering.md +4 -4
  241. package/src/api/dynamic.tsx +86 -4
  242. package/src/api/server.ts +24 -6
  243. package/src/cli/adapters/vercel-warm.ts +327 -62
  244. package/src/cli/adapters/vercel.ts +164 -1054
  245. package/src/cli/analyze.ts +3 -2
  246. package/src/cli/build/cache.ts +6 -6
  247. package/src/cli/build.ts +78 -45
  248. package/src/cli/index.ts +21 -3
  249. package/src/cli/serve/immutable.ts +31 -0
  250. package/src/cli/serve/pipeline.ts +97 -93
  251. package/src/cli/serve/standalone-worker.ts +8 -0
  252. package/src/cli/serve/standalone.ts +703 -0
  253. package/src/cli/start.ts +31 -8
  254. package/src/client/build.ts +1 -1
  255. package/src/client/chunk-fold.ts +19 -6
  256. package/src/client/react-tier.ts +3 -2
  257. package/src/client/reference-stub.ts +23 -36
  258. package/src/compat/actions/client-plugin.ts +4 -2
  259. package/src/compat/actions/config.ts +6 -5
  260. package/src/compat/actions/extensions.ts +5 -5
  261. package/src/compat/aliases.ts +8 -1
  262. package/src/compat/bundler/config.ts +88 -0
  263. package/src/compat/bundler/optimize-package-imports.ts +86 -26
  264. package/src/compat/bundler/relay-transform.ts +15 -3
  265. package/src/compat/bundler/require-context.ts +2 -1
  266. package/src/compat/bundler/webpack-loaders.ts +3 -2
  267. package/src/compat/bundler/worker.ts +2 -2
  268. package/src/compat/cache-control.ts +16 -8
  269. package/src/compat/css/inline-css.ts +1 -1
  270. package/src/compat/css/modules.ts +18 -5
  271. package/src/compat/export/build-complete.ts +1 -1
  272. package/src/compat/export/emit.ts +4 -4
  273. package/src/compat/export/standalone.ts +30 -65
  274. package/src/compat/image-optimizer/optimize.ts +1 -1
  275. package/src/compat/lifecycle/error-funnel.ts +1 -1
  276. package/src/compat/lifecycle/instrumentation-client.ts +4 -3
  277. package/src/compat/lifecycle/instrumentation.ts +117 -24
  278. package/src/compat/lifecycle/testmode.ts +3 -2
  279. package/src/compat/metadata-artifacts.ts +1 -1
  280. package/src/compat/next/config-loader.ts +140 -13
  281. package/src/compat/next/dynamic.tsx +10 -23
  282. package/src/compat/next/font/cache.ts +2 -1
  283. package/src/compat/next/font/runtime.ts +23 -3
  284. package/src/compat/next/og.ts +3 -3
  285. package/src/compat/next/usage.ts +3 -2
  286. package/src/compat/pages/api.ts +83 -14
  287. package/src/compat/pages/router.ts +30 -14
  288. package/src/compat/register/bundler.ts +3 -1
  289. package/src/compat/register/image.ts +1 -1
  290. package/src/compat/register/middleware.ts +1 -1
  291. package/src/compat/register/otel.ts +5 -9
  292. package/src/compat/register/render.ts +3 -1
  293. package/src/compat/register/use-cache.ts +3 -1
  294. package/src/compat/release.ts +46 -0
  295. package/src/compat/segment/serve.ts +1 -1
  296. package/src/compat/typecheck/check.ts +6 -4
  297. package/src/compat/validation/validate.ts +10 -7
  298. package/src/config.ts +76 -9
  299. package/src/css/assets.ts +213 -0
  300. package/src/css/build.ts +190 -247
  301. package/src/dev/client-chunk-store.ts +2 -1
  302. package/src/dev/restart/client-key.ts +2 -1
  303. package/src/dev/restart/global-css.ts +2 -1
  304. package/src/dev/restart/route-bundle-key.ts +2 -1
  305. package/src/dev/restart/route-facts.ts +2 -1
  306. package/src/dev/server.ts +34 -14
  307. package/src/extensions.ts +4 -0
  308. package/src/out-paths.ts +10 -0
  309. package/src/render/hooks.ts +13 -0
  310. package/src/render/renderer.ts +53 -33
  311. package/src/request/context.ts +9 -0
  312. package/src/resolve/dynamic.ts +7 -1
  313. package/src/resolve/engine.ts +2 -12
  314. package/src/resolve/imports.ts +12 -52
  315. package/src/resolve/package-json.ts +56 -0
  316. package/src/resolve/scan-facts.ts +250 -1
  317. package/src/routing/href.ts +10 -3
  318. package/src/routing/match.ts +287 -0
  319. package/src/routing/metadata-files.ts +20 -6
  320. package/src/routing/proxy-config.ts +137 -0
  321. package/src/routing/proxy.ts +35 -134
  322. package/src/routing/routes.ts +22 -281
  323. package/src/runtime/fingerprint.ts +23 -8
  324. package/src/runtime/load.ts +170 -0
  325. package/src/runtime/loader.ts +218 -44
  326. package/src/runtime/module-cache.ts +15 -1
  327. package/src/runtime/modules.ts +405 -82
  328. package/src/runtime/prebundle.ts +101 -0
  329. package/src/runtime/production.ts +456 -0
  330. package/src/runtime/release-graph.ts +342 -0
  331. package/src/runtime/vendor-build.ts +750 -17
  332. package/src/runtime/vendor.ts +16 -3
  333. package/src/types.ts +4 -3
  334. package/src/utils/directive.ts +40 -0
  335. package/src/utils/fs.ts +95 -4
  336. package/src/utils/resource-query.ts +12 -0
  337. package/src/cli/serve/entry.ts +0 -45
@@ -9,9 +9,18 @@
9
9
  * the alias/asset helpers this module reuses from it - lives in `./server`.
10
10
  */
11
11
 
12
- import { builtinModules, createRequire } from 'node:module'
13
- import { existsSync, readFileSync, realpathSync, statSync } from 'node:fs'
14
- import { copyFile, mkdir, readFile, rm, symlink, writeFile } from 'node:fs/promises'
12
+ import { builtinModules, createRequire, isBuiltin } from 'node:module'
13
+ import {
14
+ appendFileSync,
15
+ existsSync,
16
+ mkdirSync,
17
+ readFileSync,
18
+ readlinkSync,
19
+ realpathSync,
20
+ statSync,
21
+ writeFileSync,
22
+ } from 'node:fs'
23
+ import { copyFile, mkdir, readFile, rename, rm, symlink, writeFile } from 'node:fs/promises'
15
24
  import path from 'node:path'
16
25
  import { fileURLToPath } from 'node:url'
17
26
  import type { Loader, Metafile, OnLoadResult, OnResolveResult, Plugin } from 'esbuild'
@@ -25,7 +34,12 @@ import {
25
34
  serverDefineOptions,
26
35
  type ServerEsbuildPluginOptions,
27
36
  } from '../extensions'
28
- import { pathToFileHref, type CompatAliasTarget, type ResolvedConfig } from '../config'
37
+ import {
38
+ pathToFileHref,
39
+ type CompatAliasTarget,
40
+ type ResolvedConfig,
41
+ outCachePath,
42
+ } from '../config'
29
43
  import {
30
44
  cacheRoot,
31
45
  devArtifactUsable,
@@ -33,6 +47,7 @@ import {
33
47
  noteDevArtifactWritten,
34
48
  } from './module-cache'
35
49
  import { frameworkFingerprint } from './fingerprint'
50
+ import { isReleaseCompile } from './load'
36
51
  import {
37
52
  getExternalPackagePolicy,
38
53
  isEsmModuleEntry,
@@ -55,8 +70,10 @@ import {
55
70
  } from '../client/reference-stub'
56
71
  import { noteCompiledClientReference } from '../client/reference'
57
72
  import { reactCompatEnabled } from '../render/hooks'
58
- import { writeFileAtomic } from '../utils/fs'
73
+ import { toPosixPath, writeFileAtomic } from '../utils/fs'
74
+ import { releaseArtifactSource } from './modules'
59
75
  import { formatDuration } from '../utils/verbose'
76
+ import { hasModuleState, importMetaRefs, scanFacts } from '../resolve/scan-facts'
60
77
  import {
61
78
  addCommonJsNamedExports,
62
79
  canonicalVendorCode,
@@ -87,6 +104,9 @@ import {
87
104
  coreAliases,
88
105
  firstAliasForSpecifier,
89
106
  hashBundleSpecifier,
107
+ nodeModuleSourceLoadsAsIs,
108
+ recordNativeNodeModuleFiles,
109
+ statSignature,
90
110
  packageJsxLoaders,
91
111
  runtimeAliasBuildPlugin,
92
112
  serverAssetPlugin,
@@ -114,6 +134,11 @@ export function clearServerRuntimeCaches() {
114
134
  vendorReuseLoaded.clear()
115
135
  vendorReuseProbes.clear()
116
136
  vendorLayerProbeMemo.clear()
137
+ nativeServerEntries.clear()
138
+ nativeServerFiles.clear()
139
+ nativeVerdictStores.clear()
140
+ stateCompanions.clear()
141
+ packageFileScans.clear()
117
142
  // The marker memo tracks files under `.pnext`; a wipe takes them with it.
118
143
  esmDistPackages.clear()
119
144
  clearProvidedEntryResolutions()
@@ -130,6 +155,14 @@ export async function externalServerPackageHref(
130
155
  nested = false,
131
156
  ) {
132
157
  let plan = vendorBuildPlan(config, specifier, target, resolveDir, conditionTarget)
158
+ const demand = (file: string) => {
159
+ if (!nested) noteReleaseDependency(config, file, specifier, resolveDir, target, conditionTarget)
160
+ return pathToFileHref(file)
161
+ }
162
+ if (target === 'server' && conditionTarget === 'server' && isPackageSpecifier(specifier)) {
163
+ const native = await nativeServerEntry(config, specifier, resolveDir)
164
+ if (native) return demand(native)
165
+ }
133
166
  const aliased = await crossLayerReusePlan(
134
167
  config,
135
168
  specifier,
@@ -158,7 +191,540 @@ export async function externalServerPackageHref(
158
191
  if (plan.group) dropVendorGroup(plan.group.key)
159
192
  file = await vendorBundle(plan, nested)
160
193
  }
161
- return pathToFileHref(file)
194
+ return demand(file)
195
+ }
196
+
197
+ // --------------------------------------------------------------------------
198
+ // release dependency graph
199
+ // --------------------------------------------------------------------------
200
+
201
+ /**
202
+ * A release bundles each App Router layer's server dependencies into one graph, as Next does. The
203
+ * compile still vendors (or loads natively) what the build's own prerender imports, and logs each
204
+ * App Router demand so the release can repoint its modules at the linked graph once all compile.
205
+ */
206
+ export interface ReleaseDependency {
207
+ /** What the compile handed out: a vendor artifact or a natively loaded file. */
208
+ file: string
209
+ layer: ReleaseDependencyLayer
210
+ specifier: string
211
+ /** The file this layer resolves `specifier` to: the graph's entry. */
212
+ entry?: string
213
+ }
214
+
215
+ /** The App Router node layers, `<target>:<conditionTarget>`: RSC and client components' SSR. */
216
+ export type ReleaseDependencyLayer = 'server:server' | 'client:client'
217
+
218
+ const releaseDependencyLayers = new Set<string>(['server:server', 'client:client'])
219
+ const loggedReleaseDependencies = new Set<string>()
220
+
221
+ export function releaseDependencyLog(config: ResolvedConfig) {
222
+ return path.join(cacheRoot(config.outPath), 'dependencies.jsonl')
223
+ }
224
+
225
+ /** @internal Each build starts from an emptied out dir. */
226
+ export function clearReleaseDependencyLog() {
227
+ loggedReleaseDependencies.clear()
228
+ }
229
+
230
+ /** Appended, not held: the build and its warm child both compile. */
231
+ function noteReleaseDependency(
232
+ config: ResolvedConfig,
233
+ file: string,
234
+ specifier: string,
235
+ resolveDir: string,
236
+ target: CompatAliasTarget,
237
+ conditionTarget: ServerBundleTarget,
238
+ ) {
239
+ const layer = `${target}:${conditionTarget}` as ReleaseDependencyLayer
240
+ if (!isReleaseCompile() || !releaseDependencyLayers.has(layer)) return
241
+ const entry = resolveVendorEntry(config, specifier, resolveDir, conditionTarget)
242
+ appendReleaseLog(config, { file, layer, specifier, entry } satisfies Partial<ReleaseDependency>)
243
+ }
244
+
245
+ /** A release module compiled for another layer than its artifact directory's (a `'use client'` file). */
246
+ export function noteReleaseModuleLayer(
247
+ config: ResolvedConfig,
248
+ module: string,
249
+ target: CompatAliasTarget,
250
+ conditionTarget: ServerBundleTarget,
251
+ ) {
252
+ if (isReleaseCompile())
253
+ appendReleaseLog(config, { module, layer: `${target}:${conditionTarget}` })
254
+ }
255
+
256
+ function appendReleaseLog(config: ResolvedConfig, record: object) {
257
+ const line = JSON.stringify(record)
258
+ const log = releaseDependencyLog(config)
259
+ if (loggedReleaseDependencies.has(`${log}\0${line}`)) return
260
+ loggedReleaseDependencies.add(`${log}\0${line}`)
261
+ mkdirSync(path.dirname(log), { recursive: true })
262
+ appendFileSync(log, `${line}\n`)
263
+ }
264
+
265
+ /**
266
+ * Link one layer's dependency entries in one splitting build: each package file becomes one module and
267
+ * unused code drops out. `serverExternalPackages` and Next's built-in list stay external where the
268
+ * importer resolves them as the root does; Next bundles another copy. A package that reads files beside
269
+ * itself (`import.meta.url`) or imports computed paths loads in place, as in 0.1.6: Next's webpack
270
+ * contexts bundle computed imports instead. Returns the files written and the entries left in place.
271
+ */
272
+ /**
273
+ * esbuild names CommonJS modules by path from `absWorkingDir`; a `node_modules` linked from outside
274
+ * the app makes those names climb to the build machine's absolute path. The quoted prefix to rewrite.
275
+ */
276
+ function machineModuleKeyPrefix(root: string) {
277
+ let real: string
278
+ try {
279
+ real = realpathSync(path.join(root, 'node_modules'))
280
+ } catch {
281
+ return undefined
282
+ }
283
+ const relative = toPosixPath(path.relative(root, real))
284
+ return relative.startsWith('../') ? `"${relative}/` : undefined
285
+ }
286
+
287
+ export async function linkReleaseDependencies(
288
+ config: ResolvedConfig,
289
+ layer: ReleaseDependencyLayer,
290
+ entries: readonly { in: string; out: string }[],
291
+ outdir: string,
292
+ ) {
293
+ const [target, conditionTarget] = layer.split(':') as [CompatAliasTarget, ServerBundleTarget]
294
+ const unresolved = new Set<string>()
295
+ const inPlace = new Set<string>()
296
+ const ordered = [...entries].sort((a, b) => (a.out < b.out ? -1 : 1))
297
+ let linked = ordered
298
+ let outputs: { path: string; text: string }[]
299
+ for (;;) {
300
+ let result
301
+ try {
302
+ result = await build({
303
+ entryPoints: linked.map(entry => ({
304
+ in:
305
+ getBundlerExtensions().serverBundleEntry(entry.in, path.dirname(entry.in), entry.in) ??
306
+ entry.in,
307
+ out: entry.out,
308
+ })),
309
+ absWorkingDir: config.root,
310
+ bundle: true,
311
+ splitting: true,
312
+ write: false,
313
+ metafile: true,
314
+ outdir,
315
+ chunkNames: 'chunks/[name]-[hash]',
316
+ format: 'esm',
317
+ platform: 'neutral',
318
+ target: 'es2022',
319
+ conditions: serverBundleConditions(conditionTarget),
320
+ mainFields: ['module', 'main'],
321
+ loader: packageJsxLoaders,
322
+ jsx: 'automatic',
323
+ jsxImportSource: 'preact',
324
+ logLevel: 'silent',
325
+ ...serverDefineOptions(),
326
+ plugins: profiledVendorPlugins([
327
+ serverAssetPlugin(config),
328
+ ...getBundlerExtensions().serverEsbuildPlugins(
329
+ config,
330
+ vendorPluginOptions(config, target, conditionTarget),
331
+ ),
332
+ runtimeAliasBuildPlugin(config, target, {
333
+ bundleRequireAliases: true,
334
+ reactServerLayer: target === 'server',
335
+ }),
336
+ releaseExternalsPlugin(config, conditionTarget, unresolved, inPlace),
337
+ ]),
338
+ })
339
+ } catch (error) {
340
+ // An import that resolves nowhere stays a bare external, as in a vendor bundle: it fails when it runs.
341
+ const missing = unresolvedBareImports(error).filter(specifier => !unresolved.has(specifier))
342
+ if (missing.length === 0) throw error
343
+ for (const specifier of missing) unresolved.add(specifier)
344
+ continue
345
+ }
346
+ const found = (await inPlacePackages(config, result)).filter(root => !inPlace.has(root))
347
+ if (found.length === 0) {
348
+ outputs = result.outputFiles
349
+ break
350
+ }
351
+ for (const root of found) inPlace.add(root)
352
+ linked = ordered.filter(entry => !inPlace.has(nearestManifestDir(path.dirname(entry.in))!))
353
+ if (linked.length === 0) return { files: [], inPlace: ordered.map(entry => entry.in) }
354
+ }
355
+ const entryFiles = new Set(linked.map(entry => path.join(outdir, `${entry.out}.js`)))
356
+ const texts = new Map(outputs.map(output => [output.path, output.text]))
357
+ // Written beside the graph and renamed into place, so a crash never leaves half a graph.
358
+ const staging = `${outdir}.${process.pid}.tmp`
359
+ await rm(staging, { recursive: true, force: true })
360
+ try {
361
+ await mkdir(path.join(staging, 'chunks'), { recursive: true })
362
+ const keys = machineModuleKeyPrefix(config.root)
363
+ for (const output of outputs) {
364
+ const text = keys ? output.text.replaceAll(keys, '"node_modules/') : output.text
365
+ const code = entryFiles.has(output.path)
366
+ ? addCommonJsNamedExports(text, sharedCommonJsBody(output.path, text, texts))
367
+ : text
368
+ await writeFile(
369
+ path.join(staging, path.relative(outdir, output.path)),
370
+ releaseArtifactSource(
371
+ output.path,
372
+ target === 'server' ? applyBundledSourceTransforms(code, output.path) : code,
373
+ ),
374
+ )
375
+ }
376
+ await rm(outdir, { recursive: true, force: true })
377
+ await rename(staging, outdir)
378
+ } catch (error) {
379
+ await rm(staging, { recursive: true, force: true })
380
+ throw error
381
+ }
382
+ return {
383
+ files: outputs.map(output => output.path).sort(),
384
+ inPlace: ordered.filter(entry => !linked.includes(entry)).map(entry => entry.in),
385
+ }
386
+ }
387
+
388
+ // `import.meta` paths and an `import()` of a computed path resolve where the file lies; an import marked
389
+ // `webpackIgnore`/`@vite-ignore` is meant to stay a runtime import, as Next's bundle keeps it.
390
+ /** Reads its own location or imports a computed path (unless it opts out with a magic comment). */
391
+ function readsBesideItself(file: string, source: string) {
392
+ const refs = importMetaRefs(file, source)
393
+ if (refs.readsLocation) return true
394
+ return refs.computedImports.some(
395
+ call => !/webpackIgnore:\s*true|@vite-ignore/.test(source.slice(call.start, call.end)),
396
+ )
397
+ }
398
+
399
+ /** Package dirs whose files read beside themselves or import computed paths. */
400
+ async function inPlacePackages(
401
+ config: ResolvedConfig,
402
+ result: { metafile?: Metafile; warnings: { text: string; location: { file: string } | null }[] },
403
+ ) {
404
+ const files = new Set(
405
+ result.warnings
406
+ .filter(warning => warning.text.includes('is not a string literal') && warning.location)
407
+ .map(warning => path.resolve(config.root, warning.location!.file)),
408
+ )
409
+ const installed = Object.keys(result.metafile?.inputs ?? {})
410
+ .filter(input => !input.includes(':') && input.includes('node_modules/'))
411
+ .map(input => path.resolve(config.root, input))
412
+ await Promise.all(
413
+ installed.map(async file => {
414
+ const source = await readFile(file, 'utf8').catch(() => '')
415
+ if (readsBesideItself(file, source)) files.add(file)
416
+ }),
417
+ )
418
+ return [
419
+ ...new Set(
420
+ [...files]
421
+ .filter(file => file.includes(`${path.sep}node_modules${path.sep}`))
422
+ .map(file => nearestManifestDir(path.dirname(canonicalVendorEntry(file)!)))
423
+ .filter((root): root is string => root !== undefined),
424
+ ),
425
+ ]
426
+ }
427
+
428
+ /**
429
+ * Under splitting, a CommonJS entry's body can land in a shared chunk the entry only calls
430
+ * (`export default require_x()`): its named exports are read from that body.
431
+ */
432
+ function sharedCommonJsBody(file: string, code: string, texts: ReadonlyMap<string, string>) {
433
+ const wrapper = /(?:^|\n)export default (require_[\w$]+)\(\);?/.exec(code)?.[1]
434
+ // The chunk that defines the wrapper, from the parsed imports: `$` in its name is no regex hazard.
435
+ const chunk =
436
+ wrapper && scanFacts(file, code).imports.find(edge => edge.exports.includes(wrapper))?.specifier
437
+ const text = chunk && texts.get(path.resolve(path.dirname(file), chunk))
438
+ if (!text) return code
439
+ const start = text.indexOf(`var ${wrapper} = `)
440
+ if (start === -1) return code
441
+ const next = text.indexOf('\nvar require_', start + 1)
442
+ return text.slice(start, next === -1 ? undefined : next)
443
+ }
444
+
445
+ const frameworkPackageName = (
446
+ JSON.parse(readFileSync(path.resolve(import.meta.dirname, '../../package.json'), 'utf8')) as {
447
+ name: string
448
+ }
449
+ ).name
450
+
451
+ function releaseExternalsPlugin(
452
+ config: ResolvedConfig,
453
+ conditionTarget: ServerBundleTarget,
454
+ unresolved: ReadonlySet<string>,
455
+ inPlace: ReadonlySet<string>,
456
+ ): Plugin {
457
+ const policy = getExternalPackagePolicy()
458
+ return {
459
+ name: 'pnext-release-externals',
460
+ setup(build) {
461
+ build.onResolve({ filter: /^[^./]/ }, async args => {
462
+ const name = packageNameFromSpecifier(args.path)
463
+ if (!name || args.kind === 'entry-point') return undefined
464
+ // Builtins, unresolvable imports and the framework (the runtime's own instance) stay external.
465
+ if (
466
+ unresolved.has(args.path) ||
467
+ name === frameworkPackageName ||
468
+ isBuiltin(args.path) ||
469
+ builtinModules.includes(name) ||
470
+ isBunBuiltin(name)
471
+ ) {
472
+ return { path: args.path, external: true }
473
+ }
474
+ const conditions =
475
+ args.kind === 'require-call'
476
+ ? serverBundleRequireConditions(conditionTarget)
477
+ : serverBundleConditions(conditionTarget)
478
+ const external = policy.external(name) || policy.releaseExternal?.(name)
479
+ if (!external && inPlace.size === 0) return undefined
480
+ const fromImporter = resolveNestedPackageFromImporter(args.importer, args.path, conditions)
481
+ // A package that loads in place is imported where it lies, when Bun loads it as-is.
482
+ const loaded = fromImporter && canonicalVendorEntry(fromImporter)!
483
+ if (
484
+ loaded &&
485
+ inPlace.has(nearestManifestDir(path.dirname(loaded))!) &&
486
+ isEsmModuleEntry(loaded) &&
487
+ (await nativeServerGraph(loaded))
488
+ ) {
489
+ return { path: loaded, external: true }
490
+ }
491
+ if (!external) return undefined
492
+ const fromRoot = resolveVendorPackageSpecifier(
493
+ config.root,
494
+ path.join(config.root, 'pnext-resolve.ts'),
495
+ args.path,
496
+ conditions,
497
+ )
498
+ return fromImporter && fromImporter === fromRoot
499
+ ? { path: args.path, external: true }
500
+ : undefined
501
+ })
502
+ },
503
+ }
504
+ }
505
+
506
+ function unresolvedBareImports(error: unknown) {
507
+ const errors = (error as { errors?: { text?: string }[] } | undefined)?.errors ?? []
508
+ return errors.flatMap(({ text }) => {
509
+ const specifier = /^Could not resolve "([^"]+)"/.exec(text ?? '')?.[1]
510
+ return specifier && isPackageSpecifier(specifier) ? [specifier] : []
511
+ })
512
+ }
513
+
514
+ // --------------------------------------------------------------------------
515
+ // native server dependencies
516
+ // --------------------------------------------------------------------------
517
+
518
+ /**
519
+ * On the server a node_modules package loads natively through Bun, as Vite SSR and Node load it: one
520
+ * module instance per resolved file and no build. A bundle per subpath would inline its own copy of
521
+ * every shared internal file and split module state (better-auth's request-state slots).
522
+ *
523
+ * It is bundled only when pnext has to transform something its server graph reaches: an aliased
524
+ * import (react, next/*), a `'use client'`/`'use server'`/`'use cache'` file, a non-JS file, a
525
+ * `transpilePackages` member, or a bare import Bun resolves to a different file than the server
526
+ * layer's runtime conditions (`react-server`) select. `PNEXT_SERVER_NATIVE=0` bundles everything.
527
+ */
528
+ // eslint-disable-next-line turbo/no-undeclared-env-vars
529
+ const serverNativeEnabled = () => process.env.PNEXT_SERVER_NATIVE !== '0'
530
+
531
+ /** The server layer's conditions minus bundler-only ones (`module`): what Node would honour. */
532
+ const serverRuntimeConditions = (kind: string) =>
533
+ (kind === 'require-call'
534
+ ? serverBundleRequireConditions('server')
535
+ : serverBundleConditions('server')
536
+ ).filter(condition => condition !== 'module')
537
+
538
+ const nativeServerEntries = new Map<string, Promise<string | undefined>>()
539
+
540
+ /** The file Bun loads for `specifier` when its whole server graph needs no transform. */
541
+ function nativeServerEntry(config: ResolvedConfig, specifier: string, resolveDir: string) {
542
+ const packageName = packageNameFromSpecifier(specifier)
543
+ if (
544
+ !serverNativeEnabled() ||
545
+ serverDefineOptions().define ||
546
+ (packageName && getExternalPackagePolicy().transpile(packageName))
547
+ ) {
548
+ return Promise.resolve(undefined)
549
+ }
550
+ const key = `${resolveDir}\0${specifier}`
551
+ let entry = nativeServerEntries.get(key)
552
+ if (!entry) {
553
+ const verdicts = nativeVerdictsFor(config)
554
+ const storeKey = `${vendorPortablePath(config, resolveDir)}\0${specifier}`
555
+ const stored = verdicts.get(storeKey)
556
+ const storedEntry = stored && path.resolve(config.workspaceRoot, stored.resolved)
557
+ if (stored && statSignature(storedEntry!) === stored.signature) {
558
+ entry = Promise.resolve(stored.native ? storedEntry : undefined)
559
+ } else {
560
+ entry = nativeServerEntryUncached(config, specifier, resolveDir)
561
+ .then(native => {
562
+ const resolved = nativeResolve(specifier, resolveDir)
563
+ const signature = resolved && statSignature(resolved)
564
+ if (resolved && signature) {
565
+ const relative = path.relative(config.workspaceRoot, resolved)
566
+ verdicts.set(storeKey, { resolved: relative, signature, native: Boolean(native) })
567
+ persistNativeVerdicts()
568
+ }
569
+ return native
570
+ })
571
+ .catch(() => undefined)
572
+ }
573
+ nativeServerEntries.set(key, entry)
574
+ }
575
+ return entry
576
+ }
577
+
578
+ /**
579
+ * Verdicts outlive the process, as vendor artifacts do: re-walking every native graph made a dev
580
+ * restart pay for all of them again. One holds while its resolved entry's stat does; the store name
581
+ * carries what else a verdict reads (pnext's generation, `transpilePackages`, the alias set).
582
+ */
583
+ interface NativeVerdict {
584
+ resolved: string
585
+ signature: string
586
+ native: boolean
587
+ }
588
+
589
+ const nativeVerdictStores = new Map<
590
+ string,
591
+ { file: string; verdicts: Map<string, NativeVerdict> }
592
+ >()
593
+ let nativeVerdictFlushArmed = false
594
+
595
+ function nativeVerdictsFor(config: ResolvedConfig) {
596
+ let store = nativeVerdictStores.get(config.outPath)
597
+ if (!store) {
598
+ const inputs = JSON.stringify([
599
+ getExternalPackagePolicy().transpiled?.() ?? null,
600
+ Object.keys(coreAliases(config, 'server')).sort(),
601
+ ])
602
+ const file = vendorStoreFile(config, `native.${hashBundleSpecifier(inputs)}`)
603
+ store = { file, verdicts: new Map() }
604
+ nativeVerdictStores.set(config.outPath, store)
605
+ try {
606
+ const stored = JSON.parse(readFileSync(file, 'utf8')) as Record<string, NativeVerdict>
607
+ for (const [key, verdict] of Object.entries(stored)) store.verdicts.set(key, verdict)
608
+ } catch {
609
+ // No store yet; verdicts are written at exit.
610
+ }
611
+ }
612
+ return store.verdicts
613
+ }
614
+
615
+ function persistNativeVerdicts() {
616
+ if (nativeVerdictFlushArmed) return
617
+ nativeVerdictFlushArmed = true
618
+ process.once('exit', () => {
619
+ for (const { file, verdicts } of nativeVerdictStores.values()) {
620
+ try {
621
+ // Another process (a build's warm child) may have written since this one loaded.
622
+ const stored = JSON.parse(readFileSync(file, 'utf8')) as Record<string, NativeVerdict>
623
+ for (const [key, verdict] of Object.entries(stored)) {
624
+ if (!verdicts.has(key)) verdicts.set(key, verdict)
625
+ }
626
+ } catch {
627
+ // Nothing on disk yet.
628
+ }
629
+ try {
630
+ mkdirSync(path.dirname(file), { recursive: true })
631
+ writeFileSync(file, JSON.stringify(Object.fromEntries(verdicts)))
632
+ } catch {
633
+ // A missing store only costs the walk.
634
+ }
635
+ }
636
+ })
637
+ }
638
+
639
+ async function nativeServerEntryUncached(
640
+ config: ResolvedConfig,
641
+ specifier: string,
642
+ resolveDir: string,
643
+ ) {
644
+ const importer = path.join(resolveDir, 'pnext-resolve.ts')
645
+ const entry = nativeResolve(specifier, resolveDir)
646
+ // A CommonJS entry keeps its vendor bundle: its importers' interop (named-export facades,
647
+ // `require` of the artifact) is built around one.
648
+ if (!entry || !isEsmModuleEntry(entry)) return undefined
649
+ if (nativeImportDiffers(importer, specifier, 'import-statement', entry)) return undefined
650
+ const graph = await nativeServerGraph(entry)
651
+ if (!graph) return undefined
652
+ recordNativeNodeModuleFiles(config, graph)
653
+ return entry
654
+ }
655
+
656
+ function nativeResolve(specifier: string, fromDir: string) {
657
+ try {
658
+ return canonicalVendorEntry(Bun.resolveSync(specifier, fromDir))
659
+ } catch {
660
+ return undefined
661
+ }
662
+ }
663
+
664
+ /**
665
+ * Whether the server layer selects another file than Bun loaded, with or without `react-server`:
666
+ * every pnext process runs with that condition, but a native graph must not depend on it.
667
+ */
668
+ function nativeImportDiffers(importer: string, specifier: string, kind: string, loaded: string) {
669
+ const conditions = serverRuntimeConditions(kind)
670
+ return [conditions, conditions.filter(condition => condition !== 'react-server')].some(set => {
671
+ const selected = resolveNestedPackageFromImporter(importer, specifier, set)
672
+ return selected !== undefined && canonicalVendorEntry(selected) !== loaded
673
+ })
674
+ }
675
+
676
+ /** Per file: what it loads natively, or undefined when it needs a transform. */
677
+ const nativeServerFiles = new Map<string, Promise<string[] | undefined>>()
678
+
679
+ function nativeServerFile(file: string) {
680
+ let imports = nativeServerFiles.get(file)
681
+ if (!imports) {
682
+ imports = nativeServerFileUncached(file).catch(() => undefined)
683
+ nativeServerFiles.set(file, imports)
684
+ }
685
+ return imports
686
+ }
687
+
688
+ const nativeImportScanner = new Bun.Transpiler({ loader: 'js' })
689
+
690
+ async function nativeServerFileUncached(file: string) {
691
+ // An isolated store (`.pnpm`, `.bun`) is a layout the deployed function does not mirror.
692
+ if (!/\.[cm]?js$/.test(file) || file.includes(`${path.sep}node_modules${path.sep}.`)) {
693
+ return undefined
694
+ }
695
+ const source = await readFile(file, 'utf8')
696
+ // The runtime load plugin's own test: a file it would rewrite is not native.
697
+ if (!nodeModuleSourceLoadsAsIs(source)) return undefined
698
+ const policy = getExternalPackagePolicy()
699
+ const loads: string[] = []
700
+ for (const { path: specifier, kind } of nativeImportScanner.scanImports(source)) {
701
+ if (isBuiltin(specifier) || isBunBuiltin(specifier)) continue
702
+ const packageName = isPackageSpecifier(specifier)
703
+ ? packageNameFromSpecifier(specifier)
704
+ : undefined
705
+ // A serverExternalPackages import already loads natively through the runtime resolver.
706
+ if (packageName && policy.external(packageName)) continue
707
+ if (packageName && policy.transpile(packageName)) return undefined
708
+ // Unresolvable fails the same way bundled (a bare external), so it decides nothing.
709
+ const loaded = nativeResolve(specifier, path.dirname(file))
710
+ if (!loaded) continue
711
+ if (packageName && nativeImportDiffers(file, specifier, kind, loaded)) return undefined
712
+ loads.push(loaded)
713
+ }
714
+ return loads
715
+ }
716
+
717
+ async function nativeServerGraph(entry: string) {
718
+ const seen = new Set<string>()
719
+ let level = [entry]
720
+ while (level.length > 0) {
721
+ const fresh = [...new Set(level)].filter(file => !seen.has(file))
722
+ for (const file of fresh) seen.add(file)
723
+ const loads = await Promise.all(fresh.map(nativeServerFile))
724
+ if (loads.some(found => !found)) return undefined
725
+ level = loads.flat() as string[]
726
+ }
727
+ return seen
162
728
  }
163
729
 
164
730
  /**
@@ -196,7 +762,12 @@ const vendorPortablePath = (config: ResolvedConfig, file: string) =>
196
762
  devSourceIdentity(file, config.workspaceRoot)
197
763
 
198
764
  function vendorArtifactFile(config: ResolvedConfig, key: string) {
199
- return path.join(config.outPath, 'cache', 'server', 'vendor', `${hashBundleSpecifier(key)}.mjs`)
765
+ return path.join(
766
+ outCachePath(config.outPath),
767
+ 'server',
768
+ 'vendor',
769
+ `${hashBundleSpecifier(key)}.mjs`,
770
+ )
200
771
  }
201
772
 
202
773
  /**
@@ -295,7 +866,7 @@ function vendorBuildPlan(
295
866
  async function writeVendorArtifact(file: string, code: string) {
296
867
  const id = await publishVendorContent(file, code)
297
868
  if (id === undefined) {
298
- await writeFileAtomic(file, code)
869
+ await writeFileAtomic(file, releaseArtifactSource(file, code))
299
870
  noteDevArtifactWritten(file)
300
871
  }
301
872
  return id
@@ -316,9 +887,17 @@ function vendorContentFile(vendorDir: string, id: string) {
316
887
  async function publishVendorContent(file: string, code: string) {
317
888
  const vendorDir = path.dirname(file)
318
889
  if (vendorContentDisabled() || path.basename(vendorDir) !== 'vendor') return undefined
890
+ // First writer wins (see `linkVendorArtifact`): an alias already published keeps its bytes.
891
+ const published = /^([0-9a-f]+)\.content\.mjs$/.exec(publishedVendorContent(file) ?? '')?.[1]
892
+ if (published) {
893
+ noteVendorContentId(file, published)
894
+ noteDevArtifactWritten(file)
895
+ return published
896
+ }
319
897
  const id = hashBundleSpecifier(canonicalVendorCode(code))
320
898
  const content = vendorContentFile(vendorDir, id)
321
- if (!cachedExistsSync(content)) await writeFileAtomic(content, code)
899
+ if (!cachedExistsSync(content))
900
+ await writeFileAtomic(content, releaseArtifactSource(content, code))
322
901
  noteDevArtifactWritten(content)
323
902
  await linkVendorArtifact(file, content)
324
903
  noteVendorContentId(file, id)
@@ -326,16 +905,40 @@ async function publishVendorContent(file: string, code: string) {
326
905
  return id
327
906
  }
328
907
 
329
- /** The layer-keyed path becomes an alias to the shared bytes. */
908
+ function publishedVendorContent(file: string) {
909
+ try {
910
+ return existsSync(file) ? readlinkSync(file) : undefined
911
+ } catch {
912
+ return undefined
913
+ }
914
+ }
915
+
916
+ /**
917
+ * The layer-keyed path becomes an alias to the shared bytes. The first writer wins: a build and its
918
+ * warm child both publish, and re-pointing a live alias hands later importers a second module
919
+ * instance (the loader keys on the real path). Only a dangling alias is replaced, by rename, so a
920
+ * reader never sees it missing.
921
+ */
330
922
  async function linkVendorArtifact(file: string, content: string) {
331
- await rm(file, { force: true }).catch(() => undefined)
923
+ const link = (at: string) => symlink(path.basename(content), at)
924
+ try {
925
+ await link(file)
926
+ return
927
+ } catch (error) {
928
+ if ((error as NodeJS.ErrnoException).code === 'EEXIST' && existsSync(file)) return
929
+ }
930
+ const temp = `${file}.${process.pid.toString(36)}.${(++vendorLinkSequence).toString(36)}.link`
332
931
  try {
333
- await symlink(path.basename(content), file)
932
+ await link(temp)
933
+ await rename(temp, file)
334
934
  } catch {
935
+ await rm(temp, { force: true }).catch(() => undefined)
335
936
  await copyFile(content, file)
336
937
  }
337
938
  }
338
939
 
940
+ let vendorLinkSequence = 0
941
+
339
942
  /** What one layer's build of an entry saw, for the cross-layer comparison. */
340
943
  interface VendorLayerRecord {
341
944
  inputs: string
@@ -463,7 +1066,7 @@ function reusableVendorContent(
463
1066
  loadVendorSharedEntries(config)
464
1067
  const id = vendorSharedFor(config).get(entry)
465
1068
  if (!id) return undefined
466
- const content = vendorContentFile(path.join(config.outPath, 'cache', 'server', 'vendor'), id)
1069
+ const content = vendorContentFile(path.join(outCachePath(config.outPath), 'server', 'vendor'), id)
467
1070
  return existsSync(content) ? content : undefined
468
1071
  }
469
1072
 
@@ -1030,9 +1633,23 @@ function vendorGroupPlan(
1030
1633
  const packageName = packageNameFromSpecifier(member.specifier)
1031
1634
  const packageRoot = packageName && vendorPackageRoot(member.entry, packageName)
1032
1635
  if (!packageName || !packageRoot || !isEsmModuleEntry(member.entry)) return undefined
1636
+ const key = `${config.outPath}\0${target}\0${conditionTarget}\0${packageRoot}`
1033
1637
  return {
1034
- key: `${config.outPath}\0${target}\0${conditionTarget}\0${packageRoot}`,
1638
+ key,
1035
1639
  member,
1640
+ companions:
1641
+ target === 'server'
1642
+ ? () =>
1643
+ stateSharingCompanions(
1644
+ config,
1645
+ key,
1646
+ packageName,
1647
+ packageRoot,
1648
+ member,
1649
+ resolveDir,
1650
+ conditionTarget,
1651
+ )
1652
+ : undefined,
1036
1653
  build: (members, external, nested) =>
1037
1654
  buildVendorGroup(
1038
1655
  config,
@@ -1047,6 +1664,119 @@ function vendorGroupPlan(
1047
1664
  }
1048
1665
  }
1049
1666
 
1667
+ /**
1668
+ * A package bundled on the server keeps one instance of a stateful file only if every subpath
1669
+ * reaching it compiles in one splitting build. So the first demand pulls in the published subpaths
1670
+ * that share a stateful file with it (better-auth's `defineRequestState(...)` slots, a React
1671
+ * context), and nothing else: most packages load natively, and a build of every published subpath
1672
+ * would also run code the app never imports.
1673
+ */
1674
+ function stateSharingCompanions(
1675
+ config: ResolvedConfig,
1676
+ groupKey: string,
1677
+ packageName: string,
1678
+ packageRoot: string,
1679
+ member: VendorGroupMember,
1680
+ resolveDir: string,
1681
+ conditionTarget: ServerBundleTarget,
1682
+ ) {
1683
+ const key = `${groupKey}\0${member.specifier}`
1684
+ let companions = stateCompanions.get(key)
1685
+ if (!companions) {
1686
+ companions = (async () => {
1687
+ const shared = await statefulPackageFiles(member.entry, packageRoot)
1688
+ if (shared.length === 0) return []
1689
+ const found: VendorGroupMember[] = []
1690
+ for (const subpath of packageExportSubpaths(packageRoot)) {
1691
+ const specifier = subpath === '.' ? packageName : `${packageName}${subpath.slice(1)}`
1692
+ if (specifier === member.specifier) continue
1693
+ const sibling = vendorBuildPlan(
1694
+ config,
1695
+ specifier,
1696
+ 'server',
1697
+ resolveDir,
1698
+ conditionTarget,
1699
+ ).group
1700
+ if (sibling?.key !== groupKey || existsSync(sibling.member.file)) continue
1701
+ const reach = await packageReach(sibling.member.entry, packageRoot)
1702
+ if (shared.some(file => reach.has(file))) found.push(sibling.member)
1703
+ }
1704
+ return found.length <= MAX_STATE_COMPANIONS ? found : []
1705
+ })().catch(() => [])
1706
+ stateCompanions.set(key, companions)
1707
+ }
1708
+ return companions
1709
+ }
1710
+
1711
+ /** Past this many, the package is a hub of adapters; it keeps a build per demanded subpath. */
1712
+ const MAX_STATE_COMPANIONS = 16
1713
+ const stateCompanions = new Map<string, Promise<VendorGroupMember[]>>()
1714
+
1715
+ function packageExportSubpaths(packageRoot: string) {
1716
+ try {
1717
+ const { exports } = JSON.parse(
1718
+ readFileSync(path.join(packageRoot, 'package.json'), 'utf8'),
1719
+ ) as {
1720
+ exports?: unknown
1721
+ }
1722
+ if (!exports || typeof exports !== 'object' || Array.isArray(exports)) return []
1723
+ return Object.keys(exports).filter(
1724
+ subpath => subpath.startsWith('.') && !subpath.includes('*') && !subpath.endsWith('.json'),
1725
+ )
1726
+ } catch {
1727
+ return []
1728
+ }
1729
+ }
1730
+
1731
+ /** A top-level binding created by `new` or a non-PURE call: an identity each copy would duplicate. */
1732
+ async function statefulPackageFiles(entry: string, packageRoot: string) {
1733
+ const files = [...(await packageReach(entry, packageRoot))]
1734
+ const sources = await Promise.all(files.map(file => packageFileScan(file)))
1735
+ return files.filter((_, index) => sources[index]?.stateful)
1736
+ }
1737
+
1738
+ const packageFileScans = new Map<
1739
+ string,
1740
+ Promise<{ imports: string[]; stateful: boolean } | undefined>
1741
+ >()
1742
+ const packageScanner = new Bun.Transpiler({ loader: 'js' })
1743
+
1744
+ function packageFileScan(file: string) {
1745
+ let scan = packageFileScans.get(file)
1746
+ if (!scan) {
1747
+ scan = readFile(file, 'utf8')
1748
+ .then(source => ({
1749
+ imports: packageScanner
1750
+ .scanImports(source)
1751
+ .map(({ path: specifier }) => specifier)
1752
+ .filter(specifier => specifier.startsWith('.')),
1753
+ stateful: hasModuleState(file, source),
1754
+ }))
1755
+ .catch(() => undefined)
1756
+ packageFileScans.set(file, scan)
1757
+ }
1758
+ return scan
1759
+ }
1760
+
1761
+ /** The package's own files `entry` reaches through relative imports. */
1762
+ async function packageReach(entry: string, packageRoot: string) {
1763
+ const seen = new Set<string>()
1764
+ let level = [entry]
1765
+ while (level.length > 0) {
1766
+ const fresh = [...new Set(level)].filter(
1767
+ file => !seen.has(file) && file.startsWith(packageRoot + path.sep) && /\.[cm]?js$/.test(file),
1768
+ )
1769
+ for (const file of fresh) seen.add(file)
1770
+ const scans = await Promise.all(fresh.map(packageFileScan))
1771
+ level = scans.flatMap((scan, index) =>
1772
+ (scan?.imports ?? []).flatMap(
1773
+ specifier => nativeResolve(specifier, path.dirname(fresh[index]!)) ?? [],
1774
+ ),
1775
+ )
1776
+ }
1777
+ return seen
1778
+ }
1779
+
1050
1780
  /**
1051
1781
  * The installed package directory an entry belongs to — the grouping identity,
1052
1782
  * so a nested (non-hoisted) copy never shares a group with the hoisted one.
@@ -2666,9 +3396,12 @@ function externalBuiltinBuildPlugin(): Plugin {
2666
3396
  setup(build) {
2667
3397
  build.onResolve({ filter: /^[^./].*/ }, args => {
2668
3398
  const packageName = packageNameFromSpecifier(args.path)
2669
- if (!packageName || (!builtinModules.includes(packageName) && !isBunBuiltin(packageName))) {
2670
- return undefined
2671
- }
3399
+ // `isBuiltin` also knows the `node:`-only ones (`node:sqlite`, `node:test`).
3400
+ const builtin =
3401
+ isBuiltin(args.path) ||
3402
+ builtinModules.includes(packageName ?? '') ||
3403
+ isBunBuiltin(packageName ?? '')
3404
+ if (!packageName || !builtin) return undefined
2672
3405
  return { path: args.path, external: true }
2673
3406
  })
2674
3407
  },