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