@wular/pnext 0.0.4 → 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 -1992
  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 -1897
  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} +139 -139
  351. package/src/resolve/engine.ts +90 -77
  352. package/src/resolve/imports.ts +475 -463
  353. package/src/resolve/scan-facts.ts +318 -296
  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 +827 -815
  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
@@ -11,12 +11,12 @@
11
11
  // Served from the request interceptor registered in ../register-image.ts.
12
12
  // ---------------------------------------------------------------------------
13
13
 
14
- import path from 'node:path';
15
- import type { ResolvedConfig } from '../../config';
16
- import { getNextConfig } from '../next/config-loader';
17
- import { getImagesConfig, type ResolvedImagesConfig } from '../next/image/config';
18
- import { hasLocalMatch, hasRemoteMatch } from '../next/image/patterns';
19
- import { cacheHandlerGet, cacheHandlerSet, hasCacheHandler } from '../cache/handler';
14
+ import path from 'node:path'
15
+ import type { ResolvedConfig } from '../../config'
16
+ import { getNextConfig } from '../next/config-loader'
17
+ import { getImagesConfig, type ResolvedImagesConfig } from '../next/image/config'
18
+ import { hasLocalMatch, hasRemoteMatch } from '../next/image/patterns'
19
+ import { cacheHandlerGet, cacheHandlerSet, hasCacheHandler } from '../cache/handler'
20
20
  import {
21
21
  ANIMATABLE_TYPES,
22
22
  AVIF,
@@ -28,7 +28,7 @@ import {
28
28
  extensionForType,
29
29
  isAnimated,
30
30
  typeForExtension,
31
- } from './detect';
31
+ } from './detect'
32
32
  import {
33
33
  IMAGE_CACHE_VERSION,
34
34
  getHash,
@@ -36,74 +36,74 @@ import {
36
36
  readCacheEntry,
37
37
  writeCacheEntry,
38
38
  type ImageCacheEntry,
39
- } from './cache';
39
+ } from './cache'
40
40
  import {
41
41
  ImageError,
42
42
  fetchExternalImage,
43
43
  fetchInternalImage,
44
44
  readPublicFile,
45
45
  type ImageUpstream,
46
- } from './source';
46
+ } from './source'
47
47
 
48
- type XCacheHeader = 'MISS' | 'HIT' | 'STALE';
48
+ type XCacheHeader = 'MISS' | 'HIT' | 'STALE'
49
49
 
50
50
  interface ImageParams {
51
- href: string;
52
- isAbsolute: boolean;
53
- isStatic: boolean;
54
- width: number;
55
- quality: number;
56
- mimeType: string;
51
+ href: string
52
+ isAbsolute: boolean
53
+ isStatic: boolean
54
+ width: number
55
+ quality: number
56
+ mimeType: string
57
57
  }
58
58
 
59
59
  interface GeneratedImage {
60
- buffer: Buffer;
61
- contentType: string;
62
- maxAge: number;
63
- etag: string;
64
- upstreamEtag: string;
60
+ buffer: Buffer
61
+ contentType: string
62
+ maxAge: number
63
+ etag: string
64
+ upstreamEtag: string
65
65
  }
66
66
 
67
67
  // -- sharp ------------------------------------------------------------------
68
68
 
69
69
  interface SharpPipeline {
70
- timeout(options: { seconds: number }): SharpPipeline;
71
- rotate(): SharpPipeline;
70
+ timeout(options: { seconds: number }): SharpPipeline
71
+ rotate(): SharpPipeline
72
72
  resize(
73
73
  width: number | null,
74
74
  height?: number | null,
75
75
  options?: { withoutEnlargement?: boolean },
76
- ): SharpPipeline;
77
- webp(options: { quality: number }): SharpPipeline;
78
- avif(options: { quality: number; effort?: number }): SharpPipeline;
79
- png(options: { quality: number }): SharpPipeline;
80
- jpeg(options: { quality: number; mozjpeg?: boolean }): SharpPipeline;
81
- toBuffer(): Promise<Buffer>;
76
+ ): SharpPipeline
77
+ webp(options: { quality: number }): SharpPipeline
78
+ avif(options: { quality: number; effort?: number }): SharpPipeline
79
+ png(options: { quality: number }): SharpPipeline
80
+ jpeg(options: { quality: number; mozjpeg?: boolean }): SharpPipeline
81
+ toBuffer(): Promise<Buffer>
82
82
  }
