@wular/pnext 0.0.3 → 0.0.6

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 (392) hide show
  1. package/README.md +11 -11
  2. package/bin/pnext +1 -1
  3. package/config/lint/base.js +7 -7
  4. package/config/ts/base.json +2 -4
  5. package/config/ts/react.json +2 -6
  6. package/package.json +23 -2
  7. package/reference/compat.md +1 -1
  8. package/reference/config.md +2 -2
  9. package/reference/css.md +13 -9
  10. package/reference/dev.md +1 -1
  11. package/reference/metadata.md +7 -7
  12. package/reference/navigation.md +35 -28
  13. package/reference/performance.md +70 -70
  14. package/reference/rendering.md +17 -17
  15. package/reference/routing.md +17 -17
  16. package/reference/typegen.md +12 -8
  17. package/src/api/cache.ts +36 -37
  18. package/src/api/client-cache.ts +2 -2
  19. package/src/api/client-navigation.ts +113 -109
  20. package/src/api/dynamic.tsx +58 -55
  21. package/src/api/link.tsx +49 -52
  22. package/src/api/navigation.ts +59 -58
  23. package/src/api/server.ts +152 -144
  24. package/src/api/suspense.ts +4 -4
  25. package/src/cli/adapters/vercel-warm.ts +126 -121
  26. package/src/cli/adapters/vercel.ts +437 -443
  27. package/src/cli/analyze.ts +333 -144
  28. package/src/cli/{named-bin.ts → boot/named-bin.ts} +37 -37
  29. package/src/cli/{boot-trace.ts → boot/trace.ts} +10 -10
  30. package/src/cli/build.ts +976 -978
  31. package/src/cli/create.ts +71 -63
  32. package/src/cli/dev.ts +116 -116
  33. package/src/cli/index.ts +88 -85
  34. package/src/cli/migrate/package-json.ts +73 -71
  35. package/src/cli/migrate/report.ts +32 -33
  36. package/src/cli/migrate/{index.ts → run.ts} +48 -49
  37. package/src/cli/migrate/scan.ts +46 -46
  38. package/src/cli/migrate/spinner.ts +9 -9
  39. package/src/cli/migrate/tsconfig.ts +35 -35
  40. package/src/cli/{server-entry.ts → serve/entry.ts} +57 -57
  41. package/src/cli/{request-pipeline.ts → serve/pipeline.ts} +386 -394
  42. package/src/cli/{serve-ui.ts → serve/ui.ts} +47 -47
  43. package/src/cli/start.ts +65 -66
  44. package/src/{typegen.ts → cli/typegen.ts} +59 -59
  45. package/src/client/build.ts +794 -768
  46. package/src/client/chunk-fold.ts +245 -240
  47. package/src/client/{paths.ts → chunk-name.ts} +6 -6
  48. package/src/client/entry.ts +162 -147
  49. package/src/client/prebuilt.ts +231 -223
  50. package/src/client/profile.ts +29 -29
  51. package/src/client/react-compiler.ts +20 -15
  52. package/src/client/{compat-surface.ts → react-tier.ts} +64 -64
  53. package/src/client/reference-stub.ts +51 -51
  54. package/src/client/reference.ts +19 -19
  55. package/src/{api → client}/router/events.ts +17 -17
  56. package/src/{api → client}/router/history.ts +16 -16
  57. package/src/{api → client}/router/hub.ts +52 -53
  58. package/src/{api/router.ts → client/router/index.ts} +61 -60
  59. package/src/{api → client}/router/policies.ts +24 -24
  60. package/src/{api → client}/router/runtime.ts +1988 -1961
  61. package/src/{api → client}/router/types.ts +93 -98
  62. package/src/compat/actions/client-plugin.ts +42 -43
  63. package/src/compat/actions/client-stub.ts +14 -14
  64. package/src/compat/actions/{action-client.ts → client.ts} +194 -193
  65. package/src/compat/actions/config.ts +63 -64
  66. package/src/compat/actions/detect.ts +68 -68
  67. package/src/compat/actions/discovery.ts +105 -105
  68. package/src/compat/actions/{action-dispatch.ts → dispatch.ts} +277 -274
  69. package/src/compat/actions/early-submit.ts +1 -1
  70. package/src/compat/actions/endpoint.ts +198 -198
  71. package/src/compat/actions/extensions.ts +811 -0
  72. package/src/compat/actions/flight.ts +23 -23
  73. package/src/compat/actions/form-state.ts +34 -34
  74. package/src/compat/actions/hoist.ts +382 -236
  75. package/src/compat/actions/ids.ts +7 -12
  76. package/src/compat/actions/index.ts +8 -8
  77. package/src/compat/actions/instances.ts +46 -46
  78. package/src/compat/actions/origin.ts +47 -48
  79. package/src/compat/actions/protocol.ts +22 -22
  80. package/src/compat/actions/registry.ts +19 -19
  81. package/src/compat/{misc/action-return.ts → actions/return.ts} +60 -57
  82. package/src/compat/actions/rewrite.ts +125 -125
  83. package/src/compat/actions/{action-router.ts → router.ts} +10 -10
  84. package/src/compat/actions/serve.ts +132 -136
  85. package/src/compat/actions/server-tag.ts +4 -4
  86. package/src/compat/actions/{action-shared.ts → shared.ts} +34 -34
  87. package/src/compat/actions/unrecognized-error.ts +4 -4
  88. package/src/compat/{index.ts → aliases.ts} +112 -109
  89. package/src/compat/bundler/bun-externals.ts +18 -18
  90. package/src/compat/bundler/cjs-exports.ts +271 -223
  91. package/src/compat/bundler/config.ts +116 -111
  92. package/src/compat/bundler/externals.ts +12 -12
  93. package/src/compat/bundler/import-meta-url.ts +19 -19
  94. package/src/compat/bundler/modularize-imports.ts +36 -33
  95. package/src/compat/bundler/new-url-asset.ts +22 -24
  96. package/src/compat/bundler/optimize-package-imports.ts +111 -107
  97. package/src/compat/bundler/polyfill.ts +28 -28
  98. package/src/compat/bundler/react-compiler.ts +35 -29
  99. package/src/compat/bundler/react-profiler.tsx +11 -11
  100. package/src/compat/bundler/relay-transform.ts +48 -47
  101. package/src/compat/bundler/require-context.ts +119 -113
  102. package/src/compat/bundler/resolve-extensions.ts +19 -19
  103. package/src/compat/bundler/source-cache.ts +25 -25
  104. package/src/compat/bundler/static-imports.ts +7 -7
  105. package/src/compat/bundler/symlink-imports.ts +46 -46
  106. package/src/compat/bundler/tsconfig-paths.ts +13 -15
  107. package/src/compat/bundler/wasm.ts +58 -60
  108. package/src/compat/bundler/webpack-loaders.ts +254 -241
  109. package/src/compat/bundler/worker.ts +101 -104
  110. package/src/compat/cache/build-flags.ts +29 -29
  111. package/src/compat/cache/build-prerender-errors.ts +40 -44
  112. package/src/compat/cache/custom-handler.ts +60 -53
  113. package/src/compat/cache/fetch-patch.ts +240 -240
  114. package/src/compat/cache/handler.ts +27 -31
  115. package/src/compat/cache/modern-handler.ts +148 -126
  116. package/src/compat/cache/resume-data-cache.ts +47 -45
  117. package/src/compat/cache/revalidate.ts +233 -232
  118. package/src/compat/cache/runtime-error.ts +35 -35
  119. package/src/compat/cache/use-cache-transform.ts +442 -417
  120. package/src/compat/cache/use-cache.ts +650 -614
  121. package/src/compat/cache-control.ts +140 -142
  122. package/src/compat/client/base-path.ts +21 -20
  123. package/src/compat/client/css-order.ts +18 -18
  124. package/src/compat/client/errors/bare-boundary.ts +11 -11
  125. package/src/compat/client/errors/control-flow.ts +23 -23
  126. package/src/compat/client/errors/error-boundary.ts +61 -61
  127. package/src/compat/client/errors/global-error.ts +101 -91
  128. package/src/compat/client/errors/install.ts +71 -72
  129. package/src/compat/client/errors/lazy.ts +23 -23
  130. package/src/compat/client/errors/primitive-throw.ts +47 -45
  131. package/src/compat/client/errors/soft-refresh.ts +4 -4
  132. package/src/compat/client/link-status.ts +33 -33
  133. package/src/compat/client/{nav-compat-runtime.ts → nav-runtime.ts} +20 -20
  134. package/src/compat/client/{nav-compat.ts → nav.ts} +12 -13
  135. package/src/compat/client/navigation-scroll.ts +63 -63
  136. package/src/compat/client/optimistic-routing.ts +93 -88
  137. package/src/compat/client/prefetch-cache.ts +23 -24
  138. package/src/compat/client/route-announcer.ts +35 -35
  139. package/src/compat/client/segment-cache-policy.ts +20 -20
  140. package/src/compat/client/segment-cache.ts +309 -315
  141. package/src/compat/client/segment-prefetch.ts +127 -132
  142. package/src/compat/client/trailing-slash.ts +5 -4
  143. package/src/compat/css/chunking.ts +113 -116
  144. package/src/compat/css/inline-css.ts +21 -21
  145. package/src/compat/css/lightningcss.ts +37 -38
  146. package/src/compat/css/modules.ts +161 -175
  147. package/src/compat/css/nonce.ts +7 -7
  148. package/src/compat/css/sass-plugin.ts +18 -21
  149. package/src/compat/css/sass.ts +150 -152
  150. package/src/compat/css/styled-jsx-runtime.ts +27 -27
  151. package/src/compat/css/styled-jsx.ts +21 -21
  152. package/src/compat/edge-runtime.ts +27 -27
  153. package/src/compat/{adapter → export}/build-complete.ts +72 -75
  154. package/src/compat/export/client.ts +29 -31
  155. package/src/compat/export/{index.ts → emit.ts} +111 -110
  156. package/src/compat/export/standalone.ts +62 -54
  157. package/src/compat/image-optimizer/cache.ts +51 -49
  158. package/src/compat/image-optimizer/detect.ts +52 -52
  159. package/src/compat/image-optimizer/{index.ts → optimize.ts} +189 -194
  160. package/src/compat/image-optimizer/source.ts +77 -80
  161. package/src/compat/lifecycle/after-scope.ts +26 -26
  162. package/src/compat/lifecycle/after.ts +48 -45
  163. package/src/compat/lifecycle/error-funnel.ts +98 -102
  164. package/src/compat/lifecycle/error-serialize.ts +30 -26
  165. package/src/compat/lifecycle/error-ui.ts +67 -32
  166. package/src/compat/lifecycle/instrumentation-client.ts +36 -38
  167. package/src/compat/lifecycle/instrumentation.ts +85 -85
  168. package/src/compat/lifecycle/node-console.ts +11 -11
  169. package/src/compat/lifecycle/testmode.ts +160 -132
  170. package/src/compat/mdx/compile.ts +60 -57
  171. package/src/compat/mdx/plugin.ts +11 -11
  172. package/src/compat/mdx/{next-mdx-stub.ts → stub.ts} +5 -5
  173. package/src/compat/{metadata-route-artifacts.ts → metadata-artifacts.ts} +195 -191
  174. package/src/compat/metadata.ts +75 -81
  175. package/src/compat/next/cache.ts +68 -69
  176. package/src/compat/next/canonical-url.ts +9 -9
  177. package/src/compat/next/client-cache.ts +19 -19
  178. package/src/compat/next/client-navigation.ts +112 -116
  179. package/src/compat/next/client-only.ts +1 -1
  180. package/src/compat/next/client-script.tsx +99 -93
  181. package/src/compat/next/client-server.ts +10 -10
  182. package/src/compat/next/config-loader.ts +176 -173
  183. package/src/compat/next/config.ts +7 -7
  184. package/src/compat/next/constants.cjs +6 -6
  185. package/src/compat/next/constants.ts +6 -6
  186. package/src/compat/next/custom-server.ts +26 -24
  187. package/src/compat/next/dist/client/components/app-router-headers.ts +21 -21
  188. package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +3 -4
  189. package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -1
  190. package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -1
  191. package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -1
  192. package/src/compat/next/dynamic.tsx +21 -18
  193. package/src/compat/next/error.tsx +52 -52
  194. package/src/compat/next/font/cache.ts +74 -74
  195. package/src/compat/next/font/google.ts +2 -2
  196. package/src/compat/next/font/index.ts +1 -1
  197. package/src/compat/next/font/local.ts +3 -3
  198. package/src/compat/next/font/runtime-client.ts +17 -15
  199. package/src/compat/next/font/runtime.ts +443 -408
  200. package/src/compat/next/font/shared.ts +120 -108
  201. package/src/compat/next/form.tsx +63 -63
  202. package/src/compat/next/head.tsx +2 -2
  203. package/src/compat/next/headers.ts +103 -95
  204. package/src/compat/next/image/client.tsx +220 -0
  205. package/src/compat/next/image/config.ts +56 -58
  206. package/src/compat/next/image/optimizer.ts +40 -36
  207. package/src/compat/next/image/patterns.ts +37 -40
  208. package/src/compat/next/{image-props.ts → image/props.ts} +208 -202
  209. package/src/compat/next/image/shared.ts +65 -57
  210. package/src/compat/next/image/static-metadata.ts +98 -107
  211. package/src/compat/next/image/validate.ts +79 -88
  212. package/src/compat/next/image.tsx +19 -23
  213. package/src/compat/next/index.ts +1 -1
  214. package/src/compat/next/legacy-image.tsx +59 -60
  215. package/src/compat/next/{link-validation-transform.ts → link-transform.ts} +95 -96
  216. package/src/compat/next/link.tsx +158 -158
  217. package/src/compat/next/navigation.cjs +12 -3
  218. package/src/compat/next/navigation.ts +48 -50
  219. package/src/compat/next/offline.ts +27 -27
  220. package/src/compat/next/og.ts +121 -124
  221. package/src/compat/next/preferred-region.ts +13 -14
  222. package/src/compat/next/redirects.ts +58 -56
  223. package/src/compat/next/resource-hints.ts +73 -76
  224. package/src/compat/next/rewrites.ts +130 -133
  225. package/src/compat/next/root-params.ts +45 -45
  226. package/src/compat/next/{optimistic-route-state.ts → route-state.ts} +52 -52
  227. package/src/compat/next/router.cjs +4 -2
  228. package/src/compat/next/router.ts +58 -61
  229. package/src/compat/next/script.tsx +108 -108
  230. package/src/compat/next/server-only.ts +1 -1
  231. package/src/compat/next/server.ts +19 -19
  232. package/src/compat/next/svgr.ts +18 -17
  233. package/src/compat/next/telemetry.ts +24 -24
  234. package/src/compat/next/{image-usage.ts → usage.ts} +70 -39
  235. package/src/compat/next/user-agent.ts +53 -49
  236. package/src/compat/next/web-vitals.ts +22 -24
  237. package/src/compat/otel/api.ts +41 -41
  238. package/src/compat/otel/client-trace-metadata.ts +25 -27
  239. package/src/compat/otel/fetch-span.ts +29 -29
  240. package/src/compat/otel/tracer.ts +331 -331
  241. package/src/compat/pages/api.ts +456 -0
  242. package/src/compat/pages/client-plugin.ts +36 -36
  243. package/src/compat/pages/router-state.ts +34 -34
  244. package/src/compat/pages/{index.ts → router.ts} +130 -135
  245. package/src/compat/ppr/io.ts +12 -12
  246. package/src/compat/ppr/missing-root-params.ts +34 -38
  247. package/src/compat/ppr/root-params-scan.ts +66 -66
  248. package/src/compat/ppr/root-params-transform.ts +24 -26
  249. package/src/compat/ppr/root-params.ts +30 -30
  250. package/src/compat/ppr/segment-config-incompat.ts +6 -7
  251. package/src/compat/protocol.ts +71 -70
  252. package/src/compat/react/action-state.ts +47 -48
  253. package/src/compat/react/client-lite.ts +15 -15
  254. package/src/compat/react/client.ts +4 -4
  255. package/src/compat/react/compiler-runtime.ts +11 -11
  256. package/src/compat/react/dom-client.ts +44 -44
  257. package/src/compat/react/dom-react-server.ts +10 -16
  258. package/src/compat/react/dom-server.ts +10 -10
  259. package/src/compat/react/dom.ts +52 -52
  260. package/src/compat/react/hooks-extra.ts +34 -35
  261. package/src/compat/react/parity.ts +64 -61
  262. package/src/compat/react/preact.ts +81 -82
  263. package/src/compat/react/react-server.ts +28 -28
  264. package/src/compat/react/router-shim.ts +1 -1
  265. package/src/compat/react/server-component-use.ts +8 -8
  266. package/src/compat/react/server-inserted-html.ts +30 -31
  267. package/src/compat/react/server.ts +53 -55
  268. package/src/compat/react/use.ts +32 -32
  269. package/src/compat/react/view-transition.ts +20 -20
  270. package/src/compat/register/actions.ts +35 -824
  271. package/src/compat/register/boot.ts +41 -41
  272. package/src/compat/register/build-tier.ts +5 -5
  273. package/src/compat/register/build.ts +74 -70
  274. package/src/compat/register/bundler.ts +161 -156
  275. package/src/compat/register/cache.ts +20 -20
  276. package/src/compat/register/client-errors.ts +3 -3
  277. package/src/compat/register/config.ts +6 -6
  278. package/src/compat/register/css-extras.ts +30 -34
  279. package/src/compat/register/edge-runtime.ts +3 -3
  280. package/src/compat/register/errors.ts +10 -12
  281. package/src/compat/register/export.ts +16 -16
  282. package/src/compat/register/font.ts +11 -11
  283. package/src/compat/register/hooks.ts +2 -2
  284. package/src/compat/register/image.ts +40 -40
  285. package/src/compat/register/index.ts +59 -62
  286. package/src/compat/register/instrumentation-client.ts +10 -10
  287. package/src/compat/register/lifecycle.ts +25 -28
  288. package/src/compat/register/mdx.ts +10 -10
  289. package/src/compat/register/middleware.ts +226 -16
  290. package/src/compat/register/otel.ts +80 -88
  291. package/src/compat/register/pages-api.ts +10 -463
  292. package/src/compat/register/ppr.ts +16 -16
  293. package/src/compat/register/protocol.ts +17 -18
  294. package/src/compat/register/proxy.ts +49 -51
  295. package/src/compat/register/render.ts +79 -76
  296. package/src/compat/register/routing.ts +162 -159
  297. package/src/compat/register/segment.ts +24 -1887
  298. package/src/compat/register/static-image.ts +3 -3
  299. package/src/compat/register/{misc.ts → taint.ts} +9 -9
  300. package/src/compat/register/typed-routes.ts +14 -14
  301. package/src/compat/register/{usecache.ts → use-cache.ts} +39 -39
  302. package/src/compat/register/validation.ts +18 -18
  303. package/src/compat/segment/loading-boundary.ts +43 -45
  304. package/src/compat/segment/page-slot.ts +69 -69
  305. package/src/compat/segment/serve.ts +1884 -0
  306. package/src/compat/segment/tree.ts +113 -112
  307. package/src/compat/segment/vary-key.ts +38 -38
  308. package/src/compat/segment/vary-params.ts +138 -142
  309. package/src/compat/static-params.ts +14 -12
  310. package/src/compat/tsconfig-defaults.ts +87 -91
  311. package/src/compat/typecheck/{index.ts → check.ts} +234 -212
  312. package/src/compat/typecheck/worker.ts +15 -12
  313. package/src/compat/typed-routes/{index.ts → generate.ts} +36 -36
  314. package/src/compat/typed-routes/manifest.ts +174 -170
  315. package/src/compat/typed-routes/typegen.ts +127 -110
  316. package/src/compat/validation/errors.ts +16 -19
  317. package/src/compat/validation/prerender-diagnostics.ts +521 -504
  318. package/src/compat/validation/{index.ts → validate.ts} +647 -648
  319. package/src/compat-bootstrap.ts +16 -16
  320. package/src/config.ts +69 -69
  321. package/src/css/build.ts +230 -227
  322. package/src/css/postcss.ts +79 -80
  323. package/src/css/worker.ts +14 -15
  324. package/src/dev/client-actions.ts +10 -10
  325. package/src/dev/client-chunk-store.ts +27 -27
  326. package/src/dev/{client-key-cache.ts → restart/client-key.ts} +76 -76
  327. package/src/dev/{restart-cache.ts → restart/enabled.ts} +1 -1
  328. package/src/dev/{global-css-cache.ts → restart/global-css.ts} +91 -83
  329. package/src/dev/{node-module-bundle-cache.ts → restart/node-modules.ts} +24 -24
  330. package/src/dev/{route-bundle-key-cache.ts → restart/route-bundle-key.ts} +53 -53
  331. package/src/dev/{route-facts-cache.ts → restart/route-facts.ts} +82 -82
  332. package/src/dev/server.ts +804 -820
  333. package/src/env.ts +46 -43
  334. package/src/extensions.ts +491 -478
  335. package/src/index.ts +8 -8
  336. package/src/internal.ts +20 -23
  337. package/src/{islands → render}/boundary-error.ts +3 -3
  338. package/src/render/hooks.ts +71 -71
  339. package/src/render/island-context.ts +14 -14
  340. package/src/render/metadata.ts +310 -310
  341. package/src/{ppr-postpone.ts → render/postpone.ts} +5 -5
  342. package/src/{ppr.ts → render/ppr.ts} +244 -245
  343. package/src/render/renderer.ts +2076 -2082
  344. package/src/render/resource-hints.ts +16 -17
  345. package/src/render/slots.tsx +224 -235
  346. package/src/{islands → render}/static-children.ts +9 -12
  347. package/src/{islands → render}/static-slots.ts +37 -37
  348. package/src/{cache/context.ts → request/cache.ts} +20 -20
  349. package/src/request/context.ts +107 -107
  350. package/src/{dynamic/source.ts → resolve/dynamic.ts} +152 -143
  351. package/src/resolve/engine.ts +90 -77
  352. package/src/resolve/imports.ts +475 -463
  353. package/src/resolve/scan-facts.ts +442 -186
  354. package/src/resolve/source-text.ts +37 -37
  355. package/src/{dynamic → resolve}/tree-shake.ts +132 -128
  356. package/src/routing/forwarded.ts +19 -19
  357. package/src/routing/handler.ts +84 -91
  358. package/src/routing/href.ts +69 -70
  359. package/src/routing/{metadata.ts → metadata-files.ts} +403 -401
  360. package/src/{proxy.ts → routing/proxy.ts} +306 -312
  361. package/src/routing/{request-runtime.ts → request-environment.ts} +10 -10
  362. package/src/routing/routes.ts +833 -848
  363. package/src/routing/slots.ts +164 -160
  364. package/src/runtime/loader.ts +952 -0
  365. package/src/{dev → runtime}/module-cache.ts +309 -287
  366. package/src/{dev → runtime}/module-generations.ts +9 -9
  367. package/src/{dev → runtime}/module-transform.ts +81 -72
  368. package/src/{dev/imports.ts → runtime/modules.ts} +934 -847
  369. package/src/runtime/{server.ts → vendor-build.ts} +848 -1696
  370. package/src/runtime/vendor.ts +425 -404
  371. package/src/styles.d.ts +9 -0
  372. package/src/types.ts +320 -335
  373. package/src/utils/ansi.ts +5 -5
  374. package/src/utils/{source.ts → code.ts} +15 -12
  375. package/src/utils/content-type.ts +3 -3
  376. package/src/utils/decode.ts +2 -2
  377. package/src/utils/dev-profile.ts +13 -13
  378. package/src/utils/error-log.ts +6 -6
  379. package/src/utils/esbuild.ts +18 -18
  380. package/src/utils/fs-cache.ts +13 -13
  381. package/src/utils/fs.ts +57 -49
  382. package/src/utils/html.ts +20 -24
  383. package/src/utils/native-require.ts +8 -8
  384. package/src/utils/serialize.ts +139 -146
  385. package/src/utils/verbose.ts +18 -18
  386. package/src/cli/analyze-print.ts +0 -181
  387. package/src/compat/middleware/manifest.ts +0 -210
  388. package/src/compat/next/image-client.tsx +0 -215
  389. package/src/compat/next/link-usage.ts +0 -29
  390. package/src/css/index.ts +0 -2
  391. package/src/render/index.ts +0 -1
  392. package/src/style-modules.d.ts +0 -9
