@wular/pnext 0.0.3 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (392) hide show
  1. package/README.md +11 -11
  2. package/bin/pnext +1 -1
  3. package/config/lint/base.js +7 -7
  4. package/config/ts/base.json +2 -4
  5. package/config/ts/react.json +2 -6
  6. package/package.json +23 -2
  7. package/reference/compat.md +1 -1
  8. package/reference/config.md +2 -2
  9. package/reference/css.md +13 -9
  10. package/reference/dev.md +1 -1
  11. package/reference/metadata.md +7 -7
  12. package/reference/navigation.md +35 -28
  13. package/reference/performance.md +70 -70
  14. package/reference/rendering.md +17 -17
  15. package/reference/routing.md +17 -17
  16. package/reference/typegen.md +12 -8
  17. package/src/api/cache.ts +36 -37
  18. package/src/api/client-cache.ts +2 -2
  19. package/src/api/client-navigation.ts +113 -109
  20. package/src/api/dynamic.tsx +58 -55
  21. package/src/api/link.tsx +49 -52
  22. package/src/api/navigation.ts +59 -58
  23. package/src/api/server.ts +152 -144
  24. package/src/api/suspense.ts +4 -4
  25. package/src/cli/adapters/vercel-warm.ts +126 -121
  26. package/src/cli/adapters/vercel.ts +437 -443
  27. package/src/cli/analyze.ts +333 -144
  28. package/src/cli/{named-bin.ts → boot/named-bin.ts} +37 -37
  29. package/src/cli/{boot-trace.ts → boot/trace.ts} +10 -10
  30. package/src/cli/build.ts +976 -978
  31. package/src/cli/create.ts +71 -63
  32. package/src/cli/dev.ts +116 -116
  33. package/src/cli/index.ts +88 -85
  34. package/src/cli/migrate/package-json.ts +73 -71
  35. package/src/cli/migrate/report.ts +32 -33
  36. package/src/cli/migrate/{index.ts → run.ts} +48 -49
  37. package/src/cli/migrate/scan.ts +46 -46
  38. package/src/cli/migrate/spinner.ts +9 -9
  39. package/src/cli/migrate/tsconfig.ts +35 -35
  40. package/src/cli/{server-entry.ts → serve/entry.ts} +57 -57
  41. package/src/cli/{request-pipeline.ts → serve/pipeline.ts} +386 -394
  42. package/src/cli/{serve-ui.ts → serve/ui.ts} +47 -47
  43. package/src/cli/start.ts +65 -66
  44. package/src/{typegen.ts → cli/typegen.ts} +59 -59
  45. package/src/client/build.ts +794 -768
  46. package/src/client/chunk-fold.ts +245 -240
  47. package/src/client/{paths.ts → chunk-name.ts} +6 -6
  48. package/src/client/entry.ts +162 -147
  49. package/src/client/prebuilt.ts +231 -223
  50. package/src/client/profile.ts +29 -29
  51. package/src/client/react-compiler.ts +20 -15
  52. package/src/client/{compat-surface.ts → react-tier.ts} +64 -64
  53. package/src/client/reference-stub.ts +51 -51
  54. package/src/client/reference.ts +19 -19
  55. package/src/{api → client}/router/events.ts +17 -17
  56. package/src/{api → client}/router/history.ts +16 -16
  57. package/src/{api → client}/router/hub.ts +52 -53
  58. package/src/{api/router.ts → client/router/index.ts} +61 -60
  59. package/src/{api → client}/router/policies.ts +24 -24
  60. package/src/{api → client}/router/runtime.ts +1988 -1961
  61. package/src/{api → client}/router/types.ts +93 -98
  62. package/src/compat/actions/client-plugin.ts +42 -43
  63. package/src/compat/actions/client-stub.ts +14 -14
  64. package/src/compat/actions/{action-client.ts → client.ts} +194 -193
  65. package/src/compat/actions/config.ts +63 -64
  66. package/src/compat/actions/detect.ts +68 -68
  67. package/src/compat/actions/discovery.ts +105 -105
  68. package/src/compat/actions/{action-dispatch.ts → dispatch.ts} +277 -274
  69. package/src/compat/actions/early-submit.ts +1 -1
  70. package/src/compat/actions/endpoint.ts +198 -198
  71. package/src/compat/actions/extensions.ts +811 -0
  72. package/src/compat/actions/flight.ts +23 -23
  73. package/src/compat/actions/form-state.ts +34 -34
  74. package/src/compat/actions/hoist.ts +382 -236
  75. package/src/compat/actions/ids.ts +7 -12
  76. package/src/compat/actions/index.ts +8 -8
  77. package/src/compat/actions/instances.ts +46 -46
  78. package/src/compat/actions/origin.ts +47 -48
  79. package/src/compat/actions/protocol.ts +22 -22
  80. package/src/compat/actions/registry.ts +19 -19
  81. package/src/compat/{misc/action-return.ts → actions/return.ts} +60 -57
  82. package/src/compat/actions/rewrite.ts +125 -125
  83. package/src/compat/actions/{action-router.ts → router.ts} +10 -10
  84. package/src/compat/actions/serve.ts +132 -136
  85. package/src/compat/actions/server-tag.ts +4 -4
  86. package/src/compat/actions/{action-shared.ts → shared.ts} +34 -34
  87. package/src/compat/actions/unrecognized-error.ts +4 -4
  88. package/src/compat/{index.ts → aliases.ts} +112 -109
  89. package/src/compat/bundler/bun-externals.ts +18 -18
  90. package/src/compat/bundler/cjs-exports.ts +271 -223
  91. package/src/compat/bundler/config.ts +116 -111
  92. package/src/compat/bundler/externals.ts +12 -12
  93. package/src/compat/bundler/import-meta-url.ts +19 -19
  94. package/src/compat/bundler/modularize-imports.ts +36 -33
  95. package/src/compat/bundler/new-url-asset.ts +22 -24
  96. package/src/compat/bundler/optimize-package-imports.ts +111 -107
  97. package/src/compat/bundler/polyfill.ts +28 -28
  98. package/src/compat/bundler/react-compiler.ts +35 -29
  99. package/src/compat/bundler/react-profiler.tsx +11 -11
  100. package/src/compat/bundler/relay-transform.ts +48 -47
  101. package/src/compat/bundler/require-context.ts +119 -113
  102. package/src/compat/bundler/resolve-extensions.ts +19 -19
  103. package/src/compat/bundler/source-cache.ts +25 -25
  104. package/src/compat/bundler/static-imports.ts +7 -7
  105. package/src/compat/bundler/symlink-imports.ts +46 -46
  106. package/src/compat/bundler/tsconfig-paths.ts +13 -15
  107. package/src/compat/bundler/wasm.ts +58 -60
  108. package/src/compat/bundler/webpack-loaders.ts +254 -241
  109. package/src/compat/bundler/worker.ts +101 -104
  110. package/src/compat/cache/build-flags.ts +29 -29
  111. package/src/compat/cache/build-prerender-errors.ts +40 -44
  112. package/src/compat/cache/custom-handler.ts +60 -53
  113. package/src/compat/cache/fetch-patch.ts +240 -240
  114. package/src/compat/cache/handler.ts +27 -31
  115. package/src/compat/cache/modern-handler.ts +148 -126
  116. package/src/compat/cache/resume-data-cache.ts +47 -45
  117. package/src/compat/cache/revalidate.ts +233 -232
  118. package/src/compat/cache/runtime-error.ts +35 -35
  119. package/src/compat/cache/use-cache-transform.ts +442 -417
  120. package/src/compat/cache/use-cache.ts +650 -614
  121. package/src/compat/cache-control.ts +140 -142
  122. package/src/compat/client/base-path.ts +21 -20
  123. package/src/compat/client/css-order.ts +18 -18
  124. package/src/compat/client/errors/bare-boundary.ts +11 -11
  125. package/src/compat/client/errors/control-flow.ts +23 -23
  126. package/src/compat/client/errors/error-boundary.ts +61 -61
  127. package/src/compat/client/errors/global-error.ts +101 -91
  128. package/src/compat/client/errors/install.ts +71 -72
  129. package/src/compat/client/errors/lazy.ts +23 -23
  130. package/src/compat/client/errors/primitive-throw.ts +47 -45
  131. package/src/compat/client/errors/soft-refresh.ts +4 -4
  132. package/src/compat/client/link-status.ts +33 -33
  133. package/src/compat/client/{nav-compat-runtime.ts → nav-runtime.ts} +20 -20
  134. package/src/compat/client/{nav-compat.ts → nav.ts} +12 -13
  135. package/src/compat/client/navigation-scroll.ts +63 -63
  136. package/src/compat/client/optimistic-routing.ts +93 -88
  137. package/src/compat/client/prefetch-cache.ts +23 -24
  138. package/src/compat/client/route-announcer.ts +35 -35
  139. package/src/compat/client/segment-cache-policy.ts +20 -20
  140. package/src/compat/client/segment-cache.ts +309 -315
  141. package/src/compat/client/segment-prefetch.ts +127 -132
  142. package/src/compat/client/trailing-slash.ts +5 -4
  143. package/src/compat/css/chunking.ts +113 -116
  144. package/src/compat/css/inline-css.ts +21 -21
  145. package/src/compat/css/lightningcss.ts +37 -38
  146. package/src/compat/css/modules.ts +161 -175
  147. package/src/compat/css/nonce.ts +7 -7
  148. package/src/compat/css/sass-plugin.ts +18 -21
  149. package/src/compat/css/sass.ts +150 -152
  150. package/src/compat/css/styled-jsx-runtime.ts +27 -27
  151. package/src/compat/css/styled-jsx.ts +21 -21
  152. package/src/compat/edge-runtime.ts +27 -27
  153. package/src/compat/{adapter → export}/build-complete.ts +72 -75
  154. package/src/compat/export/client.ts +29 -31
  155. package/src/compat/export/{index.ts → emit.ts} +111 -110
  156. package/src/compat/export/standalone.ts +62 -54
  157. package/src/compat/image-optimizer/cache.ts +51 -49
  158. package/src/compat/image-optimizer/detect.ts +52 -52
  159. package/src/compat/image-optimizer/{index.ts → optimize.ts} +189 -194
  160. package/src/compat/image-optimizer/source.ts +77 -80
  161. package/src/compat/lifecycle/after-scope.ts +26 -26
  162. package/src/compat/lifecycle/after.ts +48 -45
  163. package/src/compat/lifecycle/error-funnel.ts +98 -102
  164. package/src/compat/lifecycle/error-serialize.ts +30 -26
  165. package/src/compat/lifecycle/error-ui.ts +67 -32
  166. package/src/compat/lifecycle/instrumentation-client.ts +36 -38
  167. package/src/compat/lifecycle/instrumentation.ts +85 -85
  168. package/src/compat/lifecycle/node-console.ts +11 -11
  169. package/src/compat/lifecycle/testmode.ts +160 -132
  170. package/src/compat/mdx/compile.ts +60 -57
  171. package/src/compat/mdx/plugin.ts +11 -11
  172. package/src/compat/mdx/{next-mdx-stub.ts → stub.ts} +5 -5
  173. package/src/compat/{metadata-route-artifacts.ts → metadata-artifacts.ts} +195 -191
  174. package/src/compat/metadata.ts +75 -81
  175. package/src/compat/next/cache.ts +68 -69
  176. package/src/compat/next/canonical-url.ts +9 -9
  177. package/src/compat/next/client-cache.ts +19 -19
  178. package/src/compat/next/client-navigation.ts +112 -116
  179. package/src/compat/next/client-only.ts +1 -1
  180. package/src/compat/next/client-script.tsx +99 -93
  181. package/src/compat/next/client-server.ts +10 -10
  182. package/src/compat/next/config-loader.ts +176 -173
  183. package/src/compat/next/config.ts +7 -7
  184. package/src/compat/next/constants.cjs +6 -6
  185. package/src/compat/next/constants.ts +6 -6
  186. package/src/compat/next/custom-server.ts +26 -24
  187. package/src/compat/next/dist/client/components/app-router-headers.ts +21 -21
  188. package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +3 -4
  189. package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -1
  190. package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -1
  191. package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -1
  192. package/src/compat/next/dynamic.tsx +21 -18
  193. package/src/compat/next/error.tsx +52 -52
  194. package/src/compat/next/font/cache.ts +74 -74
  195. package/src/compat/next/font/google.ts +2 -2
  196. package/src/compat/next/font/index.ts +1 -1
  197. package/src/compat/next/font/local.ts +3 -3
  198. package/src/compat/next/font/runtime-client.ts +17 -15
  199. package/src/compat/next/font/runtime.ts +443 -408
  200. package/src/compat/next/font/shared.ts +120 -108
  201. package/src/compat/next/form.tsx +63 -63
  202. package/src/compat/next/head.tsx +2 -2
  203. package/src/compat/next/headers.ts +103 -95
  204. package/src/compat/next/image/client.tsx +220 -0
  205. package/src/compat/next/image/config.ts +56 -58
  206. package/src/compat/next/image/optimizer.ts +40 -36
  207. package/src/compat/next/image/patterns.ts +37 -40
  208. package/src/compat/next/{image-props.ts → image/props.ts} +208 -202
  209. package/src/compat/next/image/shared.ts +65 -57
  210. package/src/compat/next/image/static-metadata.ts +98 -107
  211. package/src/compat/next/image/validate.ts +79 -88
  212. package/src/compat/next/image.tsx +19 -23
  213. package/src/compat/next/index.ts +1 -1
  214. package/src/compat/next/legacy-image.tsx +59 -60
  215. package/src/compat/next/{link-validation-transform.ts → link-transform.ts} +95 -96
  216. package/src/compat/next/link.tsx +158 -158
  217. package/src/compat/next/navigation.cjs +12 -3
  218. package/src/compat/next/navigation.ts +48 -50
  219. package/src/compat/next/offline.ts +27 -27
  220. package/src/compat/next/og.ts +121 -124
  221. package/src/compat/next/preferred-region.ts +13 -14
  222. package/src/compat/next/redirects.ts +58 -56
  223. package/src/compat/next/resource-hints.ts +73 -76
  224. package/src/compat/next/rewrites.ts +130 -133
  225. package/src/compat/next/root-params.ts +45 -45
  226. package/src/compat/next/{optimistic-route-state.ts → route-state.ts} +52 -52
  227. package/src/compat/next/router.cjs +4 -2
  228. package/src/compat/next/router.ts +58 -61
  229. package/src/compat/next/script.tsx +108 -108
  230. package/src/compat/next/server-only.ts +1 -1
  231. package/src/compat/next/server.ts +19 -19
  232. package/src/compat/next/svgr.ts +18 -17
  233. package/src/compat/next/telemetry.ts +24 -24
  234. package/src/compat/next/{image-usage.ts → usage.ts} +70 -39
  235. package/src/compat/next/user-agent.ts +53 -49
  236. package/src/compat/next/web-vitals.ts +22 -24
  237. package/src/compat/otel/api.ts +41 -41
  238. package/src/compat/otel/client-trace-metadata.ts +25 -27
  239. package/src/compat/otel/fetch-span.ts +29 -29
  240. package/src/compat/otel/tracer.ts +331 -331
  241. package/src/compat/pages/api.ts +456 -0
  242. package/src/compat/pages/client-plugin.ts +36 -36
  243. package/src/compat/pages/router-state.ts +34 -34
  244. package/src/compat/pages/{index.ts → router.ts} +130 -135
  245. package/src/compat/ppr/io.ts +12 -12
  246. package/src/compat/ppr/missing-root-params.ts +34 -38
  247. package/src/compat/ppr/root-params-scan.ts +66 -66
  248. package/src/compat/ppr/root-params-transform.ts +24 -26
  249. package/src/compat/ppr/root-params.ts +30 -30
  250. package/src/compat/ppr/segment-config-incompat.ts +6 -7
  251. package/src/compat/protocol.ts +71 -70
  252. package/src/compat/react/action-state.ts +47 -48
  253. package/src/compat/react/client-lite.ts +15 -15
  254. package/src/compat/react/client.ts +4 -4
  255. package/src/compat/react/compiler-runtime.ts +11 -11
  256. package/src/compat/react/dom-client.ts +44 -44
  257. package/src/compat/react/dom-react-server.ts +10 -16
  258. package/src/compat/react/dom-server.ts +10 -10
  259. package/src/compat/react/dom.ts +52 -52
  260. package/src/compat/react/hooks-extra.ts +34 -35
  261. package/src/compat/react/parity.ts +64 -61
  262. package/src/compat/react/preact.ts +81 -82
  263. package/src/compat/react/react-server.ts +28 -28
  264. package/src/compat/react/router-shim.ts +1 -1
  265. package/src/compat/react/server-component-use.ts +8 -8
  266. package/src/compat/react/server-inserted-html.ts +30 -31
  267. package/src/compat/react/server.ts +53 -55
  268. package/src/compat/react/use.ts +32 -32
  269. package/src/compat/react/view-transition.ts +20 -20
  270. package/src/compat/register/actions.ts +35 -824
  271. package/src/compat/register/boot.ts +41 -41
  272. package/src/compat/register/build-tier.ts +5 -5
  273. package/src/compat/register/build.ts +74 -70
  274. package/src/compat/register/bundler.ts +161 -156
  275. package/src/compat/register/cache.ts +20 -20
  276. package/src/compat/register/client-errors.ts +3 -3
  277. package/src/compat/register/config.ts +6 -6
  278. package/src/compat/register/css-extras.ts +30 -34
  279. package/src/compat/register/edge-runtime.ts +3 -3
  280. package/src/compat/register/errors.ts +10 -12
  281. package/src/compat/register/export.ts +16 -16
  282. package/src/compat/register/font.ts +11 -11
  283. package/src/compat/register/hooks.ts +2 -2
  284. package/src/compat/register/image.ts +40 -40
  285. package/src/compat/register/index.ts +59 -62
  286. package/src/compat/register/instrumentation-client.ts +10 -10
  287. package/src/compat/register/lifecycle.ts +25 -28
  288. package/src/compat/register/mdx.ts +10 -10
  289. package/src/compat/register/middleware.ts +226 -16
  290. package/src/compat/register/otel.ts +80 -88
  291. package/src/compat/register/pages-api.ts +10 -463
  292. package/src/compat/register/ppr.ts +16 -16
  293. package/src/compat/register/protocol.ts +17 -18
  294. package/src/compat/register/proxy.ts +49 -51
  295. package/src/compat/register/render.ts +79 -76
  296. package/src/compat/register/routing.ts +162 -159
  297. package/src/compat/register/segment.ts +24 -1887
  298. package/src/compat/register/static-image.ts +3 -3
  299. package/src/compat/register/{misc.ts → taint.ts} +9 -9
  300. package/src/compat/register/typed-routes.ts +14 -14
  301. package/src/compat/register/{usecache.ts → use-cache.ts} +39 -39
  302. package/src/compat/register/validation.ts +18 -18
  303. package/src/compat/segment/loading-boundary.ts +43 -45
  304. package/src/compat/segment/page-slot.ts +69 -69
  305. package/src/compat/segment/serve.ts +1884 -0
  306. package/src/compat/segment/tree.ts +113 -112
  307. package/src/compat/segment/vary-key.ts +38 -38
  308. package/src/compat/segment/vary-params.ts +138 -142
  309. package/src/compat/static-params.ts +14 -12
  310. package/src/compat/tsconfig-defaults.ts +87 -91
  311. package/src/compat/typecheck/{index.ts → check.ts} +234 -212
  312. package/src/compat/typecheck/worker.ts +15 -12
  313. package/src/compat/typed-routes/{index.ts → generate.ts} +36 -36
  314. package/src/compat/typed-routes/manifest.ts +174 -170
  315. package/src/compat/typed-routes/typegen.ts +127 -110
  316. package/src/compat/validation/errors.ts +16 -19
  317. package/src/compat/validation/prerender-diagnostics.ts +521 -504
  318. package/src/compat/validation/{index.ts → validate.ts} +647 -648
  319. package/src/compat-bootstrap.ts +16 -16
  320. package/src/config.ts +69 -69
  321. package/src/css/build.ts +230 -227
  322. package/src/css/postcss.ts +79 -80
  323. package/src/css/worker.ts +14 -15
  324. package/src/dev/client-actions.ts +10 -10
  325. package/src/dev/client-chunk-store.ts +27 -27
  326. package/src/dev/{client-key-cache.ts → restart/client-key.ts} +76 -76
  327. package/src/dev/{restart-cache.ts → restart/enabled.ts} +1 -1
  328. package/src/dev/{global-css-cache.ts → restart/global-css.ts} +91 -83
  329. package/src/dev/{node-module-bundle-cache.ts → restart/node-modules.ts} +24 -24
  330. package/src/dev/{route-bundle-key-cache.ts → restart/route-bundle-key.ts} +53 -53
  331. package/src/dev/{route-facts-cache.ts → restart/route-facts.ts} +82 -82
  332. package/src/dev/server.ts +804 -820
  333. package/src/env.ts +46 -43
  334. package/src/extensions.ts +491 -478
  335. package/src/index.ts +8 -8
  336. package/src/internal.ts +20 -23
  337. package/src/{islands → render}/boundary-error.ts +3 -3
  338. package/src/render/hooks.ts +71 -71
  339. package/src/render/island-context.ts +14 -14
  340. package/src/render/metadata.ts +310 -310
  341. package/src/{ppr-postpone.ts → render/postpone.ts} +5 -5
  342. package/src/{ppr.ts → render/ppr.ts} +244 -245
  343. package/src/render/renderer.ts +2076 -2082
  344. package/src/render/resource-hints.ts +16 -17
  345. package/src/render/slots.tsx +224 -235
  346. package/src/{islands → render}/static-children.ts +9 -12
  347. package/src/{islands → render}/static-slots.ts +37 -37
  348. package/src/{cache/context.ts → request/cache.ts} +20 -20
  349. package/src/request/context.ts +107 -107
  350. package/src/{dynamic/source.ts → resolve/dynamic.ts} +152 -143
  351. package/src/resolve/engine.ts +90 -77
  352. package/src/resolve/imports.ts +475 -463
  353. package/src/resolve/scan-facts.ts +442 -186
  354. package/src/resolve/source-text.ts +37 -37
  355. package/src/{dynamic → resolve}/tree-shake.ts +132 -128
  356. package/src/routing/forwarded.ts +19 -19
  357. package/src/routing/handler.ts +84 -91
  358. package/src/routing/href.ts +69 -70
  359. package/src/routing/{metadata.ts → metadata-files.ts} +403 -401
  360. package/src/{proxy.ts → routing/proxy.ts} +306 -312
  361. package/src/routing/{request-runtime.ts → request-environment.ts} +10 -10
  362. package/src/routing/routes.ts +833 -848
  363. package/src/routing/slots.ts +164 -160
  364. package/src/runtime/loader.ts +952 -0
  365. package/src/{dev → runtime}/module-cache.ts +309 -287
  366. package/src/{dev → runtime}/module-generations.ts +9 -9
  367. package/src/{dev → runtime}/module-transform.ts +81 -72
  368. package/src/{dev/imports.ts → runtime/modules.ts} +934 -847
  369. package/src/runtime/{server.ts → vendor-build.ts} +848 -1696
  370. package/src/runtime/vendor.ts +425 -404
  371. package/src/styles.d.ts +9 -0
  372. package/src/types.ts +320 -335
  373. package/src/utils/ansi.ts +5 -5
  374. package/src/utils/{source.ts → code.ts} +15 -12
  375. package/src/utils/content-type.ts +3 -3
  376. package/src/utils/decode.ts +2 -2
  377. package/src/utils/dev-profile.ts +13 -13
  378. package/src/utils/error-log.ts +6 -6
  379. package/src/utils/esbuild.ts +18 -18
  380. package/src/utils/fs-cache.ts +13 -13
  381. package/src/utils/fs.ts +57 -49
  382. package/src/utils/html.ts +20 -24
  383. package/src/utils/native-require.ts +8 -8
  384. package/src/utils/serialize.ts +139 -146
  385. package/src/utils/verbose.ts +18 -18
  386. package/src/cli/analyze-print.ts +0 -181
  387. package/src/compat/middleware/manifest.ts +0 -210
  388. package/src/compat/next/image-client.tsx +0 -215
  389. package/src/compat/next/link-usage.ts +0 -29
  390. package/src/css/index.ts +0 -2
  391. package/src/render/index.ts +0 -1
  392. package/src/style-modules.d.ts +0 -9