83
83
 
84
- type SharpModule = (input: Buffer | Uint8Array) => SharpPipeline;
84
+ type SharpModule = (input: Buffer | Uint8Array) => SharpPipeline
85
85
 
86
- let sharpPromise: Promise<SharpModule | undefined> | undefined;
86
+ let sharpPromise: Promise<SharpModule | undefined> | undefined
87
87
  function loadSharp(): Promise<SharpModule | undefined> {
88
88
  if (!sharpPromise) {
89
89
  sharpPromise = import('sharp')
90
90
  .then(m => ((m as { default?: unknown }).default ?? m) as SharpModule)
91
- .catch(() => undefined);
91
+ .catch(() => undefined)
92
92
  }
93
- return sharpPromise;
93
+ return sharpPromise
94
94
  }
95
95
 
96
96
  // -- helpers ----------------------------------------------------------------
97
97
 
98
- const warnedOnce = new Set<string>();
98
+ const warnedOnce = new Set<string>()
99
99
  function warnOnce(message: string): void {
100
- if (warnedOnce.has(message)) return;
101
- warnedOnce.add(message);
102
- console.warn(message);
100
+ if (warnedOnce.has(message)) return
101
+ warnedOnce.add(message)
102
+ console.warn(message)
103
103
  }
104
104
 
105
105
  function badRequest(message: string): Response {
106
- return new Response(message, { status: 400, headers: { 'content-type': 'text/plain' } });
106
+ return new Response(message, { status: 400, headers: { 'content-type': 'text/plain' } })
107
107
  }
108
108
 
109
109
  function errorResponse(error: unknown): Response {
@@ -111,47 +111,47 @@ function errorResponse(error: unknown): Response {
111
111
  return new Response(error.message, {
112
112
  status: error.statusCode,
113
113
  headers: { 'content-type': 'text/plain' },
114
- });
114
+ })
115
115
  }
116
- console.error('image optimizer failed', error);
117
- return new Response('Internal Server Error', { status: 500 });
116
+ console.error('image optimizer failed', error)
117
+ return new Response('Internal Server Error', { status: 500 })
118
118
  }
119
119
 
120
120
  function getMaxAge(cacheControl: string | null | undefined): number {
121
- if (!cacheControl) return 0;
121
+ if (!cacheControl) return 0
122
122
  for (const directive of cacheControl.split(',')) {
123
- const [key = '', rawValue] = directive.trim().split('=', 2);
124
- if (key.toLowerCase() !== 's-maxage' && key.toLowerCase() !== 'max-age') continue;
125
- let value = (rawValue ?? '').toLowerCase();
126
- if (value.startsWith('"') && value.endsWith('"')) value = value.slice(1, -1);
127
- const n = parseInt(value, 10);
128
- if (!isNaN(n)) return n;
123
+ const [key = '', rawValue] = directive.trim().split('=', 2)
124
+ if (key.toLowerCase() !== 's-maxage' && key.toLowerCase() !== 'max-age') continue
125
+ let value = (rawValue ?? '').toLowerCase()
126
+ if (value.startsWith('"') && value.endsWith('"')) value = value.slice(1, -1)
127
+ const n = parseInt(value, 10)
128
+ if (!isNaN(n)) return n
129
129
  }
130
- return 0;
130
+ return 0
131
131
  }
132
132
 
133
133
  /** First configured format the Accept header explicitly names, or '' (keep source format). */
134
134
  function getSupportedMimeType(formats: string[], accept: string): string {
135
135
  for (const format of formats) {
136
- if (accept.includes(format)) return format;
136
+ if (accept.includes(format)) return format
137
137
  }
138
- return '';
138
+ return ''
139
139
  }
140
140
 
141
141
  function getFileNameWithExtension(url: string, contentType: string | null): string {
142
- const [urlWithoutQueryParams = url] = url.split('?', 1);
143
- const fileNameWithExtension = urlWithoutQueryParams.split('/').pop();
144
- if (!contentType || !fileNameWithExtension) return 'image.bin';
145
- const [fileName = 'image'] = fileNameWithExtension.split('.', 1);
146
- const extension = extensionForType(contentType) ?? 'bin';
147
- return `${fileName}.${extension}`;
142
+ const [urlWithoutQueryParams = url] = url.split('?', 1)
143
+ const fileNameWithExtension = urlWithoutQueryParams.split('/').pop()
144
+ if (!contentType || !fileNameWithExtension) return 'image.bin'
145
+ const [fileName = 'image'] = fileNameWithExtension.split('.', 1)
146
+ const extension = extensionForType(contentType) ?? 'bin'
147
+ return `${fileName}.${extension}`
148
148
  }