@@ -8,16 +8,13 @@
8
8
  // - bundlerExtensions.clientEsbuildPlugins <- action client-stub plugin
9
9
  //
10
10
  // The request interceptors read the live per-server routing state core publishes via
11
- // routing/request-runtime.ts and call core render/import helpers directly (compat to core is allowed),
11
+ // routing/request-environment.ts and call core render/import helpers directly (compat to core is allowed),
12
12
  // so the thin `{ config }` interceptor context stays unchanged.
13
+ //
14
+ // The implementation (build step, interceptors, render extension, dev/prod registry arming) lives in
15
+ // ../actions/extensions; this module just wires it into the extension registries.
13
16
 
14
- import path from 'node:path';
15
- import { existsSync } from 'node:fs';
16
- import { mkdir, readFile, writeFile } from 'node:fs/promises';
17
- import { fileURLToPath } from 'node:url';
18
- import type { ResolvedConfig } from '../../config';
19
- import { pathToFileHref } from '../../config';
20
- import { nextCompatEnabled } from '../../compat';
17
+ import type { ResolvedConfig } from '../../config'
21
18
  import {
22
19
  registerBuildSteps,
23
20
  registerInitHooks,
@@ -27,97 +24,48 @@ import {
27
24
  setBundlerExtensions,
28
25
  setRenderExtensions,
29
26
  withSniff,
30
- type ActionRef,
31
- type BuildStep,
32
- type BuildStepContext,
33
- type RequestInterceptor,
34
- type ServerActionPropContext,
35
- } from '../../extensions';
36
- import { setActionModuleExtensions } from '../../render/hooks';
37
- import { getRequestRuntime, type RequestRuntime } from '../../routing/request-runtime';
38
- import { setClientActionBundler } from '../../dev/client-actions';
39
- import { toPosixPath } from '../../utils/fs';
40
- import type { ActionManifestEntry, BuildManifest, RouteManifestEntry } from '../../types';
41
- import { devServerModuleHref } from '../../dev/imports';
42
- import { serverBundleTargetForRuntime } from '../../runtime/server';
43
- import {
44
- renderActionReturnElement,
45
- renderGlobalNotFoundResponse,
46
- renderNotFoundForRoute,
47
- renderPage,
48
- renderPageResponse,
49
- } from '../../render';
50
- import { routeParamsFromPath } from '../../routing/handler';
51
- import { selectRouteForRequest } from '../../routing/routes';
52
- import { discoverActions, registerDiscoveredActions } from '../actions/discovery';
53
- import { clearActions, lookupActionSource, registerActionModule } from '../actions/registry';
27
+ } from '../../extensions'
28
+ import { setActionModuleExtensions } from '../../render/hooks'
29
+ import { getRequestRuntime } from '../../routing/request-environment'
30
+ import { setClientActionBundler } from '../../dev/client-actions'
54
31
  import {
55
32
  actionClientStubPlugin,
56
33
  actionStubSourceFor,
57
- canonical,
58
- canonicalActionId,
59
- clearClientActionModules,
60
- clientActionModuleFiles,
61
34
  clientActionModulesRoot,
62
35
  isClientActionModule,
63
- setClientActionModules,
64
- } from '../actions/client-plugin';
65
- import {
66
- isActionRequest,
67
- isProgressiveActionRequest,
68
- serveAction,
69
- type ServeActionOptions,
70
- } from '../actions/serve';
36
+ } from '../actions/client-plugin'
37
+ import { validateServerActionsConfigSync } from '../actions/config'
38
+ import { hoistNonCapturingInlineActions } from '../actions/hoist'
39
+ import { rewriteAnonymousInlineActionMarks, rewriteInlineActionTags } from '../actions/rewrite'
71
40
  import {
72
- NEXT_ACTION_NOT_FOUND_HEADER,
73
- PROP_ACTION_MARKER,
74
- } from '../actions/protocol';
75
- import { loadServerActionsConfig, validateServerActionsConfigSync } from '../actions/config';
76
- import { renderWithFormStateOverride } from '../actions/form-state';
77
- import { loadCompatRewrites, type CompatRewrite, resolveCompatRewrite } from '../next/rewrites';
78
- import { loadCompatRedirects, type CompatRedirect, resolveCompatRedirect } from '../next/redirects';
79
- import { recordRewrite } from '../protocol';
80
- import { activateTestProxy } from '../lifecycle/testmode';
81
- import { canonicalUrlHref, recordCanonicalUrl } from '../next/canonical-url';
82
- import {
83
- assignedInlineActionId,
84
- createInstanceScope,
85
- instanceActionRouteId,
86
- isInstanceActionId,
87
- isMarkedInlineAction,
88
- overriddenActionId,
89
- registerActionInstance,
90
- type InstanceScope,
91
- } from '../actions/instances';
92
- import { serverActionId, tagServerAction } from '../actions/server-tag';
93
- import { opensWithUseServerDirective } from '../actions/detect';
94
- import { hoistNonCapturingInlineActions } from '../actions/hoist';
95
- import {
96
- inlineActionModuleKey,
97
- isInlineActionId,
98
- rewriteAnonymousInlineActionMarks,
99
- rewriteInlineActionTags,
100
- } from '../actions/rewrite';
101
- import { reportActionErrorToUser } from '../lifecycle/error-funnel';
41
+ actionDispatchInterceptor,
42
+ ensureActionsRegistered,
43
+ redirectInterceptor,
44
+ rewriteInterceptor,
45
+ runActionBuildStep,
46
+ serializeServerActionProp,
47
+ tagActionModuleExports,
48
+ tagActionModulesForRender,
49
+ } from '../actions/extensions'
102
50
 