@@ -13,50 +13,50 @@
13
13
  // Lazy: the oxc-parser native binding costs ~12.6 MB RSS; load it only when a parse happens.
14
14
  const parseSync: typeof import('oxc-parser').parseSync = (...args) =>
15
15
  // eslint-disable-next-line @typescript-eslint/no-require-imports
16
- loadNative(() => require('oxc-parser') as typeof import('oxc-parser')).parseSync(...args);
17
- import { loadNative } from '../utils/native-require';
18
- import { spliceSource } from '../dev/module-transform';
16
+ loadNative(() => require('oxc-parser') as typeof import('oxc-parser')).parseSync(...args)
17
+ import { loadNative } from '../utils/native-require'
18
+ import { spliceSource } from '../runtime/module-transform'
19
19
 
20
20
  export interface ScanEdge {
21
- specifier: string;
21
+ specifier: string
22
22
  /** Bound names: `default`, `*` (namespace object), or the imported names. */
23
- exports: string[];
23
+ exports: string[]
24
24
  /** Byte offset of the statement — edges are consumed in true source order. */
25
- index: number;
25
+ index: number
26
26
  /** `export * from`: binds every named export of the target, none of them known here. */
27
- star?: boolean;
27
+ star?: boolean
28
28
  /** `export … from` rather than `import`. */
29
- reexport?: boolean;
29
+ reexport?: boolean
30
30
  }