149
149
 
150
150
  function contentDispositionHeader(type: string, fileName: string): string {
151
151
  if (/^[\x20-\x7e]*$/.test(fileName) && !fileName.includes('"')) {
152
- return `${type}; filename="${fileName}"`;
152
+ return `${type}; filename="${fileName}"`
153
153
  }
154
- return `${type}; filename*=UTF-8''${encodeURIComponent(fileName)}`;
154
+ return `${type}; filename*=UTF-8''${encodeURIComponent(fileName)}`
155
155
  }
156
156
 
157
157
  // -- validation -------------------------------------------------------------
@@ -162,82 +162,82 @@ function validateParams(
162
162
  config: ResolvedImagesConfig,
163
163
  basePath: string,
164
164
  ): ImageParams | { errorMessage: string } {
165
- const url = searchParams.get('url');
166
- const w = searchParams.get('w');
167
- const q = searchParams.get('q');
165
+ const url = searchParams.get('url')
166
+ const w = searchParams.get('w')
167
+ const q = searchParams.get('q')
168
168
 
169
- if (!url) return { errorMessage: '"url" parameter is required' };
170
- if (url.length > 3072) return { errorMessage: '"url" parameter is too long' };
169
+ if (!url) return { errorMessage: '"url" parameter is required' }
170
+ if (url.length > 3072) return { errorMessage: '"url" parameter is too long' }
171
171
  if (url.startsWith('//')) {
172
- return { errorMessage: '"url" parameter cannot be a protocol-relative URL (//)' };
172
+ return { errorMessage: '"url" parameter cannot be a protocol-relative URL (//)' }
173
173
  }
174
174
 
175
- let href: string;
176
- let isAbsolute: boolean;
175
+ let href: string
176
+ let isAbsolute: boolean
177
177
  if (url.startsWith('/')) {
178
- href = url;
179
- isAbsolute = false;
180
- let pathname = url.split('?', 1)[0] ?? url;
178
+ href = url
179
+ isAbsolute = false
180
+ let pathname = url.split('?', 1)[0] ?? url
181
181
  try {
182
- pathname = decodeURIComponent(pathname);
182
+ pathname = decodeURIComponent(pathname)
183
183
  } catch {
184
184
  // keep the raw pathname; the regex below still guards the raw form
185
185
  }
186
186
  if (/\/_next\/image($|\/)/.test(pathname)) {
187
- return { errorMessage: '"url" parameter cannot be recursive' };
187
+ return { errorMessage: '"url" parameter cannot be recursive' }
188
188
  }
189
189
  if (!hasLocalMatch(config.localPatterns, new URL(url, 'http://n'))) {
190
- return { errorMessage: '"url" parameter is not allowed' };
190
+ return { errorMessage: '"url" parameter is not allowed' }
191
191
  }
192
192
  } else {
193
- let parsed: URL;
193
+ let parsed: URL
194
194
  try {
195
- parsed = new URL(url);
195
+ parsed = new URL(url)
196
196
  } catch {
197
- return { errorMessage: '"url" parameter is invalid' };
197
+ return { errorMessage: '"url" parameter is invalid' }
198
198
  }
199
- href = parsed.toString();
200
- isAbsolute = true;
199
+ href = parsed.toString()
200
+ isAbsolute = true
201
201
  if (!['http:', 'https:'].includes(parsed.protocol)) {
202
- return { errorMessage: '"url" parameter is invalid' };
202
+ return { errorMessage: '"url" parameter is invalid' }
203
203
  }
204
204
  if (!hasRemoteMatch(config.domains, config.remotePatterns, parsed)) {
205
- return { errorMessage: '"url" parameter is not allowed' };
205
+ return { errorMessage: '"url" parameter is not allowed' }
206
206
  }
207
207
  }
208
208
 
209
- if (!w) return { errorMessage: '"w" parameter (width) is required' };
209
+ if (!w) return { errorMessage: '"w" parameter (width) is required' }
210
210
  if (!/^[0-9]+$/.test(w)) {
211
- return { errorMessage: '"w" parameter (width) must be an integer greater than 0' };
211
+ return { errorMessage: '"w" parameter (width) must be an integer greater than 0' }
212
212
  }
213
- if (!q) return { errorMessage: '"q" parameter (quality) is required' };
213
+ if (!q) return { errorMessage: '"q" parameter (quality) is required' }
214
214
  if (!/^[0-9]+$/.test(q)) {
215
- return { errorMessage: '"q" parameter (quality) must be an integer between 1 and 100' };
215
+ return { errorMessage: '"q" parameter (quality) must be an integer between 1 and 100' }
216
216
  }
217
217
 
218
- const width = parseInt(w, 10);
218
+ const width = parseInt(w, 10)
219
219
  if (width <= 0 || isNaN(width)) {
220
- return { errorMessage: '"w" parameter (width) must be an integer greater than 0' };
220
+ return { errorMessage: '"w" parameter (width) must be an integer greater than 0' }
221
221
  }
222
- const sizes = [...config.deviceSizes, ...config.imageSizes];
222
+ const sizes = [...config.deviceSizes, ...config.imageSizes]
223
223
  if (!sizes.includes(width)) {
224
- return { errorMessage: `"w" parameter (width) of ${width} is not allowed` };
224
+ return { errorMessage: `"w" parameter (width) of ${width} is not allowed` }
225
225
  }
226
226
 
227
- const quality = parseInt(q, 10);
227
+ const quality = parseInt(q, 10)
228
228
  if (isNaN(quality) || quality < 1 || quality > 100) {
229
- return { errorMessage: '"q" parameter (quality) must be an integer between 1 and 100' };
229
+ return { errorMessage: '"q" parameter (quality) must be an integer between 1 and 100' }
230
230
  }
231
231
  if (config.qualities && !config.qualities.includes(quality)) {
232
- return { errorMessage: `"q" parameter (quality) of ${q} is not allowed` };
232
+ return { errorMessage: `"q" parameter (quality) of ${q} is not allowed` }
233
233
  }
234
234
 
235
- const mimeType = getSupportedMimeType(config.formats, request.headers.get('accept') ?? '');
235
+ const mimeType = getSupportedMimeType(config.formats, request.headers.get('accept') ?? '')
236
236
  const isStatic =
237
237
  url.startsWith(`${basePath}/_next/static/media`) ||
238
- url.startsWith(`${basePath}/_next/static/immutable/media`);
238
+ url.startsWith(`${basePath}/_next/static/immutable/media`)
239
239
 
240
- return { href, isAbsolute, isStatic, width, quality, mimeType };
240
+ return { href, isAbsolute, isStatic, width, quality, mimeType }
241
241
  }
