@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
package/src/css/build.ts CHANGED
@@ -1,20 +1,45 @@
1
- import { existsSync, readFileSync } from 'node:fs'
1
+ import { existsSync } from 'node:fs'
2
+ import { productionRelease } from '../runtime/production'
2
3
  import { readFile, rename, writeFile } from 'node:fs/promises'
3
4
  import path from 'node:path'
4
5
  import type { Plugin } from 'esbuild'
5
6
  import { hashedAssetName } from '../utils/asset-hash'
6
7
  import { build } from '../utils/esbuild'
7
- import { cssModuleScopePath, ensureDir, readText } from '../utils/fs'
8
+ import { ensureDir, readText } from '../utils/fs'
8
9
  import { postcssConfigFile, runPostcss } from './postcss'
9
10
  import { extraPageExtensions, getCssExtensions } from '../extensions'
10
- import { nextCompatEnabled } from '../render/hooks'
11
11
  import { resolveImport } from '../resolve/imports'
12
+ import { readSourceSync } from '../resolve/source-text'
12
13
  import { createVerboseLogger } from '../utils/verbose'
13
14
  import type { ClientReference } from '../client/reference'
14
15
  import type { ResolvedConfig } from '../config'
15
16
  import type { RouteManifestEntry } from '../types'
16
17
 
17
18
  import type { CssWorkerRequest, CssWorkerResponse } from './worker'
19
+ import {
20
+ cssClassNames,
21
+ cssModuleClassName,
22
+ cssModuleMapping,
23
+ isCssModuleFile,
24
+ recordEmittedAsset,
25
+ } from './assets'
26
+
27
+ export {
28
+ assetHref,
29
+ assetPathname,
30
+ clearEmittedAssets,
31
+ cssModuleMapping,
32
+ emitFontCssStylesheet,
33
+ emittedAssetName,
34
+ emittedAssetNames,
35
+ publishEmittedAssets,
36
+ recordEmittedAsset,
37
+ registerCssRuntime,
38
+ routeCssAssetNames,
39
+ routeCssHref,
40
+ withAssetPrefix,
41
+ type AssetHrefConfig,
42
+ } from './assets'
18
43
 