31
31
 
32
32
  /** One module specifier literal, with the span of its quoted text. */
33
33
  export interface SpecifierEdge {
34
- specifier: string;
34
+ specifier: string
35
35
  /** Span of the quoted literal, quotes included. */
36
- start: number;
37
- end: number;
36
+ start: number
37
+ end: number
38
38
  /** `side-effect` is `import 'x'`: no clause, so no `from` for a textual pass to key on. */
39
- kind: 'import' | 'side-effect' | 'export' | 'dynamic' | 'require';
39
+ kind: 'import' | 'side-effect' | 'export' | 'dynamic' | 'require'
40
40
  }
41
41
 
42
42
  export interface ImportBinding {
43
43
  /** Name in the target module; `default`/`*` for the default/namespace forms. */
44
- imported: string;
45
- local: string;
44
+ imported: string
45
+ local: string
46
46
  /** `import type …` / `{ type X }`: erased, but part of the statement's text. */
47
- type: boolean;
47
+ type: boolean
48
48
  }
49
49
 
50
50
  /** A static `import` statement, spans included — the unit the folded passes splice. */
51
51
  export interface ImportStatement {
52
- specifier: string;
52
+ specifier: string
53
53
  /** Statement span (`import` … `;`). */
54
- start: number;
55
- end: number;
54
+ start: number
55
+ end: number
56
56
  /** Span of the quoted specifier literal inside the statement. */
57
- specifierStart: number;
58
- specifierEnd: number;
59
- bindings: ImportBinding[];
57
+ specifierStart: number
58
+ specifierEnd: number
59
+ bindings: ImportBinding[]
60
60
  }