242
242
 
243
243
  // -- optimization -----------------------------------------------------------
@@ -248,22 +248,22 @@ async function optimizeImage(
248
248
  width: number,
249
249
  quality: number,
250
250
  ): Promise<Buffer> {
251
- const sharp = await loadSharp();
252
- if (!sharp) throw new Error('sharp is not available');
251
+ const sharp = await loadSharp()
252
+ if (!sharp) throw new Error('sharp is not available')
253
253
  let transformer = sharp(buffer)
254
254
  .timeout({ seconds: 7 })
255
255
  .rotate()
256
- .resize(width, undefined, { withoutEnlargement: true });
256
+ .resize(width, undefined, { withoutEnlargement: true })
257
257
  if (contentType === AVIF) {
258
- transformer = transformer.avif({ quality: Math.max(quality - 20, 1), effort: 3 });
258
+ transformer = transformer.avif({ quality: Math.max(quality - 20, 1), effort: 3 })
259
259
  } else if (contentType === WEBP) {
260
- transformer = transformer.webp({ quality });
260
+ transformer = transformer.webp({ quality })
261
261
  } else if (contentType === PNG) {
262
- transformer = transformer.png({ quality });
262
+ transformer = transformer.png({ quality })
263
263
  } else if (contentType === JPEG) {
264
- transformer = transformer.jpeg({ quality, mozjpeg: true });
264
+ transformer = transformer.jpeg({ quality, mozjpeg: true })
265
265
  }
266
- return transformer.toBuffer();
266
+ return transformer.toBuffer()
267
267
  }
268
268
 