103
51
  export function registerActionExtensions(_config: ResolvedConfig): void {
104
52
  // Discovery reads config + source text only, so it runs under the route-facts
105
53
  // scan; the client bundle is the one thing that waits on its stub set.
106
- runActionBuildStep.early = true;
107
- registerBuildSteps(runActionBuildStep);
54
+ runActionBuildStep.early = true
55
+ registerBuildSteps(runActionBuildStep)
108
56
  // Validate serverActions.bodySizeLimit at build + start startup (Next fails
109
57
  // the CLI with the exact size-limit error before serving), rather than only
110
58
  // lazily on the first action request.
111
- registerInitHooks(config => validateServerActionsConfigSync(config.root));
59
+ registerInitHooks(config => validateServerActionsConfigSync(config.root))
112
60
  // Ordering matches the old servers: action dispatch first (on the requested
113
61
  // pathname), then next.config redirects (short-circuit before routing), then
114
62
  // rewrites swap the request for downstream matching / static-lookup /
115
63
  // staleness.
116
- registerRequestInterceptors(actionDispatchInterceptor, redirectInterceptor, rewriteInterceptor);
64
+ registerRequestInterceptors(actionDispatchInterceptor, redirectInterceptor, rewriteInterceptor)
117
65
  // The same two rules also serve requests from outside the basePath, where only
118
66
  // their `basePath: false` entries apply (both interceptors partition their
119
67
  // rules on ctx.outsideBasePath).
120
- registerOutsideBasePathInterceptors(redirectInterceptor, rewriteInterceptor);
68
+ registerOutsideBasePathInterceptors(redirectInterceptor, rewriteInterceptor)
121
69
  // Tag MODULE-SCOPE inline 'use server' functions with React-compatible `$$id` metadata at module-eval
122
70
  // time. Without this, top-level inline actions that module-scope code introspects - Next's version-skew
123
71
  // fixtures read and override `fn.$$id` - throw at import. Runs on the server graph only; module-level
@@ -126,770 +74,33 @@ export function registerActionExtensions(_config: ResolvedConfig): void {
126
74
  // client handler. Hoisting runs FIRST: it lifts capture-free anonymous inline actions to module-scope
127
75
  // declarations, which the tag pass then gives stable `if:` ids, taking the whole-route recovery render
128
76
  // off the action's critical path for pages served from prebuilt HTML.
129
- const inlineActionSniff = ['use server'];
77
+ const inlineActionSniff = ['use server']
130
78
  registerServerSourceTransforms(
131
79
  withSniff(inlineActionSniff, source => hoistNonCapturingInlineActions(source)),
132
80
  withSniff(inlineActionSniff, (source, file, root) =>
133
81
  rewriteInlineActionTags(source, file, root),
134
82
  ),
135
83
  withSniff(inlineActionSniff, source => rewriteAnonymousInlineActionMarks(source)),
136
- );
137
- setRenderExtensions({ serializeServerActionProp });
84
+ )
85
+ setRenderExtensions({ serializeServerActionProp })
138
86
  // Tag imported action-module function exports with their stable wire id so a
139
87
  // <form action={serverAction}> imported straight into a page (same module
140
88
  // singleton) serializes to that module id instead of a per-render inline id.
141
- setActionModuleExtensions({ tagActionModuleExports, tagActionModulesForRender });
89
+ setActionModuleExtensions({ tagActionModuleExports, tagActionModulesForRender })
142
90
  // Prod/plain client builds swap 'use server' imports for the RPC stub via
143
91
  // the standalone esbuild plugin (client/build.ts appends these). No-op for
144
92
  // pure-core / non-next apps.
145
- setBundlerExtensions({ clientEsbuildPlugins: () => [actionClientStubPlugin()] });
93
+ setBundlerExtensions({ clientEsbuildPlugins: () => [actionClientStubPlugin()] })
146
94
  // The dev route-client bundle resolves modules into its own esbuild namespace
147
95
  // that a standalone plugin can't intercept, so dev/imports reads this holder
148
96
  // instead. ensureArmed runs the discovery dev startup/reload used to run
149
97
  // inline, lazily per dev import version.
150
98
  setClientActionBundler({
151
99
  ensureArmed: async () => {
152
- const runtime = getRequestRuntime();
153
- if (runtime?.dev) await ensureActionsRegistered(runtime);
100
+ const runtime = getRequestRuntime()
101
+ if (runtime?.dev) await ensureActionsRegistered(runtime)
154
102
  },
155
103
  isClientActionModule,
156
104
  stubSource: file => actionStubSourceFor(file, clientActionModulesRoot() ?? undefined),
157
- });
158
- }
159
-
160
- // ---------------------------------------------------------------------------
161
- // Build step: action discovery/bundling + server-reference manifest.
162
- // ---------------------------------------------------------------------------
163
-
164
- interface ActionStepState {
165
- actions: ActionManifestEntry[];
166
- actionSources?: string[];
167
- actionImporters?: string[];
168
- deferred?: Promise<void>;
169
- }
170
-
171
- const runActionBuildStep: BuildStep = async (ctx: BuildStepContext): Promise<void> => {
172
- const config = ctx.config;
173
- const state = ctx.manifest as ActionStepState;
174
- // Discovery is what arms the client stub set and names the action source
175
- // files, and it is the only part of this step the client stage waits on.
176
- const discovery = await (ctx.log
177
- ? ctx.log.step('action discovery', () => discoverActions(config))
178
- : discoverActions(config));
179
- state.actionSources = actionSourceKeys(config, discovery);
180
- state.actionImporters = [...discovery.actionImporters];
181
- // Compiling those modules to server bundles produces manifest entries nothing
182
- // upstream of the build manifest reads, so it is handed back to run under the
183
- // client stage instead of ahead of it.
184
- state.deferred = (async () => {
185
- const actions = await compileActionModules(config, discovery, ctx.log);
186
- // The step's `manifest` accumulator carries the actions array the core build
187
- // folds into the build manifest (manifest.actions) verbatim.
188
- state.actions = actions;
189
- if (actions.length > 0) await writeServerReferenceManifest(config, actions);
190
- })();
191
- };
192
-
193
- /**
194
- * Discover 'use server' modules, compile each to a server bundle (compat
195
- * aliases + directive handling applied, like the proxy module), and produce the
196
- * action manifest start serves from. Also arms the client-bundle stub set so
197
- * client imports of these modules become the RPC stub during buildClientEntries.
198
- *
199
- * The wire id is computed from the SOURCE file path (config.root-relative), so
200
- * the client stub and the server registry agree; modulePath is the COMPILED
201
- * output the endpoint imports at request time.
202
- */
203
- function actionSourceKeys(
204
- config: ResolvedConfig,
205
- discovery: Awaited<ReturnType<typeof discoverActions>>,
206
- ): string[] {
207
- clearActions();
208
- if (discovery.modules.length === 0) {
209
- setClientActionModules(config.root, []);
210
- return [];
211
- }
212
- // Client bundling replaces these source imports with the stub.
213
- setClientActionModules(config.root, discovery.actionFiles);
214
- // Registered from the discovery facts alone (ids come from source paths), so
215
- // build-time lookupAction parity holds before the compile below finishes.
216
- registerDiscoveredActions(config, discovery);
217
- return discovery.modules.map(module => actionSourceKey(config, module.file));
218
- }
219
-
220
- /** The registry id's source key: canonical, root-relative, POSIX. */
221
- function actionSourceKey(config: ResolvedConfig, file: string) {
222
- // Must reproduce the registry id at startup: registerManifestActions calls
223
- // actionId(sourceKey) with no root, so this has to equal
224
- // workspaceRelative(canonical(file), canonical(root)).
225
- return toPosixPath(path.relative(canonical(config.root), canonical(file)));
226
- }
227
-
228
- async function compileActionModules(
229
- config: ResolvedConfig,
230
- discovery: Awaited<ReturnType<typeof discoverActions>>,
231
- log?: BuildStepContext['log'],
232
- ): Promise<ActionManifestEntry[]> {
233
- if (discovery.modules.length === 0) return [];
234
-
235
- const entries: ActionManifestEntry[] = [];
236
- const compile = async () => {
237
- for (const module of discovery.modules) {
238
- // 'build', matching render/index.tsx's prod moduleHref: the endpoint and the
239
- // page render must import the SAME compiled file so a module imported in
240
- // both the render and action layers is one instance (shared module state).
241
- const href = await devServerModuleHref(config, module.file, 'build');
242
- const compiled = fileURLToPath(href);
243
- const modulePath = toPosixPath(path.relative(config.outPath, compiled));
244
- const sourceKey = actionSourceKey(config, module.file);
245
- for (const exportName of module.exports) {
246
- entries.push({ id: module.ids[exportName]!, sourceKey, modulePath, exportName });
247
- }
248
- }
249
- };
250
- if (log) await log.step(`action modules (${discovery.modules.length})`, compile);
251
- else await compile();
252
- return entries;
253
- }
254
-
255
- // Next-compatible server-reference manifest (.next/server/): tools and Next's
256
- // own e2e suite introspect it for the registered action ids and their source
257
- // modules. pnext's real registry lives in .pnext/manifest.json; this mirror
258
- // exists purely for ecosystem compatibility.
259
- async function writeServerReferenceManifest(
260
- config: ResolvedConfig,
261
- actions: ActionManifestEntry[],
262
- ) {
263
- const node: Record<
264
- string,
265
- {
266
- workers: Record<string, unknown>;
267
- layer: Record<string, unknown>;
268
- filename: string;
269
- exportedName: string;
270
- }
271
- > = {};
272
- for (const action of actions) {
273
- node[action.id] = {
274
- workers: {},
275
- layer: {},
276
- filename: action.sourceKey,
277
- exportedName: action.exportName,
278
- };
279
- }
280
- const manifest = { node, edge: {}, encryptionKey: 'pnext' };
281
- const outDir = path.join(config.root, '.next', 'server');
282
- await mkdir(outDir, { recursive: true });
283
- await writeFile(
284
- path.join(outDir, 'server-reference-manifest.json'),
285
- JSON.stringify(manifest, null, 2),
286
- );
287
- }
288
-
289
- // Request interceptor: server-action dispatch. Actions POST to the page's own URL (id in the
290
- // next-action header for JS clients, or a hidden form field for no-JS progressive submissions).
291
- // Route-handler POSTs pass through even with an id header - actions only dispatch on page URLs, or on
292
- // unmatched paths, which still get the action error semantics.
293
-
294
- const actionDispatchInterceptor: RequestInterceptor = async (request, ctx) => {
295
- const config = ctx.config;
296
- if (!nextCompatEnabled(config)) return undefined;
297
- const runtime = getRequestRuntime();
298
- if (!runtime) return undefined;
299
- const url = new URL(request.url);
300
- const actionTarget = selectRouteForRequest(runtime.routes, url.pathname, undefined);
301
- // Route-handler POSTs pass through even with an id header — actions only
302
- // dispatch on page URLs (or unmatched paths, which still get the action
303
- // error semantics the unrecognized-action suite asserts).
304
- if (actionTarget && actionTarget.route.kind !== 'page') return undefined;
305
-
306
- // A form-encoded POST to an existing page with no recognizable action id is still an MPA action attempt
307
- // in Next, which routes every page form POST through the action handler: with no action to run it
308
- // answers 404 action-not-found rather than rendering the page. Only applies to real page targets - a
309
- // POST to a nonexistent route stays a clean 404.
310
- const isFormPost = actionTarget?.route.kind === 'page' && isFormPostRequest(request);
311
- if (
312
- !(isActionRequest(request) || (await isProgressiveActionRequest(request)) || isFormPost)
313
- ) {
314
- return undefined;
315
- }
316
-
317
- await ensureActionsRegistered(runtime);
318
-
319
- const actionId = request.headers.get('next-action');
320
- if (
321
- actionId &&
322
- canonicalUrlHref() !== undefined &&
323
- isForwardedActionForRoute(config, actionTarget, actionId)
324
- ) {
325
- return new Response(`Server Action "${actionId}" was not found on the server.`, {
326
- status: 404,
327
- headers: {
328
- 'content-type': 'text/plain; charset=utf-8',
329
- [NEXT_ACTION_NOT_FOUND_HEADER]: '1',
330
- },
331
- });
332
- }
333
-
334
- const maxBodyBytes = runtime.maxBodyBytes ?? (await resolveMaxBodyBytes(config));
335
- const options = buildServeActionOptions(runtime, request, actionTarget, maxBodyBytes);
336
- return serveAction(request, options);
337
- };
338
-
339
- // Strip the configured basePath from a redirect-target URL so it lines up with
340
- // the app-relative route table. Same matching rule as start.ts stripBasePath;
341
- // a target outside the basePath (a `basePath:false` rewrite destination) is
342
- // returned unchanged so it simply fails to match a page (external redirect).
343
- function stripActionBasePath(target: URL, basePath: string): URL {
344
- if (!basePath) return target;
345
- const pathname = target.pathname;
346
- if (pathname !== basePath && !pathname.startsWith(`${basePath}/`)) return target;
347
- const stripped = new URL(target);
348
- stripped.pathname = pathname.slice(basePath.length) || '/';
349
- return stripped;
350
- }
351
-
352
- // Rebuild the render request against the basePath-stripped URL, preserving the
353
- // action's applied cookie/header state. Returns the original request untouched
354
- // when no stripping happened.
355
- function strippedRequest(request: Request, target: URL, stripped: URL): Request {
356
- if (stripped.href === target.href) return request;
357
- return new Request(stripped.href, { headers: request.headers });
358
- }
359
-
360
- function isFormPostRequest(request: Request): boolean {
361
- if (request.method.toUpperCase() !== 'POST') return false;
362
- if (request.headers.has('next-action')) return false;
363
- const contentType = request.headers.get('content-type') ?? '';
364
- // Only multipart counts as an id-less MPA action attempt: React's progressive action forms always post
365
- // multipart/form-data, so a urlencoded POST without an action id is a plain form POST to the page - a
366
- // browser re-POSTing after a route handler's 307/308 redirect, say - and must render the page, not 404.
367
- // urlencoded POSTs that DO carry the hidden action id field are still dispatched.
368
- return contentType.includes('multipart/form-data');
369
- }
370
-
371
- // serverActions.bodySizeLimit from next.config, resolved once per project.
372
- // Invalid values throw (Next's message) — surfaced on the first action request
373
- // as a 500, matching the size-limit-invalid suite's expectation.
374
- const bodyLimitCache = new Map<string, Promise<number | undefined>>();
375
-
376
- function resolveMaxBodyBytes(config: ResolvedConfig): Promise<number | undefined> {
377
- const key = config.root;
378
- let cached = bodyLimitCache.get(key);
379
- if (!cached) {
380
- cached = loadServerActionsConfig(config.root).then(c => c.bodySizeLimitBytes);
381
- bodyLimitCache.set(key, cached);
382
- }
383
- return cached;
384
- }
385
-
386
- // Instance-recovery render dedup: the first dispatch against a prebuilt page
387
- // re-renders the route to re-materialize its `i:` action closures
388
- // (instances.ts); concurrent dispatches share that in-flight render instead of
389
- // each starting a full one. Keyed route+pathname (dynamic routes recover per
390
- // path), cleared on settle so evicted instances recover again.
391
- const instanceRecoveries = new Map<string, Promise<void>>();
392
-
393
- function dedupeInstanceRecovery(
394
- routeId: string,
395
- pathname: string,
396
- render: () => Promise<void>,
397
- ): Promise<void> {
398
- const key = `${routeId}${pathname}`;
399
- const inFlight = instanceRecoveries.get(key);
400
- if (inFlight) return inFlight;
401
- const started = render().finally(() => instanceRecoveries.delete(key));
402
- instanceRecoveries.set(key, started);
403
- return started;
404
- }
405
-
406
- /**
407
- * Reconstruct the ServeActionOptions the old start/dev handlers built inline,
408
- * from the published request runtime + core render/import helpers. Dev vs prod
409
- * only changes how modules resolve to hrefs.
410
- */
411
- function buildServeActionOptions(
412
- runtime: RequestRuntime,
413
- request: Request,
414
- actionTarget: ReturnType<typeof selectRouteForRequest>,
415
- maxBodyBytes: number | undefined,
416
- ): ServeActionOptions {
417
- const config = runtime.config;
418
- const dev = runtime.dev;
419
- const version = runtime.devImportVersion ?? 'build';
420
- const url = new URL(request.url);
421
- const actionConditionTarget =
422
- actionTarget?.route.kind === 'page'
423
- ? serverBundleTargetForRuntime(actionTarget.route.segmentConfig?.runtime)
424
- : 'server';
425
-
426
- const importModule = dev
427
- ? async (modulePath: string) =>
428
- (await import(
429
- await devServerModuleHref(config, modulePath, version, {
430
- conditionTarget: actionConditionTarget,
431
- })
432
- )) as Record<string, unknown>
433
- : async (modulePath: string) =>
434
- (await import(pathToFileHref(path.resolve(config.outPath, modulePath)))) as Record<
435
- string,
436
- unknown
437
- >;
438
-
439
- const options: ServeActionOptions = {
440
- ...(dev ? { dev: true } : {}),
441
- importModule,
442
- // skipNoindex: Next omits the fallback robots meta for an action-triggered
443
- // not-found (app-render's NonIndex), keeping the page's own robots value.
444
- renderNotFound: async notFoundRequest =>
445
- actionTarget?.route.kind === 'page'
446
- ? renderNotFoundForRoute(
447
- {
448
- config,
449
- route: actionTarget.route,
450
- params: actionTarget.params,
451
- url: new URL(notFoundRequest.url),
452
- // GET conversion: the page renderer refuses non-GET requests.
453
- request: new Request(notFoundRequest.url, { headers: notFoundRequest.headers }),
454
- ...(dev ? { dev: true, devImportVersion: version } : {}),
455
- },
456
- { skipNoindex: true },
457
- )
458
- : renderGlobalNotFoundResponse(
459
- {
460
- config,
461
- url: new URL(notFoundRequest.url),
462
- request: new Request(notFoundRequest.url, { headers: notFoundRequest.headers }),
463
- ...(dev ? { dev: true, devImportVersion: version } : {}),
464
- },
465
- { skipNoindex: true },
466
- ),
467
- renderPageForFormState: async (formRequest, state, actionId) => {
468
- if (actionTarget?.route.kind !== 'page') return null;
469
- const formUrl = new URL(formRequest.url);
470
- return renderWithFormStateOverride(state, actionId, () =>
471
- renderPageResponse({
472
- config,
473
- route: actionTarget.route,
474
- params: actionTarget.params,
475
- url: formUrl,
476
- request: new Request(formRequest.url, { headers: formRequest.headers }),
477
- ...(dev ? { dev: true, devImportVersion: version } : {}),
478
- }),
479
- );
480
- },
481
- renderRedirectTarget: async (redirectRequest, target) => {
482
- // The redirect target arrives in basePath space (redirect() prefixes it);
483
- // strip the prefix so it matches the app-relative route table and the
484
- // rendered page's usePathname excludes the basePath, exactly like a
485
- // freshly-routed GET (which start.ts strips before matching).
486
- const stripped = stripActionBasePath(target, config.basePath);
487
- const targetMatch = selectRouteForRequest(runtime.routes, stripped.pathname, undefined);
488
- if (targetMatch?.route.kind !== 'page') return null;
489
- return renderPageResponse({
490
- config,
491
- route: targetMatch.route,
492
- params: targetMatch.params,
493
- url: stripped,
494
- request: strippedRequest(redirectRequest, target, stripped),
495
- ...(dev ? { dev: true, devImportVersion: version } : {}),
496
- });
497
- },
498
- renderActionFlight: async (flightRequest, target) => {
499
- const stripped = stripActionBasePath(target, config.basePath);
500
- const targetMatch = selectRouteForRequest(runtime.routes, stripped.pathname, undefined);
501
- if (targetMatch?.route.kind !== 'page') return null;
502
- return renderPageResponse({
503
- config,
504
- route: targetMatch.route,
505
- params: targetMatch.params,
506
- url: stripped,
507
- request: strippedRequest(flightRequest, target, stripped),
508
- ...(dev ? { dev: true, devImportVersion: version } : {}),
509
- });
510
- },
511
- importSourceModule: async moduleKey => {
512
- const absolute = path.resolve(config.root, moduleKey);
513
- await import(
514
- await devServerModuleHref(config, absolute, version, {
515
- conditionTarget: actionConditionTarget,
516
- }),
517
- );
518
- },
519
- rerenderRoute: async routeId => {
520
- const route = runtime.routes.find(entry => entry.id === routeId && entry.kind === 'page');
521
- if (!route) throw new Error(`Unknown route for action re-render: ${routeId}`);
522
- await dedupeInstanceRecovery(routeId, url.pathname, () =>
523
- renderPage({
524
- config,
525
- route,
526
- params: routeParamsFromPath(route, url.pathname),
527
- url,
528
- request: new Request(request.url, { headers: request.headers }),
529
- ...(dev ? { dev: true, devImportVersion: version } : {}),
530
- }).then(() => undefined),
531
- );
532
- },
533
- isForwardedAction: id => isForwardedActionForRoute(config, actionTarget, id),
534
- onError: reportActionErrorToUser,
535
- ...(maxBodyBytes !== undefined ? { maxBodyBytes } : {}),
536
- // Element-returning actions server-render the returned tree through the same
537
- // machinery a page render uses (async server components, host elements),
538
- // producing the island wire HTML the action client revives. Scoped to page
539
- // targets — the only routes that own such actions.
540
- ...(actionTarget?.route.kind === 'page'
541
- ? {
542
- renderActionElement: (element: unknown) =>
543
- renderActionReturnElement(element, {
544
- config,
545
- route: actionTarget.route,
546
- params: actionTarget.params,
547
- url,
548
- request: new Request(request.url, { headers: request.headers }),
549
- ...(dev ? { dev: true, devImportVersion: version } : {}),
550
- }),
551
- }
552
- : {}),
553
- };
554
- return options;
555
- }
556
-
557
- /**
558
- * Next "forwarded action" discrimination: the POSTed route's static source closure does not contain the
559
- * action's source module, meaning the client dispatched the action after navigating to a different page.
560
- * For live-instance actions the owning route is encoded in the id; for module and inline actions the
561
- * registered source file is checked against the route's sourceFiles closure, realpath-canonicalized since
562
- * the closure may hold /private-var vs /var aliases of the same file on macOS.
563
- */
564
- function isForwardedActionForRoute(
565
- config: ResolvedConfig,
566
- actionTarget: ReturnType<typeof selectRouteForRequest>,
567
- id: string,
568
- ): boolean {
569
- const route = actionTarget?.route;
570
- if (route?.kind !== 'page') return false;
571
- if (isInstanceActionId(id)) {
572
- const owner = instanceActionRouteId(id);
573
- return Boolean(owner && owner !== route.id);
574
- }
575
- const source = isInlineActionId(id) ? inlineActionModuleKey(id) : lookupActionSource(id);
576
- if (!source) return false;
577
- const sources = route.sourceFiles;
578
- if (!sources?.length) return false;
579
- const target = canonical(path.resolve(config.root, source));
580
- return !sources.some(file => canonical(file) === target);
581
- }
582
-
583
- // ---------------------------------------------------------------------------
584
- // Action registry arming.
585
- //
586
- // prod: rebuild the registry from the built manifest once (id parity with the
587
- // client stub via the stored source key).
588
- // dev: re-run discovery whenever the dev import version changes (reload), so
589
- // the endpoint + client-stub set stay current without core importing us.
590
- // ---------------------------------------------------------------------------
591
-
592
- // Memoized on the in-flight PROMISE, not on a "started" flag: a page can fire several actions at once,
593
- // and a flag set before the await lets the second POST through against a registry that is still empty, so
594
- // it 404s as an unknown action. Every caller awaits the same registration. A failed one is dropped so the
595
- // next request retries instead of inheriting the rejection forever. `UNREGISTERED` keys the initial (and
596
- // post-failure) state so the memo itself is never optional.
597
- const UNREGISTERED = Symbol('pnext.actions.unregistered');
598
- const noRegistration = { key: UNREGISTERED, version: UNREGISTERED, done: Promise.resolve() };
599
- let manifestRegistration: { key: string | symbol; done: Promise<void> } = noRegistration;
600
- let devRegistration: { version: string | symbol | undefined; done: Promise<void> } =
601
- noRegistration;
602
-
603
- async function ensureActionsRegistered(runtime: RequestRuntime): Promise<void> {
604
- if (runtime.dev) {
605
- if (devRegistration.version !== runtime.devImportVersion) {
606
- const pending = { version: runtime.devImportVersion, done: Promise.resolve() };
607
- pending.done = refreshDevActions(runtime.config).catch((error: unknown) => {
608
- if (devRegistration === pending) devRegistration = noRegistration;
609
- throw error;
610
- });
611
- devRegistration = pending;
612
- }
613
- await devRegistration.done;
614
- return;
615
- }
616
- const key = runtime.config.outPath;
617
- if (manifestRegistration.key !== key) {
618
- const pending = { key, done: Promise.resolve() };
619
- pending.done = registerManifestActions(runtime.config).catch((error: unknown) => {
620
- if (manifestRegistration === pending) manifestRegistration = noRegistration;
621
- throw error;
622
- });
623
- manifestRegistration = pending;
624
- }
625
- await manifestRegistration.done;
626
- }
627
-
628
- // Rebuild the action registry from the built manifest so the endpoint can
629
- // resolve an incoming id to its compiled module + export. Also restore the
630
- // client-action module set (normally armed by the build): render-time form/prop
631
- // tagging reads it to give module-level actions their stable wire ids.
632
- async function registerManifestActions(config: ResolvedConfig): Promise<void> {
633
- const manifestPath = path.join(config.outPath, 'manifest.json');
634
- let manifest: BuildManifest;
635
- try {
636
- manifest = JSON.parse(await readFile(manifestPath, 'utf8')) as BuildManifest;
637
- } catch {
638
- return;
639
- }
640
- const files = new Set<string>();
641
- for (const action of manifest.actions ?? []) {
642
- registerActionModule(action.sourceKey, [action.exportName], {
643
- modulePath: action.modulePath,
644
- });
645
- files.add(path.resolve(config.root, action.sourceKey));
646
- }
647
- if (files.size > 0) setClientActionModules(config.root, files);
648
- }
649
-
650
- // Rescan the app for 'use server' modules and refresh both the endpoint
651
- // registry (id -> module) and the client-bundle stub set. In dev the action's
652
- // modulePath stays the source file: devServerModuleHref compiles it on demand
653
- // when the endpoint imports it. Gated on compat.next inside discoverActions.
654
- async function refreshDevActions(config: ResolvedConfig): Promise<void> {
655
- clearActions();
656
- clearClientActionModules();
657
- const discovery = await discoverActions(config);
658
- if (discovery.modules.length === 0) return;
659
- registerDiscoveredActions(config, discovery);
660
- setClientActionModules(config.root, discovery.actionFiles);
661
- }
662
-
663
- // ---------------------------------------------------------------------------
664
- // Request interceptor: next.config rewrites.
665
- //
666
- // Rewrites map the request pathname to its destination before static lookup and
667
- // routing, so prebuilt output + revalidatePath marks work against the
668
- // destination. The render keeps the requested URL canonical (usePathname); core
669
- // preserves the original URL for rendering (see start/dev canonicalUrl).
670
- // ---------------------------------------------------------------------------
671
-
672
- const rewriteCache = new Map<string, Promise<CompatRewrite[]>>();
673
-
674
- function loadRewritesFor(config: ResolvedConfig): Promise<CompatRewrite[]> {
675
- const key = config.root;
676
- let cached = rewriteCache.get(key);
677
- if (!cached) {
678
- cached = loadCompatRewrites(config.root);
679
- rewriteCache.set(key, cached);
680
- }
681
- return cached;
682
- }
683
-
684
- const redirectCache = new Map<string, Promise<CompatRedirect[]>>();
685
-
686
- function loadRedirectsFor(config: ResolvedConfig): Promise<CompatRedirect[]> {
687
- const key = config.root;
688
- let cached = redirectCache.get(key);
689
- if (!cached) {
690
- cached = loadCompatRedirects(config.root);
691
- redirectCache.set(key, cached);
692
- }
693
- return cached;
694
- }
695
-
696
- const redirectInterceptor: RequestInterceptor = async (request, ctx) => {
697
- const config = ctx.config;
698
- if (!nextCompatEnabled(config)) return undefined;
699
- // `basePath: false` entries match the raw path core would otherwise 404;
700
- // every other entry matches the in-app (basePath-stripped) path. The two
701
- // passes are disjoint.
702
- const redirects = (await loadRedirectsFor(config)).filter(
703
- redirect => redirect.outsideBasePath === Boolean(ctx.outsideBasePath),
704
- );
705
- if (redirects.length === 0) return undefined;
706
- const url = new URL(request.url);
707
- const result = resolveCompatRedirect(redirects, url.pathname, {
708
- host: request.headers.get('host') ?? '',
709
- headers: request.headers,
710
- cookies: parseCookieHeader(request.headers.get('cookie')),
711
- query: url.searchParams,
712
- });
713
- if (!result) return undefined;
714
- const location = new URL(result.location, url.href);
715
- // Next passes the incoming query through to the redirect destination (the
716
- // `_rsc` union query among it — rsc-query-routing relies on the followed
717
- // request keeping it). Destination-specified params win.
718
- for (const [key, value] of url.searchParams) {
719
- if (!location.searchParams.has(key)) location.searchParams.append(key, value);
720
- }
721
- return new Response(null, { status: result.status, headers: { location: location.href } });
722
- };
723
-
724
- const rewriteInterceptor: RequestInterceptor = async (request, ctx) => {
725
- const config = ctx.config;
726
- if (!nextCompatEnabled(config)) return undefined;
727
- // See redirectInterceptor: `basePath: false` rules own the outside-basePath
728
- // pass, the rest own the in-app one.
729
- const rewrites = (await loadRewritesFor(config)).filter(
730
- rewrite => rewrite.outsideBasePath === Boolean(ctx.outsideBasePath),
731
- );
732
- if (rewrites.length === 0) return undefined;
733
- const url = new URL(request.url);
734
- const context = {
735
- host: request.headers.get('host') ?? '',
736
- headers: request.headers,
737
- cookies: parseCookieHeader(request.headers.get('cookie')),
738
- query: url.searchParams,
739
- };
740
- // beforeFiles/afterFiles (and the array form) apply ahead of routing; a
741
- // `fallback` rewrite only gets a turn once nothing else can serve the path.
742
- let result = resolveCompatRewrite(
743
- rewrites.filter(rewrite => !rewrite.fallback),
744
- url.pathname,
745
- context,
746
- );
747
- if (!result && requestWouldMiss(config, url.pathname)) {
748
- result = resolveCompatRewrite(
749
- rewrites.filter(rewrite => rewrite.fallback),
750
- url.pathname,
751
- context,
752
- );
753
- }
754
- if (!result) return undefined;
755
- if (result.destination) {
756
- activateTestProxy(request.headers);
757
- // The incoming `host` (and its forwarded twins) name THIS server; carrying
758
- // them onto an outbound request makes the TLS handshake disagree with the
759
- // destination's certificate. Let fetch derive them from the destination URL.
760
- const headers = new Headers(request.headers);
761
- for (const header of ['host', 'x-forwarded-host', 'x-forwarded-port', 'x-forwarded-proto']) {
762
- headers.delete(header);
763
- }
764
- const rewrittenRequest = new Request(result.destination, {
765
- method: request.method,
766
- headers,
767
- ...(request.body ? { body: request.body, duplex: 'half' } : {}),
768
- });
769
- return fetch(rewrittenRequest);
770
- }
771
- const rewritten = new URL(url.href);
772
- rewritten.pathname = result.pathname;
773
- rewritten.search = result.search.toString();
774
- recordCanonicalUrl(url);
775
- recordRewrite(url, rewritten);
776
- return { request: new Request(rewritten, request) };
777
- };
778
-
779
- /**
780
- * Whether a request for `pathname` would 404 - the gate Next's `fallback` rewrites sit behind. A path is
781
- * servable when the route table matches it or a built/public file backs it; `/_next/*` assets are always
782
- * served by core. Divergence: dev has no built output, so only the route table is consulted.
783
- */
784
- function requestWouldMiss(config: ResolvedConfig, pathname: string): boolean {
785
- if (pathname.startsWith('/_next/')) return false;
786
- const runtime = getRequestRuntime();
787
- if (runtime && selectRouteForRequest(runtime.routes, pathname)) return false;
788
- return !servableFile(config, pathname);
789
- }
790
-
791
- function servableFile(config: ResolvedConfig, pathname: string): boolean {
792
- const publicRoot = path.join(config.outPath, 'public');
793
- const trimmed = pathname.replace(/^\/+|\/+$/g, '');
794
- const file = trimmed ? path.join(publicRoot, trimmed) : publicRoot;
795
- // Never let a traversal-shaped path claim to be servable (it is not).
796
- const relative = path.relative(publicRoot, file);
797
- if (relative.startsWith('..') || path.isAbsolute(relative)) return false;
798
- return (
799
- existsSync(path.join(file, 'index.html')) ||
800
- (trimmed !== '' && (existsSync(file) || existsSync(`${file}.html`)))
801
- );
802
- }
803
-
804
- function parseCookieHeader(header: string | null): Record<string, string> {
805
- if (!header) return {};
806
- const cookies: Record<string, string> = {};
807
- for (const part of header.split(';')) {
808
- const eq = part.indexOf('=');
809
- if (eq === -1) continue;
810
- cookies[part.slice(0, eq).trim()] = part.slice(eq + 1).trim();
811
- }
812
- return cookies;
813
- }
814
-
815
- // ---------------------------------------------------------------------------
816
- // Render extension: serialize a server-action function crossing the
817
- // server->client boundary into the { [PROP_ACTION_MARKER]: id } marker.
818
- //
819
- // Id policy (Next-compatible), highest priority first:
820
- // 1. caller-overridden $$id (version-skew simulation)
821
- // 2. compile-tagged module-scope inline action ($$pnextAssignedId, 'if:')
822
- // 3. tagged module-level 'use server' export (stable module id)
823
- // 4. inline / .bind() / HOC closures -> a live per-render instance ('i:')
824
- //
825
- // Per-render instance scopes are keyed on the opaque renderKey (core's
826
- // RenderOptions) so occurrence indices stay consistent within one render.
827
- // ---------------------------------------------------------------------------
828
-
829
- const instanceScopes = new WeakMap<object, InstanceScope>();
830
-
831
- function serializeServerActionProp(
832
- value: unknown,
833
- context: ServerActionPropContext,
834
- ): ActionRef | undefined {
835
- if (typeof value !== 'function') return undefined;
836
- if (!nextCompatEnabled(context.config)) return undefined;
837
-
838
- const overridden = overriddenActionId(value);
839
- if (overridden) return { [PROP_ACTION_MARKER]: overridden };
840
-
841
- const assigned = assignedInlineActionId(value);
842
- if (assigned) return { [PROP_ACTION_MARKER]: assigned };
843
-
844
- const moduleId = serverActionId(value);
845
- if (moduleId) return { [PROP_ACTION_MARKER]: moduleId };
846
-
847
- if (context.identifiedOnly && !isMarkedInlineAction(value) && !opensWithUseServerDirective(value)) {
848
- return undefined;
849
- }
850
-
851
- const scope = instanceScopeFor(context.renderKey);
852
- const id = registerActionInstance(scope, value);
853
- return { [PROP_ACTION_MARKER]: id };
854
- }
855
-
856
- /** Tag each function export of a discovered action module with its wire id. */
857
- function tagActionModuleExports(
858
- config: ResolvedConfig,
859
- file: string,
860
- module: Record<string, unknown>,
861
- ): void {
862
- if (!nextCompatEnabled(config) || !isClientActionModule(file)) return;
863
- const root = clientActionModulesRoot() ?? config.root;
864
- for (const [exportName, value] of Object.entries(module)) {
865
- if (typeof value === 'function') {
866
- tagServerAction(value, canonicalActionId(file, exportName, root));
867
- }
868
- }
869
- }
870
-
871
- /** Import + tag every discovered action module ahead of a page render. */
872
- async function tagActionModulesForRender(
873
- config: ResolvedConfig,
874
- importModule: (file: string) => Promise<Record<string, unknown>>,
875
- ): Promise<void> {
876
- if (!nextCompatEnabled(config)) return;
877
- for (const file of clientActionModuleFiles()) {
878
- try {
879
- tagActionModuleExports(config, file, await importModule(file));
880
- } catch {
881
- // Best-effort: an action module that fails to import here still POSTs via
882
- // the client stub when JS runs; the form just renders without the hidden id.
883
- }
884
- }
885
- }
886
-
887
- function instanceScopeFor(renderKey: object): InstanceScope {
888
- let scope = instanceScopes.get(renderKey);
889
- if (!scope) {
890
- const routeId = (renderKey as { route?: RouteManifestEntry }).route?.id ?? 'unknown';
891
- scope = createInstanceScope(routeId);
892
- instanceScopes.set(renderKey, scope);
893
- }
894
- return scope;
105
+ })
895
106
  }