61
61
 
62
62
  /**
@@ -65,32 +65,61 @@ export interface ImportStatement {
65
65
  * parse, so a folded pass on an already-scanned source costs nothing beyond the lookup.
66
66
  */
67
67
  export interface RewriteFacts {
68
- edges: SpecifierEdge[];
69
- imports: ImportStatement[];
70
- importMetas: { start: number; end: number }[];
68
+ edges: SpecifierEdge[]
69
+ imports: ImportStatement[]
70
+ importMetas: { start: number; end: number }[]
71
71
  /**
72
72
  * The parse recovered from errors, so the record may be incomplete — folded
73
73
  * passes fall back to their textual form rather than silently under-rewrite.
74
74
  */
75
- unreliable: boolean;
75
+ unreliable: boolean
76
+ }
77
+
78
+ /**
79
+ * A `name(loader, options?)` call whose loader statically targets one module —
80
+ * the shape `dynamic()` extraction needs. Emitted for every identifier-callee
81
+ * call with such a loader; consumers gate on their own `dynamic` binding set.
82
+ */
83
+ export interface DynamicCallFact {
84
+ name: string
85
+ /** Callee identifier start — aligns with the textual call scan's index. */
86
+ start: number
87
+ /** Call end (past the closing paren). */
88
+ end: number
89
+ /** Loader target, cooked value. */
90
+ specifier: string
91
+ /** Span of the quoted specifier literal, quotes included. */
92
+ specifierStart: number
93
+ specifierEnd: number
94
+ /** From the `.then(m => m.X)` arm (or awaited member); `default` otherwise. */
95
+ exportName: string
96
+ /** First arg is a string literal (`dynamic('./x')` form), not a loader fn. */
97
+ literal?: boolean
98
+ /** End of the loader argument — options/appended args follow it. */
99
+ loaderEnd: number
100
+ /** Second-argument span, when present. */
101
+ optionsStart?: number
102
+ optionsEnd?: number
76
103
  }
77
104
 
78
105
  export interface ScanFacts {
79
106
  /** Static value imports and `export … from` re-exports, in source order. */
80
- imports: ScanEdge[];
107
+ imports: ScanEdge[]
81
108
  /** `import('literal')` expressions, in source order. */
82
- dynamicImports: { specifier: string; index: number }[];
109
+ dynamicImports: { specifier: string; index: number }[]
83
110
  /** `require('literal')` calls — CJS sources still reach these scanners. */
84
- requires: { specifier: string; index: number; specifierStart: number }[];
111
+ requires: { specifier: string; index: number; specifierStart: number }[]
85
112
  /** This module's own value export names, `default` included. */
86
- exportNames: string[];
113
+ exportNames: string[]
87
114
  /** Specifiers of `export * from` (whose names live in the target). */
88
- exportStars: string[];
115
+ exportStars: string[]
89
116
  /** A real `'use client'` directive — prologue position only, never a comment. */
90
- useClient: boolean;
117
+ useClient: boolean
118
+ /** Loader-shaped calls (see `DynamicCallFact`); only sources that can bind `dynamic` are walked. */
119
+ dynamicCalls: DynamicCallFact[]
91
120
  }
92
121
 
93
- const stylesheetOrData = /\.(?:css|scss|sass|less|styl|json)$/;
122
+ const stylesheetOrData = /\.(?:css|scss|sass|less|styl|json)$/
94
123
 
95
124
  const emptyFacts: ScanFacts = {
96
125
  imports: [],
@@ -99,62 +128,72 @@ const emptyFacts: ScanFacts = {
99
128
  exportNames: [],
100
129
  exportStars: [],
101
130
  useClient: false,
102
- };
131
+ dynamicCalls: [],
132
+ }
103
133
 
104
134
  const emptyRewriteFacts: RewriteFacts = {
105
135
  edges: [],
106
136
  imports: [],
107
137
  importMetas: [],
108
138
  unreliable: false,
109
- };
139
+ }
110
140
 
111
- const memo = new Map<string, { scan: ScanFacts; rewrite: RewriteFacts }>();
112
- let hits = 0;
113
- let misses = 0;
141
+ const memo = new Map<string, { scan: ScanFacts; rewrite: RewriteFacts }>()
142
+ let hits = 0
143
+ let misses = 0
114
144
 
115
145
  export function scanFacts(file: string, source: string): ScanFacts {
116
146
  // The module walk reaches stylesheets/data as ordinary edges; they hold no
117
147
  // module facts and are not parseable as script.
118
- if (stylesheetOrData.test(file)) return emptyFacts;
119
- return parsed(file, source).scan;
148
+ if (stylesheetOrData.test(file)) return emptyFacts
149
+ return parsed(file, source).scan
120
150
  }
121
151
 