269
269
  async function generateImage(
@@ -271,33 +271,33 @@ async function generateImage(
271
271
  params: ImageParams,
272
272
  config: ResolvedImagesConfig,
273
273
  ): Promise<GeneratedImage> {
274
- const { href, width, quality, mimeType } = params;
275
- const upstreamBuffer = upstream.buffer;
276
- const upstreamEtag = upstream.etag;
277
- const maxAge = Math.max(config.minimumCacheTTL, getMaxAge(upstream.cacheControl));
274
+ const { href, width, quality, mimeType } = params
275
+ const upstreamBuffer = upstream.buffer
276
+ const upstreamEtag = upstream.etag
277
+ const maxAge = Math.max(config.minimumCacheTTL, getMaxAge(upstream.cacheControl))
278
278
 
279
- const upstreamType = detectContentType(upstreamBuffer);
279
+ const upstreamType = detectContentType(upstreamBuffer)
280
280
  if (!upstreamType || !upstreamType.startsWith('image/') || upstreamType.includes(',')) {
281
- console.error("The requested resource isn't a valid image for", href, 'received', upstreamType);
282
- throw new ImageError(400, "The requested resource isn't a valid image.");
281
+ console.error("The requested resource isn't a valid image for", href, 'received', upstreamType)
282
+ throw new ImageError(400, "The requested resource isn't a valid image.")
283
283
  }
284
284
  if (upstreamType.startsWith('image/svg') && !config.dangerouslyAllowSVG) {
285
285
  console.error(
286
286
  `The requested resource "${href}" has type "${upstreamType}" but dangerouslyAllowSVG is disabled. Consider adding the "unoptimized" property to the <Image>.`,
287
- );
288
- throw new ImageError(400, '"url" parameter is valid but image type is not allowed');
287
+ )
288
+ throw new ImageError(400, '"url" parameter is valid but image type is not allowed')
289
289
  }
290
290
  if (ANIMATABLE_TYPES.includes(upstreamType) && isAnimated(upstreamBuffer)) {
291
291
  warnOnce(
292
292
  `The requested resource "${href}" is an animated image so it will not be optimized. Consider adding the "unoptimized" property to the <Image>.`,
293
- );
293
+ )
294
294
  return {
295
295
  buffer: upstreamBuffer,
296
296
  contentType: upstreamType,
297
297
  maxAge,
298
298
  etag: upstreamEtag,
299
299
  upstreamEtag,
300
- };
300
+ }
301
301
  }
302
302
  if (BYPASS_TYPES.includes(upstreamType)) {
303
303
  return {
@@ -306,31 +306,27 @@ async function generateImage(
306
306
  maxAge,
307
307
  etag: upstreamEtag,
308
308
  upstreamEtag,
309
- };
309
+ }
310
310
  }
311
311
 
312
- let contentType: string;
312
+ let contentType: string
313
313
  if (mimeType) {
314
- contentType = mimeType;
315
- } else if (
316
- extensionForType(upstreamType) &&
317
- upstreamType !== WEBP &&
318
- upstreamType !== AVIF
319
- ) {
320
- contentType = upstreamType;
314
+ contentType = mimeType
315
+ } else if (extensionForType(upstreamType) && upstreamType !== WEBP && upstreamType !== AVIF) {
316
+ contentType = upstreamType
321
317
  } else {
322
- contentType = JPEG;
318
+ contentType = JPEG
323
319
  }
324
320
 
325
321
  try {
326
- const optimizedBuffer = await optimizeImage(upstreamBuffer, contentType, width, quality);
322
+ const optimizedBuffer = await optimizeImage(upstreamBuffer, contentType, width, quality)
327
323
  return {
328
324
  buffer: optimizedBuffer,
329
325
  contentType,
330
326
  maxAge,
331
327
  etag: getHash([optimizedBuffer]),
332
328
  upstreamEtag,
333
- };
329
+ }
334
330
  } catch {
335
331
  // If optimization fails, fall back to the original image bytes.
336
332
  return {
@@ -339,31 +335,31 @@ async function generateImage(
339
335
  maxAge: config.minimumCacheTTL,
340
336
  etag: upstreamEtag,
341
337
  upstreamEtag,
342
- };
338
+ }
343
339
  }
344
340
  }
345
341
 
346
342
  // -- cache orchestration ----------------------------------------------------
347
343
 
348
344
  interface OptimizerImagesConfig extends ResolvedImagesConfig {
349
- maximumDiskCacheSize: number | undefined;
345
+ maximumDiskCacheSize: number | undefined
350
346
  }
351
347
 