19
44
  interface Pending {
20
45
  resolve: () => void
@@ -126,8 +151,6 @@ interface CssBuildOptions {
126
151
  verbose?: boolean
127
152
  }
128
153
 
129
- let cssRuntimeRegistered = false
130
-
131
154
  export function globalCssSource(config: ResolvedConfig) {
132
155
  return globalCssSources(config)[0]
133
156
  }
@@ -135,7 +158,10 @@ export function globalCssSource(config: ResolvedConfig) {
135
158
  export function globalCssSources(
136
159
  config: Pick<ResolvedConfig, 'root' | 'appPath'> & Partial<Pick<ResolvedConfig, 'workspaceRoot'>>,
137
160
  ) {
138
- return globalCssSourcesForPaths(config.root, config.appPath, config.workspaceRoot)
161
+ return (
162
+ productionRelease(config)?.globalCss ??
163
+ globalCssSourcesForPaths(config.root, config.appPath, config.workspaceRoot)
164
+ )
139
165
  }
140
166
 
141
167
  // The walk below reads and resolves the ROOT LAYOUT'S WHOLE IMPORT GRAPH, and every render asks it
@@ -213,7 +239,7 @@ function collectGlobalCssSources(
213
239
  }
214
240
  visited.add(resolvedFile)
215
241
 
216
- for (const specifier of moduleSpecifiers(readFileSync(resolvedFile, 'utf8'))) {
242
+ for (const specifier of moduleSpecifiers(readSourceSync(resolvedFile))) {
217
243
  const resolved = resolveImport(root, resolvedFile, specifier, workspaceRoot)
218
244
  if (!resolved) continue
219
245
  if (isCssFile(resolved)) {
@@ -233,130 +259,6 @@ function rootLayoutFile(appPath: string, missing?: Set<string>) {
233
259
  return path.join(appPath, 'layout.tsx')
234
260
  }
235
261
 
236
- export function globalCssHref(config: ResolvedConfig) {
237
- return globalCssSources(config).length > 0 ? assetHref(config, 'global.css') : undefined
238
- }
239
-
240
- export type AssetHrefConfig = Pick<ResolvedConfig, 'assetPrefix' | 'compat' | 'outPath'>
241
-
242
- /**
243
- * Logical build-asset name -> the content-hashed name the build actually emitted
244
- * (`global.css` -> `global-1f4a9c2b3d5e6f70.css`). A production asset name must
245
- * carry its content, or the URL cannot honestly be served `immutable`: the same
246
- * name would answer different bytes after a deploy.
247
- *
248
- * Keyed by outPath, never process-global: one process builds several apps (the
249
- * test suite does it constantly) and their names must not cross. Empty in dev,
250
- * where names stay flat and every asset is served `no-cache` anyway.
251
- */
252
- const emittedAssets = new Map<string, Map<string, string>>()
253
-
254
- export function recordEmittedAsset(outPath: string, logical: string, emitted: string): void {
255
- const names = emittedAssets.get(outPath) ?? new Map<string, string>()
256
- names.set(logical, emitted)
257
- emittedAssets.set(outPath, names)
258
- }
259
-
260
- /** Re-publish the build's name map at server boot (see BuildManifest.assetNames). */
261
- export function publishEmittedAssets(
262
- outPath: string,
263
- names: Record<string, string> | undefined,
264
- ): void {
265
- if (!names) return
266
- for (const [logical, emitted] of Object.entries(names))
267
- recordEmittedAsset(outPath, logical, emitted)
268
- }
269
-
270
- export function emittedAssetNames(outPath: string): Record<string, string> {
271
- return Object.fromEntries(emittedAssets.get(outPath) ?? [])
272
- }
273
-
274
- export function clearEmittedAssets(outPath: string): void {
275
- emittedAssets.delete(outPath)
276
- }
277
-
278
- /** The file name a logical asset name resolves to. Identity until a build records one. */
279
- export function emittedAssetName(
280
- config: Pick<ResolvedConfig, 'outPath'> | undefined,
281
- name: string,
282
- ): string {
283
- if (!config?.outPath) return name
284
- return emittedAssets.get(config.outPath)?.get(name) ?? name
285
- }
286
-
287
- // Where a built asset lives in the URL space. next-compat serves the build output under Next's
288
- // static path, so a compat app's document references its CSS/JS exactly as Next does; core keeps
289
- // `/assets/`. Every document-emitted build-asset URL goes through here (and the dev/prod servers
290
- // accept both spellings), so an emitted href can never name a path the server will not serve —
291
- // including the content hash the production name carries.
292
- export function assetPathname(
293
- config: Pick<ResolvedConfig, 'compat' | 'outPath'> | undefined,
294
- name: string,
295
- ) {
296
- const emitted = emittedAssetName(config, name)
297
- return nextCompatEnabled(config ?? {}) ? `/_next/static/${emitted}` : `/assets/${emitted}`
298
- }
299
-
300
- export function assetHref(config: AssetHrefConfig | undefined, name: string) {
301
- return withAssetPrefix(config, assetPathname(config, name))
302
- }
303
-
304
- // A route's built CSS filenames - the one rule behind the document's links, the
305
- // inline-CSS path and analyze, so no consumer can disagree about whether a
306
- // route has CSS. compat cssChunking records split names in cssAssets.
307
- export function routeCssAssetNames(
308
- route: Pick<RouteManifestEntry, 'id' | 'cssImports' | 'cssAssets'>,
309
- ): string[] {
310
- if (route.cssImports.length === 0) return []
311
- return route.cssAssets?.length ? route.cssAssets : [`${route.id}.css`]
312
- }
313
-
314
- export function routeCssHref(
315
- route: RouteManifestEntry,
316
- config?: AssetHrefConfig,
317
- ): string | string[] | undefined {
318
- const assets = routeCssAssetNames(route)
319
- if (assets.length === 0) return undefined
320
- return assets.map(asset => assetHref(config, asset))
321
- }
322
-
323
- /**
324
- * Put a render's next/font rules in the first stylesheet chunk, matching Next's
325
- * linked CSS delivery without changing the rest of the route's chunk order.
326
- */
327
- export async function emitFontCssStylesheet(
328
- config: ResolvedConfig,
329
- routeId: string,
330
- fontCss: string,
331
- baseAsset: string | undefined,
332
- options: { dev: boolean },
333
- ) {
334
- const outDir = path.join(config.outPath, options.dev ? 'cache' : 'public', 'assets')
335
- await ensureDir(outDir)
336
- const baseFile = baseAsset ? path.join(outDir, baseAsset) : undefined
337
- const baseCss = baseFile && existsSync(baseFile) ? await readFile(baseFile, 'utf8') : ''
338
- const contents = [fontCss, baseCss].filter(Boolean).join('\n')
339
- const logicalName = baseAsset
340
- ? baseAsset.replace(/-[0-9a-f]{16}(?=\.css$)/, '')
341
- : `${routeId}.css`
342
- const name = hashedAssetName(logicalName, contents)
343
- const file = path.join(outDir, name)
344
- if (!existsSync(file) || (await readFile(file, 'utf8')) !== contents)
345
- await writeFile(file, contents)
346
- return name
347
- }
348
-
349
- // Prepend the configured assetPrefix (a CDN origin or path) to an app-absolute
350
- // asset URL. Link hrefs use basePath instead and must NOT go through here.
351
- export function withAssetPrefix(
352
- config: Pick<ResolvedConfig, 'assetPrefix'> | undefined,
353
- href: string,
354
- ) {
355
- const prefix = config?.assetPrefix
356
- if (!prefix) return href
357
- return `${prefix.replace(/\/$/, '')}${href}`
358
- }
359
-
360
262
  /**
361
263
  * Spawn the CSS worker and load the app's postcss config before any stylesheet
362
264
  * is asked for, so Tailwind's cold boot overlaps with the rest of startup
@@ -394,7 +296,7 @@ export async function buildGlobalCss(config: ResolvedConfig, options: CssBuildOp
394
296
  await log.step('global css: esbuild bundle', () =>
395
297
  build({
396
298
  stdin: {
397
- contents: sources.map(file => `@import ${JSON.stringify(file)};`).join('\n'),
299
+ contents: stdinImports(config.root, sources),
398
300
  loader: 'css',
399
301
  resolveDir: config.root,
400
302
  sourcefile: 'global.css',
@@ -447,8 +349,9 @@ async function fingerprintAsset(
447
349
  outDir: string,
448
350
  assetName: string,
449
351
  options: CssBuildOptions,
352
+ file = assetName,
450
353
  ) {
451
- const outfile = path.join(outDir, assetName)
354
+ const outfile = path.join(outDir, file)
452
355
  if (options.dev) return outfile
453
356
  const emittedName = hashedAssetName(assetName, await readFile(outfile))
454
357
  const emitted = path.join(outDir, emittedName)
@@ -457,6 +360,19 @@ async function fingerprintAsset(
457
360
  return emitted
458
361
  }
459
362
 
363
+ // Relative to the build's resolveDir: an absolute specifier would match the public-url plugin's
364
+ // `^/` filter and cost a JS round trip per import.
365
+ function stdinImports(root: string, files: string[]) {
366
+ return files
367
+ .map(file => {
368
+ const relative = path.relative(root, file).split(path.sep).join('/')
369
+ const specifier =
370
+ path.isAbsolute(relative) || relative.startsWith('../') ? relative : `./${relative}`
371
+ return `@import ${JSON.stringify(specifier)};`
372
+ })
373
+ .join('\n')
374
+ }
375
+
460
376
  function moduleSpecifiers(source: string) {
461
377
  const specifiers: string[] = []
462
378
  const pattern = /(?:import|export)\s+(?:[^'";]+?\s+from\s+)?['"]([^'"]+)['"]/g
@@ -501,38 +417,120 @@ export function prepareRouteCssChunks(routes: Pick<RouteManifestEntry, 'id' | 'c
501
417
  for (const [id, segments] of plan) routeCssPlan.set(id, segments)
502
418
  }
503
419
 
420
+ interface CssChunk {
421
+ imports: string[]
422
+ name: string
423
+ label: string
424
+ }
425
+
426
+ /** A route's stylesheet chunks, one per planned slice, and the split names the route records. */
427
+ function routeCssChunks(route: RouteManifestEntry) {
428
+ if (route.cssImports.length === 0) return undefined
429
+ const segments = routeCssPlan.get(route.id)
430
+ if (segments && segments.length > 1) {
431
+ const cssAssets = segments.map((_, index) => `${route.id}-${index}.css`)
432
+ const chunks: CssChunk[] = segments.map((imports, index) => ({
433
+ imports,
434
+ name: cssAssets[index]!,
435
+ label: `route css ${route.route} #${index + 1}`,
436
+ }))
437
+ return { cssAssets, chunks }
438
+ }
439
+ const chunks: CssChunk[] = [
440
+ { imports: route.cssImports, name: `${route.id}.css`, label: `route css ${route.route}` },
441
+ ]
442
+ return { cssAssets: undefined, chunks }
443
+ }
444
+
445
+ function clientReferenceCssChunks(reference: ClientReference): CssChunk[] {
446
+ if (!reference.cssImports || reference.cssImports.length === 0) return []
447
+ return [
448
+ {
449
+ imports: reference.cssImports,
450
+ name: `${reference.id}.css`,
451
+ label: `island css ${reference.id}`,
452
+ },
453
+ ]
454
+ }
455
+
504
456
  export async function buildRouteCss(
505
457
  config: ResolvedConfig,
506
458
  route: RouteManifestEntry,
507
459
  options: CssBuildOptions = {},
508
460
  ) {
509
- if (route.cssImports.length === 0) return undefined
510
- const segments = routeCssPlan.get(route.id)
511
- if (segments && segments.length > 1) {
512
- const assetNames = segments.map((_, index) => `${route.id}-${index}.css`)
513
- route.cssAssets = assetNames
514
- const files: string[] = []
515
- for (const [index, segment] of segments.entries()) {
516
- files.push(
517
- await bundleCssChunk(
518
- config,
519
- segment,
520
- assetNames[index]!,
521
- `route css ${route.route} #${index + 1}`,
522
- options,
523
- ),
524
- )
525
- }
526
- return files
461
+ const css = routeCssChunks(route)
462
+ if (!css) return undefined
463
+ route.cssAssets = css.cssAssets
464
+ const files: string[] = []
465
+ for (const chunk of css.chunks) {
466
+ files.push(await bundleCssChunk(config, chunk.imports, chunk.name, chunk.label, options))
527
467
  }
528
- route.cssAssets = undefined
529
- return bundleCssChunk(
530
- config,
531
- route.cssImports,
532
- `${route.id}.css`,
533
- `route css ${route.route}`,
534
- options,
535
- )
468
+ return css.cssAssets ? files : files[0]
469
+ }
470
+
471
+ /** Logical names the document stylesheets own; a route chunk spelled the same waits for them. */
472
+ const DOCUMENT_CSS = new Set([
473
+ 'global.css',
474
+ 'not-found.css',
475
+ 'global-not-found.css',
476
+ 'global-error.css',
477
+ ])
478
+
479
+ /**
480
+ * Every route's and island's stylesheet for a production build, bundled at once rather than one
481
+ * route after another and alongside the document stylesheets. Each route gets a `publish` that the
482
+ * prerender loop calls in route order once the document sheets are done: it records the route's
483
+ * names exactly when, and in the order, a serial pass would, so the manifest is unchanged.
484
+ */
485
+ export function buildRouteStylesheets(
486
+ config: ResolvedConfig,
487
+ routes: RouteManifestEntry[],
488
+ documentCss: Promise<unknown>,
489
+ options: CssBuildOptions = {},
490
+ ) {
491
+ // One bundle per distinct input; an island several routes reach is built once.
492
+ const builds = new Map<string, { file: string; outDir: Promise<string>; emitted?: string }>()
493
+ const taken = new Set<string>()
494
+ const publish = new Map<RouteManifestEntry, () => Promise<void>>()
495
+ for (const route of routes) {
496
+ const css = routeCssChunks(route)
497
+ const chunks = [
498
+ ...(css?.chunks ?? []),
499
+ ...route.clientReferences.flatMap(clientReferenceCssChunks),
500
+ ]
501
+ const own = chunks.map(chunk => {
502
+ const key = `${chunk.name}\0${chunk.imports.join('\0')}`
503
+ let build = builds.get(key)
504
+ if (!build) {
505
+ // Two routes can share an id (`/a-b`, `/a/b`): different input under a taken name gets its own file.
506
+ const file = taken.has(chunk.name) ? `.${builds.size}-${chunk.name}` : chunk.name
507
+ taken.add(chunk.name)
508
+ const write = () => writeCssChunk(config, chunk.imports, file, chunk.label, options)
509
+ const outDir = DOCUMENT_CSS.has(file) ? documentCss.then(write) : write()
510
+ outDir.catch(() => undefined)
511
+ build = { file, outDir }
512
+ builds.set(key, build)
513
+ }
514
+ return { name: chunk.name, build }
515
+ })
516
+ publish.set(route, async () => {
517
+ if (css) route.cssAssets = css.cssAssets
518
+ for (const { name, build } of own) {
519
+ if (build.emitted) recordEmittedAsset(config.outPath, name, build.emitted)
520
+ else {
521
+ const emitted = await fingerprintAsset(
522
+ config,
523
+ await build.outDir,
524
+ name,
525
+ options,
526
+ build.file,
527
+ )
528
+ build.emitted = path.basename(emitted)
529
+ }
530
+ }
531
+ })
532
+ }
533
+ return publish
536
534
  }
537
535
 
538
536
  // The synthetic document conventions are created at render time and never
@@ -581,14 +579,9 @@ export async function buildClientReferenceCss(
581
579
  reference: ClientReference,
582
580
  options: CssBuildOptions = {},
583
581
  ) {
584
- if (!reference.cssImports || reference.cssImports.length === 0) return undefined
585
- return bundleCssChunk(
586
- config,
587
- reference.cssImports,
588
- `${reference.id}.css`,
589
- `island css ${reference.id}`,
590
- options,
591
- )
582
+ const [chunk] = clientReferenceCssChunks(reference)
583
+ if (!chunk) return undefined
584
+ return bundleCssChunk(config, chunk.imports, chunk.name, chunk.label, options)
592
585
  }
593
586
 
594
587
  async function bundleCssChunk(
@@ -597,6 +590,18 @@ async function bundleCssChunk(
597
590
  assetName: string,
598
591
  label: string,
599
592
  options: CssBuildOptions,
593
+ ) {
594
+ const outDir = await writeCssChunk(config, cssImports, assetName, label, options)
595
+ return fingerprintAsset(config, outDir, assetName, options)
596
+ }
597
+
598
+ /** Bundle (and postcss) one stylesheet under its logical name; returns its directory. */
599
+ async function writeCssChunk(
600
+ config: ResolvedConfig,
601
+ cssImports: string[],
602
+ assetName: string,
603
+ label: string,
604
+ options: CssBuildOptions,
600
605
  ) {
601
606
  const log = createVerboseLogger(options.verbose ?? false, 'css')
602
607
 
@@ -611,7 +616,7 @@ async function bundleCssChunk(
611
616
  const postcss = hasPostcssConfig(config.root)
612
617
  await build({
613
618
  stdin: {
614
- contents: cssImports.map(file => `@import ${JSON.stringify(file)};`).join('\n'),
619
+ contents: stdinImports(config.root, cssImports),
615
620
  loader: 'css',
616
621
  resolveDir: config.root,
617
622
  sourcefile: assetName,
@@ -649,8 +654,7 @@ async function bundleCssChunk(
649
654
  await rewriteTailwindConfigDirectives(outfile, cssImports)
650
655
  await log.step(`${label}: postcss`, () => runPostcssOffThread(config, outfile, options))
651
656
  }
652
-
653
- return fingerprintAsset(config, outDir, assetName, options)
657
+ return outDir
654
658
  }
655
659
 
656
660
  // A leading slash in a CSS url() is a public URL, not an app-root filesystem
@@ -667,34 +671,6 @@ function cssPublicUrlPlugin(): Plugin {
667
671
  }
668
672
  }
669
673
 
670
- export function registerCssRuntime() {
671
- if (cssRuntimeRegistered) return
672
- cssRuntimeRegistered = true
673
-
674
- Bun.plugin({
675
- name: 'pnext-css-runtime',
676
- setup(plugin) {
677
- plugin.onLoad({ filter: /\.(?:css|scss|sass)$/ }, async ({ path: file }) =>
678
- isCssModuleFile(file)
679
- ? {
680
- exports: { default: await cssModuleMapping(file) },
681
- loader: 'object',
682
- }
683
- : {
684
- contents: 'export default undefined;',
685
- loader: 'js',
686
- },
687
- )
688
- },
689
- })
690
- }
691
-
692
- // Sass is unsupported (no compiler); its imports still must not break the
693
- // bundle, so .scss/.sass load as empty modules with module class-name maps.
694
- function isCssModuleFile(file: string) {
695
- return /\.module\.(?:css|scss|sass)$/.test(file)
696
- }
697
-
698
674
  export function cssModuleClientPlugin(): Plugin {
699
675
  return {
700
676
  name: 'pnext-css-modules',
@@ -822,31 +798,6 @@ function cssModuleBuildPlugin(): Plugin {
822
798
  }
823
799
  }
824
800
 
825
- async function cssModuleMapping(file: string) {
826
- // Compat handles *.module.{scss,sass} (sass compiles + scopes); core keeps
827
- // plain .module.css. Lazy read: the registry populates at compat bootstrap.
828
- const compatMapping = getCssExtensions().resolveCssModule(file)
829
- if (compatMapping) return compatMapping
830
- const source = await readText(file)
831
- const classNames = cssClassNames(source)
832
-
833
- return Object.fromEntries(
834
- [...classNames].map(className => [className, cssModuleClassName(file, className)]),
835
- )
836
- }
837
-
838
- function cssClassNames(source: string) {
839
- const classNames = new Set<string>()
840
- const classPattern = /(^|[^\\])\.(-?[_a-zA-Z][\w-]*)/g
841
- let match: RegExpExecArray | null
842
-
843
- while ((match = classPattern.exec(source))) {
844
- if (match[2]) classNames.add(match[2])
845
- }
846
-
847
- return classNames
848
- }
849
-
850
801
  function transformCssModule(source: string, file: string) {
851
802
  let css = source
852
803
  for (const className of cssClassNames(source)) {
@@ -899,15 +850,6 @@ function keyframeNames(css: string) {
899
850
  return names
900
851
  }
901
852
 
902
- function cssModuleClassName(file: string, className: string) {
903
- const base = path
904
- .basename(file)
905
- .replace(/\.module\.css$/, '')
906
- .replace(/[^_a-zA-Z0-9]/g, '_')
907
- const hash = pathHash(cssModuleScopePath(file))
908
- return `${base}_${className}_${hash}`
909
- }
910
-
911
853
  function rewriteClassSelector(css: string, scopedClassName: string, originalClassName: string) {
912
854
  const esbuildNamePattern = new RegExp(
913
855
  `\\.${escapeRegex(originalClassName)}(?![-_a-zA-Z0-9])`,
@@ -916,12 +858,6 @@ function rewriteClassSelector(css: string, scopedClassName: string, originalClas
916
858
  return css.replace(esbuildNamePattern, `.${scopedClassName}`)
917
859
  }
918
860
 
919
- function pathHash(file: string) {
920
- let hash = 5381
921
- for (const char of file) hash = ((hash << 5) + hash) ^ char.charCodeAt(0)
922
- return (hash >>> 0).toString(36).slice(0, 5)
923
- }
924
-
925
861
  function escapeRegex(value: string) {
926
862
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
927
863
  }
package/src/dev/server.ts CHANGED
@@ -52,7 +52,7 @@ import {
52
52
  staticMetadataCacheControl,
53
53
  } from '../routing/metadata-files'
54
54
  import { handleRouteModule, type RouteHandlerModule } from '../routing/handler'
55
- import { malformedUrlResponse, trailingSlashRedirect } from '../routing/href'
55
+ import { malformedUrlResponse, repeatedSlashRedirect, trailingSlashRedirect } from '../routing/href'
56
56
  import { type PeerAddressSource, withForwardedHeaders } from '../routing/forwarded'
57
57
  import {
58
58
  assertNoServerActionsWithoutCompat,
@@ -87,7 +87,14 @@ import {
87
87
  setWorkUnitRoute,
88
88
  } from '../request/context'
89
89
  import { setRequestRuntime } from '../routing/request-environment'
90
- import { ensureDir, listFiles, writeText } from '../utils/fs'
90
+ import {
91
+ ensureDir,
92
+ exactNameIndex,
93
+ type ExactNames,
94
+ hasExactName,
95
+ listFiles,
96
+ writeText,
97
+ } from '../utils/fs'
91
98
  import { contentType } from '../utils/content-type'
92
99
  import { traceEnabled } from '../utils/trace-flags'
93
100
  import {
@@ -290,6 +297,7 @@ export async function startDevServer(options: DevServerOptions) {
290
297
  markBoot('boot:scanRoutes')
291
298
  await validateProxyFiles(config)
292
299
  let proxyRunner = createProxyRunner(config)
300
+ const exactNames = exactNameIndex(config.root)
293
301
  markBoot('boot:proxy')
294
302
  let devImportVersion = String(Date.now())
295
303
  // The proxy runs serially in front of every request, so its compile+import is
@@ -436,6 +444,8 @@ export async function startDevServer(options: DevServerOptions) {
436
444
  if (badRequest) return badRequest
437
445
  request = withForwardedHeaders(request, server)
438
446
  let url = new URL(request.url)
447
+ const slashRedirect = repeatedSlashRedirect(url)
448
+ if (slashRedirect) return slashRedirect
439
449
  const profile = devRequestProfile(request, url)
440
450
  if (profile) logDevProfile(profile, 'prologue (bootstrap/headers/url)', prologueStart)
441
451
  let pageLog = startDevPageStallTimer(pendingDevPageLoadLog(routes, request, url))
@@ -475,6 +485,10 @@ export async function startDevServer(options: DevServerOptions) {
475
485
  }
476
486
 
477
487
  try {
488
+ // Before the proxy, for every method, as in Next: the proxy only ever sees canonical paths.
489
+ const canonicalRedirect = trailingSlashRedirect(config, url)
490
+ if (canonicalRedirect) return finish(canonicalRedirect)
491
+
478
492
  const initialPrefetchResponse = maybeDevPagePrefetchResponse(routes, url.pathname, request)
479
493
  if (initialPrefetchResponse) return finish(initialPrefetchResponse)
480
494
 
@@ -570,7 +584,7 @@ export async function startDevServer(options: DevServerOptions) {
570
584
  }
571
585
 
572
586
  const staticResponse = await profileDevStep(profile, 'static lookup', () =>
573
- maybeStaticFile(config.publicPath, url.pathname),
587
+ maybeStaticFile(config.publicPath, url.pathname, exactNames),
574
588
  )
575
589
  if (staticResponse) {
576
590
  setWorkUnitRoute('static-asset', 'static')
@@ -582,7 +596,11 @@ export async function startDevServer(options: DevServerOptions) {
582
596
  .staticAssetPublicPrefixes()
583
597
  .some(prefix => url.pathname.startsWith(prefix))
584
598
  ) {
585
- const emitted = await maybeStaticFile(path.join(config.outPath, 'public'), url.pathname)
599
+ const emitted = await maybeStaticFile(
600
+ path.join(config.outPath, 'public'),
601
+ url.pathname,
602
+ exactNames,
603
+ )
586
604
  if (emitted) {
587
605
  setWorkUnitRoute('static-asset', 'static')
588
606
  return finish(applyProxyResponse(emitted, proxyResponse))
@@ -597,6 +615,7 @@ export async function startDevServer(options: DevServerOptions) {
597
615
  const outPublicStatic = await maybeStaticFile(
598
616
  path.join(config.outPath, 'public'),
599
617
  staticAssetPathname,
618
+ exactNames,
600
619
  )
601
620
  if (outPublicStatic) {
602
621
  setWorkUnitRoute('static-asset', 'static')
@@ -647,9 +666,6 @@ export async function startDevServer(options: DevServerOptions) {
647
666
  }
648
667
  }
649
668
 
650
- const canonicalRedirect = trailingSlashRedirect(config, url, request.method.toUpperCase())
651
- if (canonicalRedirect) return finish(applyProxyResponse(canonicalRedirect, proxyResponse))
652
-
653
669
  const nav = parseNavState(request)
654
670
  const matched = profileDevSyncStep(profile, 'match route', () =>
655
671
  selectRouteForRequest(routes, url.pathname, nav),
@@ -1402,9 +1418,12 @@ async function maybeStaticMetadataFile(config: ResolvedConfig, url: URL) {
1402
1418
  })
1403
1419
  }
1404
1420
 
1405
- async function maybeStaticFile(publicPath: string, pathname: string) {
1421
+ async function maybeStaticFile(publicPath: string, pathname: string, exactNames: ExactNames) {
1406
1422
  const filePath = path.join(publicPath, pathname.replace(/^\/+/, ''))
1407
1423
  if (!isInside(publicPath, filePath) || !existsSync(filePath)) return null
1424
+ if (exactNames && !hasExactName(exactNames, publicPath, path.relative(publicPath, filePath))) {
1425
+ return null
1426
+ }
1408
1427
  const fileStat = await stat(filePath)
1409
1428
  if (!fileStat.isFile()) return null
1410
1429
  return devResponse(await readFile(filePath), contentType(filePath))
package/src/extensions.ts CHANGED
@@ -80,6 +80,9 @@ export function createExtensionHost(): ExtensionHost {
80
80
  }
81
81
 
82
82
  let activeHost = createExtensionHost()
83
+ // Anchored for modules that must not import this one (api/server, shared by embedded hosts).
84
+ const ANCHOR = Symbol.for('pnext.extensionHost')
85
+ ;(globalThis as Record<symbol, unknown>)[ANCHOR] ??= activeHost
83
86
 
84
87
  /** The host every accessor in this module reads. */
85
88
  export function getExtensionHost(): ExtensionHost {
@@ -148,6 +151,7 @@ export function withExtensionHost<T>(host: ExtensionHost, callback: () => T): T
148
151
  }
149
152
 
150
153
  function announceHostChange() {
154
+ ;(globalThis as Record<symbol, unknown>)[ANCHOR] = activeHost
151
155
  for (const handler of resetHandlers) handler()
152
156
  // Consumers latch the loadable-extension set (the server load plugin's filter
153
157
  // regex); a swap changes it, so invalidate exactly as a registration would.
@@ -66,12 +66,25 @@ export interface FontExtensions {
66
66
  * the same memo the render reads. Default: none.
67
67
  */
68
68
  prewarmFontAssets: (config: ResolvedConfig, options: { dev?: boolean }) => Promise<void>
69
+
70
+ /** Resolve every declared font for a release, keyed like the render looks them up. Default: none. */
71
+ releaseFonts: (config: ResolvedConfig) => Promise<Record<string, ReleasedFont>>
72
+ }
73
+
74
+ /** A font resolution as a release stores it; production serves it instead of resolving. */
75
+ export interface ReleasedFont {
76
+ css: string
77
+ sizeAdjust: boolean
78
+ preloads: import('../types').MetadataLink[]
79
+ usesFonts: boolean
80
+ files: string[]
69
81
  }
70
82
 
71
83
  const fontExtensions: FontExtensions = {
72
84
  runWithFontScope: callback => callback(),
73
85
  collectFontAssets: () => Promise.resolve({ css: '', preloads: [] }),
74
86
  prewarmFontAssets: () => Promise.resolve(),
87
+ releaseFonts: () => Promise.resolve({}),
75
88
  }
76
89
 
77
90
  export function getFontExtensions(): FontExtensions {