122
152
  /** Every specifier a module pulls in — static, dynamic and `require`, in source order. */
123
153
  export function importSpecifiers(source: string, sourcefile = 'source') {
124
- const facts = scanFacts(sourcefile, source);
154
+ const facts = scanFacts(sourcefile, source)
125
155
  return [
126
156
  ...facts.imports.map(edge => edge.specifier),
127
157
  ...facts.dynamicImports.map(entry => entry.specifier),
128
158
  ...facts.requires.map(entry => entry.specifier),
129
- ];
159
+ ]
130
160
  }
131
161
 
132
162
  export function moduleExportNames(source: string, sourcefile = 'source.tsx') {
133
- return scanFacts(sourcefile, source).exportNames;
163
+ return scanFacts(sourcefile, source).exportNames
134
164
  }
135
165
 
136
166
  /** Specifiers of `export * from` — their names live in the target module. */
137
167
  export function moduleExportStars(source: string, sourcefile = 'source.tsx') {
138
- return scanFacts(sourcefile, source).exportStars;
168
+ return scanFacts(sourcefile, source).exportStars
169
+ }
170
+
171
+ /**
172
+ * AST-derived `dynamic()` loader facts — the single source of truth. A call
173
+ * without a fact is an unanalyzable loader: no detection, nothing to splice.
174
+ */
175
+ export function dynamicCallFacts(source: string, file = 'module.tsx'): DynamicCallFact[] {
176
+ if (stylesheetOrData.test(file)) return []
177
+ return parsed(file, source).scan.dynamicCalls
139
178
  }
140
179
 
141
180
  /** Module-record spans for the rewrite passes (see `RewriteFacts`). */
142
181
  export function rewriteFacts(file: string, source: string): RewriteFacts {
143
- if (stylesheetOrData.test(file)) return emptyRewriteFacts;
144
- return parsed(file, source).rewrite;
182
+ if (stylesheetOrData.test(file)) return emptyRewriteFacts
183
+ return parsed(file, source).rewrite
145
184
  }
146
185
 
147
186
  function parsed(file: string, source: string, lang = langForFile(file)) {
148
- const key = `${lang}\0${source.length}\0${Bun.hash(source).toString()}`;
149
- const cached = memo.get(key);
187
+ const key = `${lang}\0${source.length}\0${Bun.hash(source).toString()}`
188
+ const cached = memo.get(key)
150
189
  if (cached) {
151
- hits += 1;
152
- return cached;
190
+ hits += 1
191
+ return cached
153
192
  }
154
- misses += 1;
155
- const facts = parseFacts(file, source, lang);
156
- memo.set(key, facts);
157
- return facts;
193
+ misses += 1
194
+ const facts = parseFacts(file, source, lang)
195
+ memo.set(key, facts)
196
+ return facts
158
197
  }
159
198
 
160
199
  /**
@@ -168,68 +207,76 @@ export function rewriteSpecifierLiterals(
168
207
  file: string,
169
208
  replace: (specifier: string, kind: SpecifierEdge['kind']) => string | undefined,
170
209
  ): string {
171
- const facts = rewriteFacts(file, source);
210
+ const facts = rewriteFacts(file, source)
172
211
  // A partially recovered parse has a partial record; the textual scan the
173
212
  // folded passes replaced is the safe answer there.
174
- const edges = facts.unreliable ? textualEdges(source) : facts.edges;
175
- const edits: { start: number; end: number; value: string }[] = [];
213
+ const edges = facts.unreliable ? textualEdges(source) : facts.edges
214
+ const edits: { start: number; end: number; value: string }[] = []
176
215
  for (const edge of edges) {
177
- const value = replace(edge.specifier, edge.kind);
178
- if (value !== undefined) edits.push({ start: edge.start, end: edge.end, value });
216
+ const value = replace(edge.specifier, edge.kind)
217
+ if (value !== undefined) edits.push({ start: edge.start, end: edge.end, value })
179
218
  }
180
- return edits.length > 0 ? spliceSource(source, edits) : source;
219
+ return edits.length > 0 ? spliceSource(source, edits) : source
181
220
  }
182
221
 
183
- const textualSpecifier = /(\bfrom\s*|\b(?:import|require)\s*\(\s*)(['"])([^'"]+)\2/g;
222
+ const textualSpecifier = /(\bfrom\s*|\b(?:import|require)\s*\(\s*)(['"])([^'"]+)\2/g
184
223
 
185
224
  function textualEdges(source: string): SpecifierEdge[] {
186
- const edges: SpecifierEdge[] = [];
187
- textualSpecifier.lastIndex = 0;
188
- let match: RegExpExecArray | null;
225
+ const edges: SpecifierEdge[] = []
226
+ textualSpecifier.lastIndex = 0
227
+ let match: RegExpExecArray | null
189
228
  while ((match = textualSpecifier.exec(source))) {
190
- const start = match.index + match[1]!.length;
229
+ const start = match.index + match[1]!.length
191
230
  edges.push({
192
231
  specifier: match[3]!,
193
232
  start,
194
233
  end: start + match[3]!.length + 2,
195
- kind: match[1]!.includes('require') ? 'require' : match[1]!.includes('import') ? 'dynamic' : 'import',
196
- });
234
+ kind: match[1]!.includes('require')
235
+ ? 'require'
236
+ : match[1]!.includes('import')
237
+ ? 'dynamic'
238
+ : 'import',
239
+ })
197
240
  }
198
- return edges;
241
+ return edges
199
242
  }
200
243
 
201
244
  /** Memo effectiveness, for the dev-boot benches. */
202
245
  export function scanFactsStats() {
203
- return { hits, misses, sources: memo.size };
246
+ return { hits, misses, sources: memo.size }
204
247
  }
205
248
 
206
249
  function parseFacts(file: string, source: string, lang: ParserLang) {
207
250
  // Parse errors are recovered, never thrown: a scan of a half-typed file in
208
251
  // the dev watcher must still yield whatever the parser did understand.
209
- const result = parseSync(file, source, { lang });
210
- const imports: ScanEdge[] = [];
211
- const exportNames: string[] = [];
212
- const exportStars: string[] = [];
213
- const edges: SpecifierEdge[] = [];
214
- const importStatements: ImportStatement[] = [];
252
+ const result = parseSync(file, source, { lang })
253
+ const imports: ScanEdge[] = []
254
+ const exportNames: string[] = []
255
+ const exportStars: string[] = []
256
+ const edges: SpecifierEdge[] = []
257
+ const importStatements: ImportStatement[] = []
215
258
 
216
259
  for (const statement of result.module.staticImports) {
217
- const names: string[] = [];
218
- const bindings: ImportBinding[] = [];
219
- let bindsValue = statement.entries.length === 0;
260
+ const names: string[] = []
261
+ const bindings: ImportBinding[] = []
262
+ let bindsValue = statement.entries.length === 0
220
263
  for (const entry of statement.entries) {
221
- const kind: string = entry.importName.kind;
264
+ const kind: string = entry.importName.kind
222
265
  bindings.push({
223
266
  imported:
224
- kind === 'Default' ? 'default' : kind === 'NamespaceObject' ? '*' : (entry.importName.name ?? ''),
267
+ kind === 'Default'
268
+ ? 'default'
269
+ : kind === 'NamespaceObject'
270
+ ? '*'
271
+ : (entry.importName.name ?? ''),
225
272
  local: entry.localName.value ?? '',
226
273
  type: entry.isType,
227
- });
228
- if (entry.isType) continue;
229
- bindsValue = true;
230
- if (kind === 'Default') names.push('default');
231
- else if (kind === 'NamespaceObject') names.push('*');
232
- else if (entry.importName.name) names.push(entry.importName.name);
274
+ })
275
+ if (entry.isType) continue
276
+ bindsValue = true
277
+ if (kind === 'Default') names.push('default')
278
+ else if (kind === 'NamespaceObject') names.push('*')
279
+ else if (entry.importName.name) names.push(entry.importName.name)
233
280
  }