352
348
  function resolveOptimizerConfig(): OptimizerImagesConfig {
353
- const base = getImagesConfig();
354
- const raw = (getNextConfig().images ?? {}) as Record<string, unknown>;
349
+ const base = getImagesConfig()
350
+ const raw = (getNextConfig().images ?? {}) as Record<string, unknown>
355
351
  return {
356
352
  ...base,
357
353
  maximumDiskCacheSize:
358
354
  typeof raw.maximumDiskCacheSize === 'number' ? raw.maximumDiskCacheSize : undefined,
359
- };
355
+ }
360
356
  }
361
357
 
362
358
  function diskCacheEnabled(config: OptimizerImagesConfig): boolean {
363
- if (config.customCacheHandler && hasCacheHandler()) return false;
364
- if (config.maximumDiskCacheSize === 0) return false;
365
- const experimental = (getNextConfig().experimental ?? {}) as Record<string, unknown>;
366
- return experimental.isrFlushToDisk !== false;
359
+ if (config.customCacheHandler && hasCacheHandler()) return false
360
+ if (config.maximumDiskCacheSize === 0) return false
361
+ const experimental = (getNextConfig().experimental ?? {}) as Record<string, unknown>
362
+ return experimental.isrFlushToDisk !== false
367
363
  }
368
364
 
369
365
  async function fetchUpstream(
@@ -378,7 +374,7 @@ async function fetchUpstream(
378
374
  config.dangerouslyAllowLocalIP,
379
375
  config.maximumResponseBody,
380
376
  config.maximumRedirects,
381
- );
377
+ )
382
378
  }
383
379
  const publicFile = await readPublicFile(
384
380
  [
@@ -387,16 +383,16 @@ async function fetchUpstream(
387
383
  path.join(coreConfig.root, 'public'),
388
384
  ],
389
385
  params.href,
390
- );
386
+ )
391
387
  if (publicFile) {
392
388
  return {
393
389
  buffer: publicFile,
394
390
  contentType: null,
395
391
  cacheControl: null,
396
392
  etag: getHash([publicFile]),
397
- };
393
+ }
398
394
  }
399
- return fetchInternalImage(params.href, origin, config.maximumResponseBody);
395
+ return fetchInternalImage(params.href, origin, config.maximumResponseBody)
400
396
  }
401
397
 
402
398
  async function generateAndStore(
@@ -406,8 +402,8 @@ async function generateAndStore(
406
402
  coreConfig: ResolvedConfig,
407
403
  origin: string,
408
404
  ): Promise<ImageCacheEntry & { contentType: string }> {
409
- const upstream = await fetchUpstream(params, config, coreConfig, origin);
410
- const image = await generateImage(upstream, params, config);
405
+ const upstream = await fetchUpstream(params, config, coreConfig, origin)
406
+ const image = await generateImage(upstream, params, config)
411
407
  const entry: ImageCacheEntry & { contentType: string } = {
412
408
  buffer: image.buffer,
413
409
  etag: image.etag,
@@ -416,9 +412,9 @@ async function generateAndStore(
416
412
  maxAge: image.maxAge,
417
413
  expireAt: Math.max(image.maxAge, config.minimumCacheTTL) * 1000 + Date.now(),
418
414
  contentType: image.contentType,
419
- };
415
+ }
420
416
  if (config.customCacheHandler && hasCacheHandler()) {
421
- const revalidate = Math.max(image.maxAge, config.minimumCacheTTL);
417
+ const revalidate = Math.max(image.maxAge, config.minimumCacheTTL)
422
418
  await cacheHandlerSet(
423
419
  cacheKey,
424
420
  {
@@ -430,23 +426,23 @@ async function generateAndStore(
430
426
  revalidate,
431
427
  },
432
428
  { kind: 'IMAGE' },
433
- );
429
+ )
434
430
  } else if (diskCacheEnabled(config)) {
435
431
  await writeCacheEntry(
436
432
  imageCacheDir(coreConfig.root),
437
433
  cacheKey,
438
434
  entry,
439
435
  config.maximumDiskCacheSize,
440
- );
436
+ )
441
437
  }
442
- return entry;
438
+ return entry
443
439
  }
444
440
 
445
441
  // Concurrent requests for the same (url, w, q, format) share one upstream
446
442
  // fetch + optimization; every waiter reports MISS, matching Next.