234
281
  importStatements.push({
235
282
  specifier: statement.moduleRequest.value,
@@ -238,86 +285,91 @@ function parseFacts(file: string, source: string, lang: ParserLang) {
238
285
  specifierStart: statement.moduleRequest.start,
239
286
  specifierEnd: statement.moduleRequest.end,
240
287
  bindings,
241
- });
288
+ })
242
289
  edges.push({
243
290
  specifier: statement.moduleRequest.value,
244
291
  start: statement.moduleRequest.start,
245
292
  end: statement.moduleRequest.end,
246
293
  kind: statement.entries.length === 0 ? 'side-effect' : 'import',
247
- });
294
+ })
248
295
  // `import type { … }` binds nothing at runtime: not an edge.
249
- if (!bindsValue) continue;
296
+ if (!bindsValue) continue
250
297
  imports.push({
251
298
  specifier: statement.moduleRequest.value,
252
299
  exports: names,
253
300
  index: statement.start,
254
- });
301
+ })
255
302
  }
256
303
 
257
304
  // One `export { a, b } from 'x'` reports the same moduleRequest per entry.
258
- const seenExportRequests = new Set<number>();
305
+ const seenExportRequests = new Set<number>()
259
306
  for (const statement of result.module.staticExports) {
260
307
  for (const entry of statement.entries) {
261
- const request = entry.moduleRequest;
262
- if (!request || seenExportRequests.has(request.start)) continue;
263
- seenExportRequests.add(request.start);
264
- edges.push({ specifier: request.value, start: request.start, end: request.end, kind: 'export' });
308
+ const request = entry.moduleRequest
309
+ if (!request || seenExportRequests.has(request.start)) continue
310
+ seenExportRequests.add(request.start)
311
+ edges.push({
312
+ specifier: request.value,
313
+ start: request.start,
314
+ end: request.end,
315
+ kind: 'export',
316
+ })
265
317
  }
266
318
  }
267
319
 
268
320
  for (const statement of result.module.staticExports) {
269
- const edges = new Map<string, ScanEdge>();
321
+ const edges = new Map<string, ScanEdge>()
270
322
  for (const entry of statement.entries) {
271
- if (entry.isType) continue;
272
- const exportKind: string = entry.exportName.kind;
273
- if (exportKind === 'Default') exportNames.push('default');
274
- else if (entry.exportName.name) exportNames.push(entry.exportName.name);
275
-
276
- const specifier = entry.moduleRequest?.value;
277
- if (!specifier) continue;
278
- const importKind: string = entry.importName.kind;
279
- const star = importKind === 'AllButDefault';
280
- if (star) exportStars.push(specifier);
281
- let edge = edges.get(specifier);
323
+ if (entry.isType) continue
324
+ const exportKind: string = entry.exportName.kind
325
+ if (exportKind === 'Default') exportNames.push('default')
326
+ else if (entry.exportName.name) exportNames.push(entry.exportName.name)
327
+
328
+ const specifier = entry.moduleRequest?.value
329
+ if (!specifier) continue
330
+ const importKind: string = entry.importName.kind
331
+ const star = importKind === 'AllButDefault'
332
+ if (star) exportStars.push(specifier)
333
+ let edge = edges.get(specifier)
282
334
  if (!edge) {
283
- edge = { specifier, exports: [], index: statement.start, reexport: true };
284
- edges.set(specifier, edge);
285
- imports.push(edge);
335
+ edge = { specifier, exports: [], index: statement.start, reexport: true }
336
+ edges.set(specifier, edge)
337
+ imports.push(edge)
286
338
  }
287
- if (star) edge.star = true;
288
- else if (importKind === 'All') edge.exports.push('*');
289
- else if (entry.importName.name) edge.exports.push(entry.importName.name);
339
+ if (star) edge.star = true
340
+ else if (importKind === 'All') edge.exports.push('*')
341
+ else if (entry.importName.name) edge.exports.push(entry.importName.name)
290
342
  }
291
343
  }
292
344
 
293
- imports.sort((a, b) => a.index - b.index);
345
+ imports.sort((a, b) => a.index - b.index)
294
346
 
295
- const dynamicImports: { specifier: string; index: number }[] = [];
347
+ const dynamicImports: { specifier: string; index: number }[] = []
296
348
  for (const entry of result.module.dynamicImports) {
297
- const raw = source.slice(entry.moduleRequest.start, entry.moduleRequest.end);
298
- const specifier = literalSpecifier(raw);
299
- if (specifier === undefined) continue;
300
- dynamicImports.push({ specifier, index: entry.start });
349
+ const raw = source.slice(entry.moduleRequest.start, entry.moduleRequest.end)
350
+ const specifier = literalSpecifier(raw)
351
+ if (specifier === undefined) continue
352
+ dynamicImports.push({ specifier, index: entry.start })
301
353
  // The literal's own span, without the whitespace `moduleRequest` includes.
302
- const lead = raw.length - raw.trimStart().length;
354
+ const lead = raw.length - raw.trimStart().length
303
355
  edges.push({
304
356
  specifier,
305
357
  start: entry.moduleRequest.start + lead,
306
358
  end: entry.moduleRequest.start + lead + specifier.length + 2,
307
359
  kind: 'dynamic',
308
- });
360
+ })
309
361
  }
310
362
 
311
- const requires = requireCalls(source, result.comments);
363
+ const requires = requireCalls(source, result.comments)
312
364
  for (const call of requires) {
313
365
  edges.push({
314
366
  specifier: call.specifier,
315
367
  start: call.specifierStart,
316
368
  end: call.specifierStart + call.specifier.length + 2,
317
369
  kind: 'require',
318
- });
370
+ })
319
371
  }
320
- edges.sort((a, b) => a.start - b.start);
372
+ edges.sort((a, b) => a.start - b.start)
321
373
 
322
374
  return {
323
375
  scan: {
@@ -327,6 +379,12 @@ function parseFacts(file: string, source: string, lang: ParserLang) {
327
379
  exportNames,
328
380
  exportStars,
329
381
  useClient: hasUseClient(source, result),
382
+ // `result.program` deserializes the whole AST: walk it only for sources
383
+ // that can bind `dynamic()` at all (same cheap gate as resolve/dynamic.ts).
384
+ dynamicCalls:
385
+ source.includes('@wular/pnext') || source.includes('next/dynamic')
386
+ ? collectDynamicCalls(result.program as unknown as AstNode)
387
+ : [],
330
388
  },
331
389
  rewrite: {
332
390
  edges,
@@ -334,7 +392,7 @@ function parseFacts(file: string, source: string, lang: ParserLang) {
334
392
  importMetas: result.module.importMetas.map(meta => ({ start: meta.start, end: meta.end })),
335
393
  unreliable: result.errors.length > 0,
336
394
  },
337
- };
395
+ }
338
396
  }
339
397
 
340
398
  /**
@@ -343,23 +401,23 @@ function parseFacts(file: string, source: string, lang: ParserLang) {
343
401
  * consulted for the prologue shapes the scanner will not commit to.
344
402
  */
345
403
  function hasUseClient(source: string, result: { program: { body: readonly Directive[] } }) {
346
- const sniffed = sniffDirective(source, 'use client');
347
- if (sniffed !== undefined) return sniffed;
404
+ const sniffed = sniffDirective(source, 'use client')
405
+ if (sniffed !== undefined) return sniffed
348
406
  // Uncertain, but a directive is its raw text: no occurrence, no directive.
349
- if (!source.includes('use client')) return false;
350
- return hasDirective(result.program.body, 'use client');
407
+ if (!source.includes('use client')) return false
408
+ return hasDirective(result.program.body, 'use client')
351
409
  }
352
410
 
353
411
  interface Directive {
354
- type: string;
355
- directive?: string | null;
412
+ type: string
413
+ directive?: string | null
356
414
  }
357
415
 
358
416
  // A string literal is only a directive if what follows it ends the statement; a
359
417
  // continuation token means it was an expression (`'use client' + x`).
360
- const continuesExpression = /[+\-*/%.,?:=<>!&|^~([`)\]}]/;
361
- const wordContinuation = /^(?:in|instanceof)\b/;
362
- const lineTerminator = /[\n\r\u2028\u2029]/;
418
+ const continuesExpression = /[+\-*/%.,?:=<>!&|^~([`)\]}]/
419
+ const wordContinuation = /^(?:in|instanceof)\b/
420
+ const lineTerminator = /[\n\r\u2028\u2029]/
363
421
 
364
422
  /**
365
423
  * Directive-prologue sniff over raw source. Returns undefined - not a guess - for any shape it cannot decide
@@ -367,77 +425,82 @@ const lineTerminator = /[\n\r\u2028\u2029]/;
367
425
  * AST.
368
426
  */
369
427
  function sniffDirective(source: string, directive: string): boolean | undefined {
370
- let index = 0;
428
+ let index = 0
371
429
  if (source.startsWith('#!')) {
372
- const nl = source.search(lineTerminator);
373
- if (nl < 0) return false;
374
- index = nl + 1;
430
+ const nl = source.search(lineTerminator)
431
+ if (nl < 0) return false
432
+ index = nl + 1
375
433
  }
376
434
  for (;;) {
377
- index = skipTrivia(source, index);
378
- if (index < 0) return undefined;
379
- const quote = source[index];
380
- if (quote !== '"' && quote !== "'") return false; // prologue is over
435
+ index = skipTrivia(source, index)
436
+ if (index < 0) return undefined
437
+ const quote = source[index]
438
+ if (quote !== '"' && quote !== "'") return false // prologue is over
381
439
 
382
- let end = index + 1;
440
+ let end = index + 1
383
441
  while (end < source.length) {
384
- const char = source[end]!;
442
+ const char = source[end]!
385
443
  // An escape makes the raw text differ from the directive, but decoding it
386
444
  // exactly is the AST's job, not this scanner's.
387
- if (char === '\\' || lineTerminator.test(char)) return undefined;
388
- if (char === quote) break;
389
- end += 1;
445
+ if (char === '\\' || lineTerminator.test(char)) return undefined
446
+ if (char === quote) break
447
+ end += 1
390
448
  }
391
- if (end >= source.length) return undefined; // unterminated literal
392
- const raw = source.slice(index + 1, end);
449
+ if (end >= source.length) return undefined // unterminated literal
450
+ const raw = source.slice(index + 1, end)
393
451
 
394
- const after = skipTrivia(source, end + 1);
395
- if (after < 0) return undefined;
396
- const next = source[after];
397
- let terminated = after >= source.length || next === ';';
452
+ const after = skipTrivia(source, end + 1)
453
+ if (after < 0) return undefined
454
+ const next = source[after]
455
+ let terminated = after >= source.length || next === ';'
398
456
  if (!terminated && lineTerminator.test(source.slice(end + 1, after))) {
399
457
  // ASI closes the statement unless the next token continues the expression.
400
- terminated = !continuesExpression.test(next!) && !wordContinuation.test(source.slice(after, after + 11));
458
+ terminated =
459
+ !continuesExpression.test(next!) && !wordContinuation.test(source.slice(after, after + 11))
401
460
  }
402
- if (!terminated) return undefined;
403
- if (raw === directive) return true;
404
- index = next === ';' ? after + 1 : after;
405
- if (index >= source.length) return false;
461
+ if (!terminated) return undefined
462
+ if (raw === directive) return true
463
+ index = next === ';' ? after + 1 : after
464
+ if (index >= source.length) return false
406
465
  }
407
466
  }
408
467
 
409
468
  /** Past whitespace and comments; -1 for an unterminated block comment. */
410
469
  function skipTrivia(source: string, index: number) {
411
470
  for (;;) {
412
- while (index < source.length && /\s/.test(source[index]!)) index += 1;
413
- if (source[index] !== '/') return index;
471
+ while (index < source.length && /\s/.test(source[index]!)) index += 1
472
+ if (source[index] !== '/') return index
414
473
  if (source[index + 1] === '/') {
415
- let end = index + 2;
416
- while (end < source.length && !lineTerminator.test(source[end]!)) end += 1;
417
- index = end;
418
- continue;
474
+ let end = index + 2
475
+ while (end < source.length && !lineTerminator.test(source[end]!)) end += 1
476
+ index = end
477
+ continue
419
478
  }
420
479
  if (source[index + 1] === '*') {
421
- const end = source.indexOf('*/', index + 2);
422
- if (end < 0) return -1;
423
- index = end + 2;
424
- continue;
480
+ const end = source.indexOf('*/', index + 2)
481
+ if (end < 0) return -1
482
+ index = end + 2
483
+ continue
425
484
  }
426
- return index;
485
+ return index
427
486
  }
428
487
  }
429
488
 