447
- const inFlight = new Map<string, Promise<ImageCacheEntry & { contentType: string }>>();
443
+ const inFlight = new Map<string, Promise<ImageCacheEntry & { contentType: string }>>()
448
444
  // One background stale-refresh per key at a time.
449
- const refreshing = new Set<string>();
445
+ const refreshing = new Set<string>()
450
446
 
451
447
  async function readCache(
452
448
  cacheKey: string,
@@ -455,14 +451,13 @@ async function readCache(
455
451
  ): Promise<(ImageCacheEntry & { isStale: boolean }) | undefined> {
456
452
  if (config.customCacheHandler && hasCacheHandler()) {
457
453
  const cached = (await cacheHandlerGet(cacheKey, { kind: 'IMAGE' })) as
458
- | { value?: Record<string, unknown>; lastModified?: number }
459
- | undefined;
460
- const value = cached?.value;
461
- if (value?.kind !== 'IMAGE' || typeof value.buffer !== 'string') return undefined;
454
+ { value?: Record<string, unknown>; lastModified?: number } | undefined
455
+ const value = cached?.value
456
+ if (value?.kind !== 'IMAGE' || typeof value.buffer !== 'string') return undefined
462
457
  const revalidate =
463
- typeof value.revalidate === 'number' ? value.revalidate : config.minimumCacheTTL;
464
- const lastModified = cached?.lastModified ?? Date.now();
465
- const expireAt = Math.max(revalidate, config.minimumCacheTTL) * 1000 + lastModified;
458
+ typeof value.revalidate === 'number' ? value.revalidate : config.minimumCacheTTL
459
+ const lastModified = cached?.lastModified ?? Date.now()
460
+ const expireAt = Math.max(revalidate, config.minimumCacheTTL) * 1000 + lastModified
466
461
  return {
467
462
  buffer: Buffer.from(value.buffer, 'base64'),
468
463
  etag: typeof value.etag === 'string' ? value.etag : getHash([value.buffer]),
@@ -471,12 +466,12 @@ async function readCache(
471
466
  maxAge: revalidate,
472
467
  expireAt,
473
468
  isStale: expireAt < Date.now(),
474
- };
469
+ }
475
470
  }
476
- if (!diskCacheEnabled(config)) return undefined;
477
- const entry = await readCacheEntry(imageCacheDir(coreConfig.root), cacheKey);
478
- if (!entry) return undefined;
479
- return { ...entry, isStale: Date.now() > entry.expireAt };
471
+ if (!diskCacheEnabled(config)) return undefined
472
+ const entry = await readCacheEntry(imageCacheDir(coreConfig.root), cacheKey)
473
+ if (!entry) return undefined
474
+ return { ...entry, isStale: Date.now() > entry.expireAt }
480
475
  }
481
476
 
482
477
  // -- response ---------------------------------------------------------------
@@ -491,34 +486,34 @@ function buildResponse(
491
486
  maxAge: number,
492
487
  xCache: XCacheHeader,
493
488
  ): Response {
494
- const headers = new Headers();
495
- headers.set('Vary', 'Accept');
489
+ const headers = new Headers()
490
+ headers.set('Vary', 'Accept')
496
491
  headers.set(
497
492
  'Cache-Control',
498
493
  params.isStatic
499
494
  ? 'public, max-age=315360000, immutable'
500
495
  : `public, max-age=${maxAge}, must-revalidate`,
501
- );
502
- headers.set('ETag', etag);
496
+ )
497
+ headers.set('ETag', etag)
503
498
 
504
- const ifNoneMatch = request.headers.get('if-none-match');
499
+ const ifNoneMatch = request.headers.get('if-none-match')
505
500
  if (ifNoneMatch && (ifNoneMatch === etag || ifNoneMatch === `"${etag}"`)) {
506
- headers.set('X-Nextjs-Cache', xCache);
507
- return new Response(null, { status: 304, headers });
501
+ headers.set('X-Nextjs-Cache', xCache)
502
+ return new Response(null, { status: 304, headers })
508
503
  }
509
504
 
510
- headers.set('Content-Type', contentType);
511
- const fileName = getFileNameWithExtension(params.href, contentType);
505
+ headers.set('Content-Type', contentType)
506
+ const fileName = getFileNameWithExtension(params.href, contentType)
512
507
  headers.set(
513
508
  'Content-Disposition',
514
509
  contentDispositionHeader(config.contentDispositionType, fileName),
515
- );
516
- headers.set('Content-Security-Policy', config.contentSecurityPolicy);
517
- headers.set('X-Nextjs-Cache', xCache);
518
- headers.set('Content-Length', String(buffer.byteLength));
510
+ )
511
+ headers.set('Content-Security-Policy', config.contentSecurityPolicy)
512
+ headers.set('X-Nextjs-Cache', xCache)
513
+ headers.set('Content-Length', String(buffer.byteLength))
519
514
 