430
- function hasDirective(body: readonly { type: string; directive?: string | null }[], directive: string) {
489
+ function hasDirective(
490
+ body: readonly { type: string; directive?: string | null }[],
491
+ directive: string,
492
+ ) {
431
493
  // The prologue ends at the first non-directive statement; comments and other
432
494
  // directives before it do not.
433
495
  for (const statement of body) {
434
- if (statement.type !== 'ExpressionStatement' || typeof statement.directive !== 'string') return false;
435
- if (statement.directive === directive) return true;
496
+ if (statement.type !== 'ExpressionStatement' || typeof statement.directive !== 'string')
497
+ return false
498
+ if (statement.directive === directive) return true
436
499
  }
437
- return false;
500
+ return false
438
501
  }
439
502
 
440
- const requirePattern = /\brequire\s*\(\s*(['"])([^'"]+)\1\s*\)/g;
503
+ const requirePattern = /\brequire\s*\(\s*(['"])([^'"]+)\1\s*\)/g
441
504
 
442
505
  /**
443
506
  * CJS `require` is an ordinary call, not a module record entry, so it stays on a regex - but a regex reads
@@ -446,33 +509,226 @@ const requirePattern = /\brequire\s*\(\s*(['"])([^'"]+)\1\s*\)/g;
446
509
  * that lands inside one.
447
510
  */
448
511
  function requireCalls(source: string, comments: readonly { start: number; end: number }[]) {
449
- const calls: { specifier: string; index: number; specifierStart: number }[] = [];
450
- if (!source.includes('require')) return calls;
451
- requirePattern.lastIndex = 0;
452
- let match: RegExpExecArray | null;
512
+ const calls: { specifier: string; index: number; specifierStart: number }[] = []
513
+ if (!source.includes('require')) return calls
514
+ requirePattern.lastIndex = 0
515
+ let match: RegExpExecArray | null
453
516
  // Both comments and matches ascend, so one shared cursor walks them together.
454
- let cursor = 0;
517
+ let cursor = 0
455
518
  while ((match = requirePattern.exec(source))) {
456
- const index = match.index;
457
- while (cursor < comments.length && comments[cursor]!.end <= index) cursor += 1;
458
- if (cursor < comments.length && index >= comments[cursor]!.start) continue;
519
+ const index = match.index
520
+ while (cursor < comments.length && comments[cursor]!.end <= index) cursor += 1
521
+ if (cursor < comments.length && index >= comments[cursor]!.start) continue
459
522
  calls.push({
460
523
  specifier: match[2]!,
461
524
  index,
462
525
  specifierStart: index + match[0].indexOf(match[1]!),
463
- });
526
+ })
464
527
  }
465
- return calls;
528
+ return calls
466
529
  }
467
530
 
468
531
  function literalSpecifier(raw: string) {
469
- return /^\s*(['"])([^'"]*)\1\s*$/.exec(raw)?.[2] || undefined;
532
+ return /^\s*(['"])([^'"]*)\1\s*$/.exec(raw)?.[2] || undefined
533
+ }
534
+
535
+ interface AstNode {
536
+ type: string
537
+ start: number
538
+ end: number
539
+ [key: string]: unknown
540
+ }
541
+
542
+ function isNode(value: unknown): value is AstNode {
543
+ return typeof value === 'object' && value !== null && typeof (value as AstNode).type === 'string'
544
+ }
545
+
546
+ function child(node: AstNode | undefined, key: string): AstNode | undefined {
547
+ const value = node?.[key]
548
+ return isNode(value) ? value : undefined
549
+ }
550
+
551
+ function collectDynamicCalls(program: AstNode): DynamicCallFact[] {
552
+ const calls: DynamicCallFact[] = []
553
+ const visit = (value: unknown) => {
554
+ if (Array.isArray(value)) {
555
+ for (const item of value) visit(item)
556
+ return
557
+ }
558
+ if (!isNode(value)) return
559
+ if (value.type === 'CallExpression') {
560
+ const fact = dynamicCallFact(value)
561
+ if (fact) calls.push(fact)
562
+ }
563
+ for (const key in value) {
564
+ if (key === 'type' || key === 'loc' || key === 'range') continue
565
+ const entry = value[key]
566
+ if (entry && typeof entry === 'object') visit(entry)
567
+ }
568
+ }
569
+ visit(program.body)
570
+ return calls.sort((a, b) => a.start - b.start)
571
+ }
572
+
573
+ function dynamicCallFact(call: AstNode): DynamicCallFact | undefined {
574
+ const callee = child(call, 'callee')
575
+ if (callee?.type !== 'Identifier') return undefined
576
+ const args = call.arguments
577
+ if (!Array.isArray(args) || !isNode(args[0])) return undefined
578
+ const loader = args[0]
579
+
580
+ let target: { specifier: AstNode; exportName: string } | undefined
581
+ let literal: boolean | undefined
582
+ if (stringValue(loader) !== undefined) {
583
+ target = { specifier: loader, exportName: 'default' }
584
+ literal = true
585
+ } else if (loader.type === 'ArrowFunctionExpression' || loader.type === 'FunctionExpression') {
586
+ target = loaderImportTarget(loader) ?? firstImportTarget(loader)
587
+ }
588
+ if (!target) return undefined
589
+
590
+ const specifier = stringValue(target.specifier)
591
+ if (specifier === undefined) return undefined
592
+ const options = isNode(args[1]) ? args[1] : undefined
593
+ return {
594
+ name: callee.name as string,
595
+ start: callee.start,
596
+ end: call.end,
597
+ specifier,
598
+ specifierStart: target.specifier.start,
599
+ specifierEnd: target.specifier.end,
600
+ exportName: target.exportName,
601
+ ...(literal ? { literal } : {}),
602
+ loaderEnd: loader.end,
603
+ ...(options ? { optionsStart: options.start, optionsEnd: options.end } : {}),
604
+ }
605
+ }
606
+
607
+ function stringValue(node: AstNode | undefined): string | undefined {
608
+ if (!node) return undefined
609
+ if (node.type !== 'Literal' && node.type !== 'StringLiteral') return undefined
610
+ return typeof node.value === 'string' ? node.value : undefined
611
+ }
612
+
613
+ /** Past parens, `await`, `as`/`!` and optional-chain wrappers to the loader's real expression. */
614
+ function unwrapExpression(node: AstNode | undefined): AstNode | undefined {
615
+ let current = node
616
+ for (;;) {
617
+ if (!current) return undefined
618
+ if (current.type === 'ParenthesizedExpression' || current.type === 'ChainExpression') {
619
+ current = child(current, 'expression')
620
+ } else if (current.type === 'AwaitExpression') {
621
+ current = child(current, 'argument')
622
+ } else if (
623
+ current.type === 'TSAsExpression' ||
624
+ current.type === 'TSNonNullExpression' ||
625
+ current.type === 'TSSatisfiesExpression'
626
+ ) {
627
+ current = child(current, 'expression')
628
+ } else {
629
+ return current
630
+ }
631
+ }
632
+ }
633
+
634
+ /** The expression a loader function resolves to: its expression body or first `return`. */
635
+ function loaderResult(fn: AstNode): AstNode | undefined {
636
+ const body = child(fn, 'body')
637
+ if (!body) return undefined
638
+ if (body.type !== 'BlockStatement' && body.type !== 'FunctionBody') return unwrapExpression(body)
639
+ const statements = body.body
640
+ if (!Array.isArray(statements)) return undefined
641
+ for (const statement of statements) {
642
+ if (isNode(statement) && statement.type === 'ReturnStatement') {
643
+ return unwrapExpression(child(statement, 'argument'))
644
+ }
645
+ }
646
+ return undefined
647
+ }
648
+
649
+ function importSource(node: AstNode | undefined): AstNode | undefined {
650
+ if (node?.type !== 'ImportExpression') return undefined
651
+ const source = child(node, 'source')
652
+ return source && stringValue(source) !== undefined ? source : undefined
653
+ }
654
+
655
+ /**
656
+ * Statically resolve a loader body: `import('x')`, `import('x').then(m => m.X)`
657
+ * (any param/arrow formatting), or `(await import('x')).X`.
658
+ */
659
+ function loaderImportTarget(fn: AstNode): { specifier: AstNode; exportName: string } | undefined {
660
+ const result = loaderResult(fn)
661
+ if (!result) return undefined
662
+
663
+ const direct = importSource(result)
664
+ if (direct) return { specifier: direct, exportName: 'default' }
665
+
666
+ if (result.type === 'MemberExpression' && result.computed !== true) {
667
+ const source = importSource(unwrapExpression(child(result, 'object')))
668
+ const property = child(result, 'property')
669
+ if (source && property?.type === 'Identifier') {
670
+ return { specifier: source, exportName: property.name as string }
671
+ }
672
+ }
673
+
674
+ if (result.type !== 'CallExpression') return undefined
675
+ const callee = child(result, 'callee')
676
+ if (callee?.type !== 'MemberExpression' || callee.computed === true) return undefined
677
+ if (child(callee, 'property')?.name !== 'then') return undefined
678
+ const source = importSource(unwrapExpression(child(callee, 'object')))
679
+ if (!source) return undefined
680
+ return { specifier: source, exportName: thenArmExport(result) ?? 'default' }
681
+ }
682
+
683
+ /** `X` of a `.then(m => m.X)` arm; undefined for any other arm shape. */
684
+ function thenArmExport(thenCall: AstNode): string | undefined {
685
+ const args = thenCall.arguments
686
+ if (!Array.isArray(args) || !isNode(args[0])) return undefined
687
+ const arm = args[0]
688
+ if (arm.type !== 'ArrowFunctionExpression' && arm.type !== 'FunctionExpression') return undefined
689
+ const params = arm.params
690
+ const param = Array.isArray(params) && isNode(params[0]) ? params[0] : undefined
691
+ if (param?.type !== 'Identifier') return undefined
692
+ const result = loaderResult(arm)
693
+ if (result?.type !== 'MemberExpression' || result.computed === true) return undefined
694
+ const object = unwrapExpression(child(result, 'object'))
695
+ if (object?.type !== 'Identifier' || object.name !== param.name) return undefined
696
+ const property = child(result, 'property')
697
+ return property?.type === 'Identifier' ? (property.name as string) : undefined
698
+ }
699
+
700
+ /**
701
+ * Fallback for loader shapes the structured pass cannot follow: the first
702
+ * literal `import()` anywhere in the loader, default export — mirrors what the
703
+ * textual extraction always did for these.
704
+ */
705
+ function firstImportTarget(fn: AstNode): { specifier: AstNode; exportName: string } | undefined {
706
+ let found: AstNode | undefined
707
+ const visit = (value: unknown) => {
708
+ if (found) return
709
+ if (Array.isArray(value)) {
710
+ for (const item of value) visit(item)
711
+ return
712
+ }
713
+ if (!isNode(value)) return
714
+ const source = importSource(value)
715
+ if (source) {
716
+ found = source
717
+ return
718
+ }
719
+ for (const key in value) {
720
+ const entry = value[key]
721
+ if (entry && typeof entry === 'object') visit(entry)
722
+ }
723
+ }
724
+ visit(fn.body)
725
+ return found ? { specifier: found, exportName: 'default' } : undefined
470
726
  }
471
727
 
472
- type ParserLang = 'ts' | 'tsx';
728
+ type ParserLang = 'ts' | 'tsx'
473
729
 
474
730
  // `.ts` forbids JSX (`<T>value` is a type assertion there); everything else —
475
731
  // including extensionless scan inputs — parses as the tsx superset.
476
732
  function langForFile(file: string): ParserLang {
477
- return /\.[mc]?ts$/.test(file) ? 'ts' : 'tsx';
733
+ return /\.[mc]?ts$/.test(file) ? 'ts' : 'tsx'
478
734
  }