520
- if (request.method === 'HEAD') return new Response(null, { status: 200, headers });
521
- return new Response(new Uint8Array(buffer), { status: 200, headers });
515
+ if (request.method === 'HEAD') return new Response(null, { status: 200, headers })
516
+ return new Response(new Uint8Array(buffer), { status: 200, headers })
522
517
  }
523
518
 
524
519
  // -- entry point ------------------------------------------------------------
@@ -527,13 +522,13 @@ export async function serveImageOptimizer(
527
522
  request: Request,
528
523
  coreConfig: ResolvedConfig,
529
524
  ): Promise<Response> {
530
- const config = resolveOptimizerConfig();
531
- const url = new URL(request.url);
532
- const basePath = '';
525
+ const config = resolveOptimizerConfig()
526
+ const url = new URL(request.url)
527
+ const basePath = ''
533
528
 
534
- const paramsResult = validateParams(request, url.searchParams, config, basePath);
535
- if ('errorMessage' in paramsResult) return badRequest(paramsResult.errorMessage);
536
- const params = paramsResult;
529
+ const paramsResult = validateParams(request, url.searchParams, config, basePath)
530
+ if ('errorMessage' in paramsResult) return badRequest(paramsResult.errorMessage)
531
+ const params = paramsResult
537
532
 
538
533
  const cacheKey = getHash([
539
534
  IMAGE_CACHE_VERSION,
@@ -541,11 +536,11 @@ export async function serveImageOptimizer(
541
536
  params.width,
542
537
  params.quality,
543
538
  params.mimeType,
544
- ]);
545
- const origin = url.origin;
539
+ ])
540
+ const origin = url.origin
546
541
 
547
542
  try {
548
- const cached = await readCache(cacheKey, config, coreConfig);
543
+ const cached = await readCache(cacheKey, config, coreConfig)
549
544
  if (cached && !cached.isStale) {
550
545
  return buildResponse(
551
546
  request,
@@ -556,15 +551,15 @@ export async function serveImageOptimizer(
556
551
  cached.etag,
557
552
  cached.maxAge,
558
553
  'HIT',
559
- );
554
+ )
560
555
  }
561
556
  if (cached) {
562
557
  // Serve the stale bytes now, refresh in the background.
563
558
  if (!refreshing.has(cacheKey)) {
564
- refreshing.add(cacheKey);
559
+ refreshing.add(cacheKey)
565
560
  void generateAndStore(cacheKey, params, config, coreConfig, origin)
566
561
  .catch(() => undefined)
567
- .finally(() => refreshing.delete(cacheKey));
562
+ .finally(() => refreshing.delete(cacheKey))
568
563
  }
569
564
  return buildResponse(
570
565
  request,
@@ -575,16 +570,16 @@ export async function serveImageOptimizer(
575
570
  cached.etag,
576
571
  cached.maxAge,
577
572
  'STALE',
578
- );
573
+ )
579
574
  }
580
575
 
581
- let pending = inFlight.get(cacheKey);
576
+ let pending = inFlight.get(cacheKey)
582
577
  if (!pending) {
583
- pending = generateAndStore(cacheKey, params, config, coreConfig, origin);
584
- inFlight.set(cacheKey, pending);
585
- void pending.catch(() => undefined).finally(() => inFlight.delete(cacheKey));
578
+ pending = generateAndStore(cacheKey, params, config, coreConfig, origin)
579
+ inFlight.set(cacheKey, pending)
580
+ void pending.catch(() => undefined).finally(() => inFlight.delete(cacheKey))
586
581
  }
587
- const entry = await pending;
582
+ const entry = await pending
588
583
  return buildResponse(
589
584
  request,
590
585
  params,
@@ -594,8 +589,8 @@ export async function serveImageOptimizer(
594
589
  entry.etag,
595
590
  entry.maxAge,
596
591
  'MISS',
597
- );
592
+ )
598
593
  } catch (error) {
599
- return errorResponse(error);
594
+ return errorResponse(error)
600
595
  }
601
596
  }