@wular/pnext 0.0.1

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 (371) hide show
  1. package/README.md +153 -0
  2. package/bin/pnext +67 -0
  3. package/config/lint/base.js +48 -0
  4. package/config/ts/base.json +26 -0
  5. package/config/ts/react.json +13 -0
  6. package/package.json +70 -0
  7. package/reference/compat.md +63 -0
  8. package/reference/config.md +120 -0
  9. package/reference/css.md +58 -0
  10. package/reference/dev.md +69 -0
  11. package/reference/env.md +40 -0
  12. package/reference/metadata.md +86 -0
  13. package/reference/navigation.md +149 -0
  14. package/reference/overview.md +35 -0
  15. package/reference/performance.md +97 -0
  16. package/reference/rendering.md +127 -0
  17. package/reference/routing.md +167 -0
  18. package/reference/typegen.md +64 -0
  19. package/src/api/cache.ts +80 -0
  20. package/src/api/client-cache.ts +9 -0
  21. package/src/api/client-navigation.ts +279 -0
  22. package/src/api/dynamic.tsx +102 -0
  23. package/src/api/link.tsx +119 -0
  24. package/src/api/navigation.ts +198 -0
  25. package/src/api/router/events.ts +53 -0
  26. package/src/api/router/history.ts +70 -0
  27. package/src/api/router/hub.ts +194 -0
  28. package/src/api/router/policies.ts +107 -0
  29. package/src/api/router/runtime.ts +5238 -0
  30. package/src/api/router/types.ts +299 -0
  31. package/src/api/router.ts +167 -0
  32. package/src/api/server.ts +323 -0
  33. package/src/api/suspense.ts +16 -0
  34. package/src/cache/context.ts +61 -0
  35. package/src/cli/adapters/vercel-warm.ts +375 -0
  36. package/src/cli/adapters/vercel.ts +1310 -0
  37. package/src/cli/analyze-print.ts +181 -0
  38. package/src/cli/analyze.ts +328 -0
  39. package/src/cli/boot-trace.ts +29 -0
  40. package/src/cli/build.ts +3114 -0
  41. package/src/cli/dev.ts +276 -0
  42. package/src/cli/index.ts +196 -0
  43. package/src/cli/named-bin.ts +119 -0
  44. package/src/cli/serve-ui.ts +160 -0
  45. package/src/cli/start.ts +1425 -0
  46. package/src/client/build.ts +2136 -0
  47. package/src/client/chunk-fold.ts +526 -0
  48. package/src/client/entry.ts +1525 -0
  49. package/src/client/paths.ts +22 -0
  50. package/src/client/prebuilt.ts +621 -0
  51. package/src/client/profile.ts +75 -0
  52. package/src/client/react-compiler.ts +94 -0
  53. package/src/client/reference-stub.ts +145 -0
  54. package/src/client/reference.ts +47 -0
  55. package/src/compat/actions/action-client.ts +531 -0
  56. package/src/compat/actions/action-dispatch.ts +676 -0
  57. package/src/compat/actions/action-router.ts +40 -0
  58. package/src/compat/actions/action-shared.ts +95 -0
  59. package/src/compat/actions/client-plugin.ts +135 -0
  60. package/src/compat/actions/client-stub.ts +65 -0
  61. package/src/compat/actions/config.ts +164 -0
  62. package/src/compat/actions/detect.ts +208 -0
  63. package/src/compat/actions/discovery.ts +244 -0
  64. package/src/compat/actions/early-submit.ts +40 -0
  65. package/src/compat/actions/endpoint.ts +602 -0
  66. package/src/compat/actions/flight.ts +52 -0
  67. package/src/compat/actions/form-state.ts +73 -0
  68. package/src/compat/actions/hoist.ts +485 -0
  69. package/src/compat/actions/ids.ts +39 -0
  70. package/src/compat/actions/index.ts +41 -0
  71. package/src/compat/actions/instances.ts +144 -0
  72. package/src/compat/actions/origin.ts +109 -0
  73. package/src/compat/actions/protocol.ts +125 -0
  74. package/src/compat/actions/registry.ts +74 -0
  75. package/src/compat/actions/rewrite.ts +282 -0
  76. package/src/compat/actions/serve.ts +414 -0
  77. package/src/compat/actions/server-tag.ts +21 -0
  78. package/src/compat/actions/unrecognized-error.ts +30 -0
  79. package/src/compat/adapter/build-complete.ts +257 -0
  80. package/src/compat/bundler/bun-externals.ts +53 -0
  81. package/src/compat/bundler/cjs-exports.ts +542 -0
  82. package/src/compat/bundler/config.ts +363 -0
  83. package/src/compat/bundler/externals.ts +34 -0
  84. package/src/compat/bundler/import-meta-url.ts +60 -0
  85. package/src/compat/bundler/modularize-imports.ts +119 -0
  86. package/src/compat/bundler/new-url-asset.ts +87 -0
  87. package/src/compat/bundler/optimize-package-imports.ts +273 -0
  88. package/src/compat/bundler/polyfill.ts +88 -0
  89. package/src/compat/bundler/react-compiler.ts +61 -0
  90. package/src/compat/bundler/react-profiler.tsx +25 -0
  91. package/src/compat/bundler/relay-transform.ts +116 -0
  92. package/src/compat/bundler/require-context.ts +281 -0
  93. package/src/compat/bundler/resolve-extensions.ts +75 -0
  94. package/src/compat/bundler/source-cache.ts +61 -0
  95. package/src/compat/bundler/static-imports.ts +25 -0
  96. package/src/compat/bundler/symlink-imports.ts +119 -0
  97. package/src/compat/bundler/tsconfig-paths.ts +50 -0
  98. package/src/compat/bundler/wasm.ts +153 -0
  99. package/src/compat/bundler/webpack-loaders.ts +685 -0
  100. package/src/compat/bundler/worker.ts +278 -0
  101. package/src/compat/cache/build-flags.ts +81 -0
  102. package/src/compat/cache/build-prerender-errors.ts +163 -0
  103. package/src/compat/cache/custom-handler.ts +159 -0
  104. package/src/compat/cache/fetch-patch.ts +745 -0
  105. package/src/compat/cache/handler.ts +100 -0
  106. package/src/compat/cache/modern-handler.ts +275 -0
  107. package/src/compat/cache/resume-data-cache.ts +143 -0
  108. package/src/compat/cache/revalidate.ts +759 -0
  109. package/src/compat/cache/runtime-error.ts +124 -0
  110. package/src/compat/cache/use-cache-transform.ts +961 -0
  111. package/src/compat/cache/use-cache.ts +1695 -0
  112. package/src/compat/cache-control.ts +269 -0
  113. package/src/compat/client/base-path.ts +64 -0
  114. package/src/compat/client/css-order.ts +36 -0
  115. package/src/compat/client/errors/control-flow.ts +92 -0
  116. package/src/compat/client/errors/error-boundary.ts +222 -0
  117. package/src/compat/client/errors/global-error.ts +238 -0
  118. package/src/compat/client/errors/install.ts +217 -0
  119. package/src/compat/client/errors/lazy.ts +53 -0
  120. package/src/compat/client/errors/primitive-throw.ts +126 -0
  121. package/src/compat/client/errors/soft-refresh.ts +14 -0
  122. package/src/compat/client/link-status.ts +86 -0
  123. package/src/compat/client/nav-compat-runtime.ts +57 -0
  124. package/src/compat/client/nav-compat.ts +42 -0
  125. package/src/compat/client/navigation-scroll.ts +154 -0
  126. package/src/compat/client/optimistic-routing.ts +206 -0
  127. package/src/compat/client/prefetch-cache.ts +111 -0
  128. package/src/compat/client/route-announcer.ts +72 -0
  129. package/src/compat/client/segment-cache-policy.ts +159 -0
  130. package/src/compat/client/segment-cache.ts +1077 -0
  131. package/src/compat/client/segment-prefetch.ts +375 -0
  132. package/src/compat/client/trailing-slash.ts +24 -0
  133. package/src/compat/css/chunking.ts +254 -0
  134. package/src/compat/css/inline-css.ts +73 -0
  135. package/src/compat/css/lightningcss.ts +90 -0
  136. package/src/compat/css/modules.ts +373 -0
  137. package/src/compat/css/nonce.ts +30 -0
  138. package/src/compat/css/sass-plugin.ts +65 -0
  139. package/src/compat/css/sass.ts +392 -0
  140. package/src/compat/css/styled-jsx-runtime.ts +80 -0
  141. package/src/compat/css/styled-jsx.ts +49 -0
  142. package/src/compat/edge-runtime.ts +71 -0
  143. package/src/compat/export/client.ts +112 -0
  144. package/src/compat/export/index.ts +272 -0
  145. package/src/compat/export/standalone.ts +207 -0
  146. package/src/compat/image-optimizer/cache.ts +119 -0
  147. package/src/compat/image-optimizer/detect.ts +143 -0
  148. package/src/compat/image-optimizer/index.ts +601 -0
  149. package/src/compat/image-optimizer/source.ts +243 -0
  150. package/src/compat/index.ts +458 -0
  151. package/src/compat/lifecycle/after-scope.ts +86 -0
  152. package/src/compat/lifecycle/after.ts +173 -0
  153. package/src/compat/lifecycle/error-funnel.ts +306 -0
  154. package/src/compat/lifecycle/error-serialize.ts +87 -0
  155. package/src/compat/lifecycle/error-ui.ts +167 -0
  156. package/src/compat/lifecycle/instrumentation-client.ts +138 -0
  157. package/src/compat/lifecycle/instrumentation.ts +277 -0
  158. package/src/compat/lifecycle/node-console.ts +19 -0
  159. package/src/compat/lifecycle/testmode.ts +263 -0
  160. package/src/compat/mdx/compile.ts +219 -0
  161. package/src/compat/mdx/next-mdx-stub.ts +46 -0
  162. package/src/compat/mdx/plugin.ts +37 -0
  163. package/src/compat/metadata-route-artifacts.ts +458 -0
  164. package/src/compat/metadata.ts +295 -0
  165. package/src/compat/middleware/manifest.ts +210 -0
  166. package/src/compat/misc/action-return.ts +173 -0
  167. package/src/compat/next/cache.ts +211 -0
  168. package/src/compat/next/canonical-url.ts +35 -0
  169. package/src/compat/next/client-cache.ts +57 -0
  170. package/src/compat/next/client-navigation.ts +313 -0
  171. package/src/compat/next/client-only.ts +3 -0
  172. package/src/compat/next/client-script.tsx +215 -0
  173. package/src/compat/next/client-server.ts +39 -0
  174. package/src/compat/next/config-loader.ts +569 -0
  175. package/src/compat/next/config.ts +29 -0
  176. package/src/compat/next/constants.cjs +6 -0
  177. package/src/compat/next/constants.ts +6 -0
  178. package/src/compat/next/custom-server.ts +236 -0
  179. package/src/compat/next/dist/client/components/app-router-headers.ts +32 -0
  180. package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +38 -0
  181. package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -0
  182. package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -0
  183. package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -0
  184. package/src/compat/next/dynamic.tsx +46 -0
  185. package/src/compat/next/error.tsx +148 -0
  186. package/src/compat/next/font/cache.ts +171 -0
  187. package/src/compat/next/font/google.ts +2 -0
  188. package/src/compat/next/font/index.ts +8 -0
  189. package/src/compat/next/font/local.ts +5 -0
  190. package/src/compat/next/font/runtime-client.ts +71 -0
  191. package/src/compat/next/font/runtime.ts +974 -0
  192. package/src/compat/next/font/shared.ts +281 -0
  193. package/src/compat/next/form.tsx +156 -0
  194. package/src/compat/next/head.tsx +10 -0
  195. package/src/compat/next/headers.ts +247 -0
  196. package/src/compat/next/image/config.ts +196 -0
  197. package/src/compat/next/image/optimizer.ts +96 -0
  198. package/src/compat/next/image/patterns.ts +103 -0
  199. package/src/compat/next/image/shared.ts +141 -0
  200. package/src/compat/next/image/static-metadata.ts +283 -0
  201. package/src/compat/next/image/validate.ts +269 -0
  202. package/src/compat/next/image-client.tsx +215 -0
  203. package/src/compat/next/image-props.ts +575 -0
  204. package/src/compat/next/image-usage.ts +102 -0
  205. package/src/compat/next/image.tsx +56 -0
  206. package/src/compat/next/index.ts +1 -0
  207. package/src/compat/next/legacy-image.tsx +97 -0
  208. package/src/compat/next/link-usage.ts +29 -0
  209. package/src/compat/next/link-validation-transform.ts +200 -0
  210. package/src/compat/next/link.tsx +466 -0
  211. package/src/compat/next/navigation.cjs +21 -0
  212. package/src/compat/next/navigation.ts +188 -0
  213. package/src/compat/next/offline.ts +51 -0
  214. package/src/compat/next/og.ts +324 -0
  215. package/src/compat/next/optimistic-route-state.ts +188 -0
  216. package/src/compat/next/preferred-region.ts +39 -0
  217. package/src/compat/next/redirects.ts +131 -0
  218. package/src/compat/next/resource-hints.ts +136 -0
  219. package/src/compat/next/rewrites.ts +350 -0
  220. package/src/compat/next/root-params.ts +142 -0
  221. package/src/compat/next/router.cjs +49 -0
  222. package/src/compat/next/router.ts +143 -0
  223. package/src/compat/next/script.tsx +355 -0
  224. package/src/compat/next/server-only.ts +3 -0
  225. package/src/compat/next/server.ts +28 -0
  226. package/src/compat/next/svgr.ts +58 -0
  227. package/src/compat/next/telemetry.ts +77 -0
  228. package/src/compat/next/user-agent.ts +100 -0
  229. package/src/compat/next/web-vitals.ts +56 -0
  230. package/src/compat/otel/api.ts +95 -0
  231. package/src/compat/otel/client-trace-metadata.ts +71 -0
  232. package/src/compat/otel/fetch-span.ts +77 -0
  233. package/src/compat/otel/tracer.ts +944 -0
  234. package/src/compat/pages/client-plugin.ts +108 -0
  235. package/src/compat/pages/index.ts +527 -0
  236. package/src/compat/pages/router-state.ts +94 -0
  237. package/src/compat/ppr/io.ts +38 -0
  238. package/src/compat/ppr/missing-root-params.ts +105 -0
  239. package/src/compat/ppr/root-params-scan.ts +164 -0
  240. package/src/compat/ppr/root-params-transform.ts +75 -0
  241. package/src/compat/ppr/root-params.ts +129 -0
  242. package/src/compat/ppr/segment-config-incompat.ts +34 -0
  243. package/src/compat/protocol.ts +202 -0
  244. package/src/compat/react/client.ts +59 -0
  245. package/src/compat/react/compiler-runtime.ts +60 -0
  246. package/src/compat/react/dom-client.ts +115 -0
  247. package/src/compat/react/dom-react-server.ts +20 -0
  248. package/src/compat/react/dom-server.ts +40 -0
  249. package/src/compat/react/dom.ts +154 -0
  250. package/src/compat/react/preact.ts +522 -0
  251. package/src/compat/react/react-server.ts +84 -0
  252. package/src/compat/react/router-shim.ts +26 -0
  253. package/src/compat/react/server-component-use.ts +48 -0
  254. package/src/compat/react/server-inserted-html.ts +87 -0
  255. package/src/compat/react/server.ts +156 -0
  256. package/src/compat/react/view-transition.ts +60 -0
  257. package/src/compat/register/actions.ts +875 -0
  258. package/src/compat/register/boot.ts +141 -0
  259. package/src/compat/register/build-tier.ts +11 -0
  260. package/src/compat/register/build.ts +182 -0
  261. package/src/compat/register/bundler.ts +587 -0
  262. package/src/compat/register/cache.ts +85 -0
  263. package/src/compat/register/client-errors.ts +18 -0
  264. package/src/compat/register/config.ts +17 -0
  265. package/src/compat/register/css-extras.ts +120 -0
  266. package/src/compat/register/edge-runtime.ts +6 -0
  267. package/src/compat/register/errors.ts +46 -0
  268. package/src/compat/register/export.ts +23 -0
  269. package/src/compat/register/font.ts +36 -0
  270. package/src/compat/register/hooks.ts +34 -0
  271. package/src/compat/register/image.ts +133 -0
  272. package/src/compat/register/index.ts +111 -0
  273. package/src/compat/register/instrumentation-client.ts +35 -0
  274. package/src/compat/register/lifecycle.ts +86 -0
  275. package/src/compat/register/mdx.ts +48 -0
  276. package/src/compat/register/middleware.ts +36 -0
  277. package/src/compat/register/misc.ts +44 -0
  278. package/src/compat/register/otel.ts +288 -0
  279. package/src/compat/register/pages-api.ts +473 -0
  280. package/src/compat/register/ppr.ts +56 -0
  281. package/src/compat/register/protocol.ts +57 -0
  282. package/src/compat/register/proxy.ts +127 -0
  283. package/src/compat/register/render.ts +268 -0
  284. package/src/compat/register/routing.ts +410 -0
  285. package/src/compat/register/segment.ts +1903 -0
  286. package/src/compat/register/static-image.ts +21 -0
  287. package/src/compat/register/typed-routes.ts +35 -0
  288. package/src/compat/register/usecache.ts +131 -0
  289. package/src/compat/register/validation.ts +56 -0
  290. package/src/compat/segment/loading-boundary.ts +113 -0
  291. package/src/compat/segment/page-slot.ts +200 -0
  292. package/src/compat/segment/tree.ts +481 -0
  293. package/src/compat/segment/vary-key.ts +102 -0
  294. package/src/compat/segment/vary-params.ts +551 -0
  295. package/src/compat/static-params.ts +33 -0
  296. package/src/compat/tsconfig-defaults.ts +301 -0
  297. package/src/compat/typecheck/index.ts +1481 -0
  298. package/src/compat/typecheck/worker.ts +26 -0
  299. package/src/compat/typed-routes/index.ts +92 -0
  300. package/src/compat/typed-routes/manifest.ts +356 -0
  301. package/src/compat/typed-routes/typegen.ts +566 -0
  302. package/src/compat/validation/errors.ts +159 -0
  303. package/src/compat/validation/index.ts +1770 -0
  304. package/src/compat/validation/prerender-diagnostics.ts +1508 -0
  305. package/src/compat-bootstrap.ts +67 -0
  306. package/src/config.ts +218 -0
  307. package/src/css/build.ts +697 -0
  308. package/src/css/index.ts +2 -0
  309. package/src/css/postcss.ts +236 -0
  310. package/src/css/worker.ts +34 -0
  311. package/src/dev/client-actions.ts +35 -0
  312. package/src/dev/client-chunk-store.ts +92 -0
  313. package/src/dev/client-key-cache.ts +178 -0
  314. package/src/dev/global-css-cache.ts +212 -0
  315. package/src/dev/imports.ts +2430 -0
  316. package/src/dev/module-cache.ts +721 -0
  317. package/src/dev/module-generations.ts +38 -0
  318. package/src/dev/module-transform.ts +188 -0
  319. package/src/dev/node-module-bundle-cache.ts +63 -0
  320. package/src/dev/restart-cache.ts +10 -0
  321. package/src/dev/route-bundle-key-cache.ts +154 -0
  322. package/src/dev/route-facts-cache.ts +223 -0
  323. package/src/dev/server.ts +1710 -0
  324. package/src/dynamic/source.ts +307 -0
  325. package/src/dynamic/tree-shake.ts +262 -0
  326. package/src/env.ts +92 -0
  327. package/src/extensions.ts +1898 -0
  328. package/src/index.ts +34 -0
  329. package/src/internal.ts +43 -0
  330. package/src/islands/boundary-error.ts +8 -0
  331. package/src/islands/static-children.ts +37 -0
  332. package/src/islands/static-slots.ts +106 -0
  333. package/src/ppr-postpone.ts +24 -0
  334. package/src/ppr.ts +784 -0
  335. package/src/proxy.ts +752 -0
  336. package/src/render/hooks.ts +384 -0
  337. package/src/render/index.ts +1 -0
  338. package/src/render/island-context.ts +47 -0
  339. package/src/render/metadata.ts +857 -0
  340. package/src/render/renderer.ts +7391 -0
  341. package/src/render/resource-hints.ts +44 -0
  342. package/src/render/slots.tsx +679 -0
  343. package/src/request/context.ts +396 -0
  344. package/src/resolve/engine.ts +219 -0
  345. package/src/resolve/imports.ts +1104 -0
  346. package/src/resolve/scan-facts.ts +474 -0
  347. package/src/resolve/source-text.ts +86 -0
  348. package/src/routing/forwarded.ts +41 -0
  349. package/src/routing/handler.ts +271 -0
  350. package/src/routing/href.ts +203 -0
  351. package/src/routing/metadata.ts +1018 -0
  352. package/src/routing/request-runtime.ts +43 -0
  353. package/src/routing/routes.ts +2560 -0
  354. package/src/routing/slots.ts +432 -0
  355. package/src/runtime/server.ts +3453 -0
  356. package/src/runtime/vendor.ts +1160 -0
  357. package/src/style-modules.d.ts +9 -0
  358. package/src/typegen.ts +151 -0
  359. package/src/types.ts +725 -0
  360. package/src/utils/ansi.ts +9 -0
  361. package/src/utils/content-type.ts +31 -0
  362. package/src/utils/decode.ts +7 -0
  363. package/src/utils/dev-profile.ts +31 -0
  364. package/src/utils/error-log.ts +29 -0
  365. package/src/utils/fs-cache.ts +31 -0
  366. package/src/utils/fs.ts +119 -0
  367. package/src/utils/html.ts +46 -0
  368. package/src/utils/serialize.ts +378 -0
  369. package/src/utils/source.ts +35 -0
  370. package/src/utils/verbose.ts +39 -0
  371. package/tsconfig.json +10 -0
@@ -0,0 +1,685 @@
1
+ // `turbopack.rules` loader-chain support (COMPAT).
2
+ //
3
+ // Next's turbopack.rules let a project route specific extensions through custom "loader" modules -
4
+ // plain functions receiving the raw file content and returning replacement module source. pnext has no
5
+ // webpack/turbopack graph to run these against, so this plugin re-implements the loader-invocation
6
+ // contract directly: resolve each configured loader module, call it with a minimal loader-context
7
+ // (resourcePath, resourceQuery, fs, async/callback, getOptions, getResolve, addDependency,
8
+ // importModule), and feed the result to esbuild via the loader picked from the rule's `as` or the
9
+ // source file's own extension - matching the sync/async/callback-style loaders, raw-Buffer content and
10
+ // chained-output-as-module-source shapes the fixtures exercise.
11
+ //
12
+ // Also supports the conditional-array rule form (condition.query selects a chain by the import's
13
+ // query), `turbopack.resolveAlias`, and no-loader `type` asset rules.
14
+ //
15
+ // Registered first in both client and server esbuild plugin arrays so a configured rule preempts
16
+ // pnext's default handling for the same extension.
17
+
18
+ import { createHash } from 'node:crypto';
19
+ import { existsSync, readFile, readFileSync, realpathSync } from 'node:fs';
20
+ import { mkdir, readFile as readFileP, writeFile } from 'node:fs/promises';
21
+ import { createRequire } from 'node:module';
22
+ import path from 'node:path';
23
+ import { pathToFileURL } from 'node:url';
24
+ import type { Plugin } from 'esbuild';
25
+ import type { ResolvedConfig } from '../../config';
26
+ import { rewriteSpecifierLiterals } from '../../resolve/scan-facts';
27
+ import { turbopackLoaderRules, turbopackResolveAlias, type WebpackLoaderRule } from './config';
28
+
29
+ interface LoaderFn {
30
+ (this: LoaderContext, content: string | Buffer): unknown;
31
+ raw?: boolean;
32
+ }
33
+
34
+ interface LoaderContext {
35
+ resourcePath: string;
36
+ resourceQuery: string;
37
+ mode: string;
38
+ fs: {
39
+ readFile: typeof readFile;
40
+ readFileSync: typeof readFileSync;
41
+ };
42
+ addDependency: (file: string) => void;
43
+ getOptions: () => Record<string, unknown>;
44
+ async: () => (err: unknown, result?: unknown) => void;
45
+ callback: (err: unknown, result?: unknown, ...rest: unknown[]) => void;
46
+ getResolve: (options?: unknown) => (context: string, request: string) => Promise<string>;
47
+ emitFile: (name: string, content: unknown) => void;
48
+ importModule: (request: string) => Promise<unknown>;
49
+ }
50
+
51
+ /** Where a loader chain runs — client builds cannot support `this.importModule`. */
52
+ type LoaderContextTarget = 'client' | 'server';
53
+
54
+ const RESOLVE_EXTENSIONS = ['', '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.json'];
55
+
56
+ function stripLeadingWildcard(glob: string): string {
57
+ return glob.startsWith('*') ? glob.slice(1) : glob;
58
+ }
59
+
60
+ function escapeRegExp(value: string): string {
61
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
62
+ }
63
+
64
+ function ruleMatches(rule: WebpackLoaderRule, filePath: string): boolean {
65
+ return rule.glob.startsWith('*')
66
+ ? filePath.endsWith(stripLeadingWildcard(rule.glob))
67
+ : path.basename(filePath) === rule.glob;
68
+ }
69
+
70
+ function conditionMatches(rule: WebpackLoaderRule, resourceQuery: string): boolean {
71
+ if (!rule.condition) return true;
72
+ const query = rule.condition.query;
73
+ return typeof query === 'string' ? resourceQuery === query : query.test(resourceQuery);
74
+ }
75
+
76
+ /** First rule whose glob matches (ignoring any `condition`). */
77
+ function findRule(rules: WebpackLoaderRule[], filePath: string): WebpackLoaderRule | undefined {
78
+ return rules.find(rule => ruleMatches(rule, filePath));
79
+ }
80
+
81
+ /** First rule whose glob matches AND whose `condition.query` accepts `resourceQuery`. */
82
+ function findRuleForResource(
83
+ rules: WebpackLoaderRule[],
84
+ filePath: string,
85
+ resourceQuery: string,
86
+ ): WebpackLoaderRule | undefined {
87
+ return rules.find(rule => ruleMatches(rule, filePath) && conditionMatches(rule, resourceQuery));
88
+ }
89
+
90
+ /**
91
+ * Whether a configured `turbopack.rules` loader chain claims this file. The generic "ignored
92
+ * asset"/static-image resolvers redirect those extensions to their own namespace unconditionally, ahead
93
+ * of the bundler-extension plugins where this plugin lives, so a configured rule for e.g. `*.svg` would
94
+ * never get a chance without this escape hatch. Registered as the sole `hasLoaderRuleFor`
95
+ * implementation so all three asset-claiming resolvers share one check. Accepts a raw specifier with an
96
+ * optional query or hash suffix and strips it here.
97
+ */
98
+ export function hasWebpackLoaderRuleFor(filePath: string): boolean {
99
+ const [sourcePath = filePath] = filePath.split(/[?#]/, 1);
100
+ return findRule(turbopackLoaderRules(), sourcePath) !== undefined;
101
+ }
102
+
103
+ /**
104
+ * Run the loader chain a `turbopack.rules` entry configures for `specifier` (alias-aware,
105
+ * query-carrying) and materialize its output as a real module on disk, returning that path - or
106
+ * undefined when no rule claims it.
107
+ *
108
+ * This is the SERVER-graph counterpart of the esbuild plugin. Server modules are not one esbuild graph:
109
+ * each app module is compiled on its own and route files are inlined into a route bundle, so a rule
110
+ * source never reaches a plugin onLoad with its importer's query intact. Core's resolvers ask this seam
111
+ * instead and import the artifact directly. Loaders may be effectful, so each (file, query) pair runs
112
+ * exactly once per process and every resolver shares the result.
113
+ */
114
+ export function webpackLoaderRuleModule(
115
+ config: ResolvedConfig,
116
+ specifier: string,
117
+ importer: string,
118
+ ): string | undefined | Promise<string | undefined> {
119
+ const rules = turbopackLoaderRules();
120
+ if (rules.length === 0) return undefined;
121
+ const [base = specifier] = specifier.split(/[?#]/, 1);
122
+ const query = specifier.slice(base.length);
123
+ const fromDir = path.dirname(originalResourcePath(importer, config));
124
+ const resourcePath = resolveSpecifier(config.root, fromDir, base) ?? base;
125
+ if (!findRuleForResource(rules, resourcePath, query)) return undefined;
126
+ const key = `${resourcePath}\0${query}`;
127
+ const existing = ruleModuleArtifacts.get(key);
128
+ if (existing) return existing;
129
+ const built = buildRuleModuleArtifact(config, rules, resourcePath, query).catch(error => {
130
+ ruleModuleArtifacts.delete(key);
131
+ throw error;
132
+ });
133
+ ruleModuleArtifacts.set(key, built);
134
+ return built;
135
+ }
136
+
137
+ const ruleModuleArtifacts = new Map<string, Promise<string | undefined>>();
138
+
139
+ async function buildRuleModuleArtifact(
140
+ config: ResolvedConfig,
141
+ rules: WebpackLoaderRule[],
142
+ resourcePath: string,
143
+ query: string,
144
+ ): Promise<string | undefined> {
145
+ const loaded = await loadRuleModule(config.root, rules, resourcePath, query, 'server', config);
146
+ if (!loaded) return undefined;
147
+ // Named by the source name + a hash of (path, query, output): distinct
148
+ // `?query` imports of one file stay distinct modules, and a re-run that
149
+ // produces the same text reuses the same artifact.
150
+ const hash = createHash('sha256')
151
+ .update(`${resourcePath}\0${query}\0${loaded.contents}`)
152
+ .digest('hex')
153
+ .slice(0, 16);
154
+ const name = path.basename(resourcePath).replace(/[^A-Za-z0-9_.-]+/g, '-');
155
+ // The chain's output loader doubles as the artifact extension, so Bun and
156
+ // esbuild both parse it the way the rule's `as` declared.
157
+ const outFile = path.join(config.outPath, 'cache', 'loader-rules', `${name}.${hash}.${loaded.loader}`);
158
+ await mkdir(path.dirname(outFile), { recursive: true });
159
+ await writeFile(outFile, loaded.contents);
160
+ return outFile;
161
+ }
162
+
163
+ /** esbuild/Bun `loader` for a rule's declared (or inferred) output extension. */
164
+ function outputLoader(rule: WebpackLoaderRule, resourcePath: string): 'ts' | 'tsx' | 'jsx' {
165
+ const ext = (rule.as ? stripLeadingWildcard(rule.as) : path.extname(resourcePath)).toLowerCase();
166
+ if (ext.endsWith('.tsx')) return 'tsx';
167
+ if (ext.endsWith('.ts')) return 'ts';
168
+ return 'jsx';
169
+ }
170
+
171
+ function toBuffer(content: string | Buffer): Buffer {
172
+ return Buffer.isBuffer(content) ? content : Buffer.from(content, 'utf8');
173
+ }
174
+
175
+ function toStringContent(content: string | Buffer): string {
176
+ return Buffer.isBuffer(content) ? content.toString('utf8') : content;
177
+ }
178
+
179
+ // ---------------------------------------------------------------------------
180
+ // Specifier resolution (resolveAlias + relative/absolute, with extension probe)
181
+ // ---------------------------------------------------------------------------
182
+
183
+ // Absolute path of pnext's own `src/` (or `dist/` in a built install) — modules
184
+ // under it are framework runtime, not app code, and are exempt from user
185
+ // `resolveAlias`/`resolve.alias` entries.
186
+ const PNEXT_RUNTIME_ROOT = path.resolve(import.meta.dirname, '../..');
187
+
188
+ function isPNextRuntimeImporter(importer: string): boolean {
189
+ if (!importer) return false;
190
+ const rel = path.relative(PNEXT_RUNTIME_ROOT, importer);
191
+ return Boolean(rel) && !rel.startsWith('..') && !path.isAbsolute(rel);
192
+ }
193
+
194
+ function firstExisting(base: string): string | undefined {
195
+ for (const ext of RESOLVE_EXTENSIONS) {
196
+ if (existsSync(base + ext)) return base + ext;
197
+ }
198
+ return undefined;
199
+ }
200
+
201
+ /**
202
+ * Map a specifier through `turbopack.resolveAlias`, returning the resolved absolute target path, or
203
+ * undefined when no alias matches. Supports exact keys and trailing-glob keys.
204
+ */
205
+ function resolveAliasSpecifier(specifier: string, root: string): string | undefined {
206
+ for (const [key, target] of Object.entries(turbopackResolveAlias())) {
207
+ if (key.endsWith('/*')) {
208
+ const prefix = key.slice(0, -1); // e.g. '@/*' -> '@/'
209
+ if (specifier.startsWith(prefix)) {
210
+ const rest = specifier.slice(prefix.length);
211
+ const mapped = target.endsWith('/*') ? target.slice(0, -1) + rest : target.replace('*', rest);
212
+ return resolveAliasTarget(mapped, root);
213
+ }
214
+ } else if (specifier === key) {
215
+ return resolveAliasTarget(target, root);
216
+ }
217
+ }
218
+ return undefined;
219
+ }
220
+
221
+ function resolveAliasTarget(target: string, root: string): string {
222
+ if (path.isAbsolute(target)) return target;
223
+ if (target.startsWith('.')) return path.resolve(root, target);
224
+ return target;
225
+ }
226
+
227
+ /**
228
+ * Resolve a specifier (alias-aware) to an absolute on-disk path. Returns the best candidate even when
229
+ * the file cannot be probed; bare non-alias specifiers return undefined and are left to the host
230
+ * resolver.
231
+ */
232
+ function resolveSpecifier(root: string, fromDir: string, specifier: string): string | undefined {
233
+ const aliased = resolveAliasSpecifier(specifier, root);
234
+ if (aliased) return firstExisting(aliased) ?? aliased;
235
+ if (path.isAbsolute(specifier)) return firstExisting(specifier) ?? specifier;
236
+ if (specifier.startsWith('.')) {
237
+ const base = path.resolve(fromDir, specifier);
238
+ return firstExisting(base) ?? base;
239
+ }
240
+ return undefined;
241
+ }
242
+
243
+ /**
244
+ * Rewrite `turbopack.resolveAlias` BARE specifiers to their resolved absolute paths in server source.
245
+ *
246
+ * Both esbuild passes and the runtime Bun.plugin hook below map aliases in onResolve - but Bun's runtime
247
+ * resolver rejects an unknown bare package specifier before any plugin onResolve runs, so a module the
248
+ * SERVER RUNTIME loads directly can only be fixed in its source. That is the case for a
249
+ * `this.importModule()` target's own transitive imports: the direct request is alias-resolved by
250
+ * importModuleFor, everything past it is plain import() resolution. Relative and absolute specifiers
251
+ * are left alone - those resolve natively.
252
+ */
253
+ export function rewriteResolveAliasImports(source: string, file: string, root: string): string {
254
+ const aliases = turbopackResolveAlias();
255
+ if (Object.keys(aliases).length === 0) return source;
256
+ return rewriteSpecifierLiterals(source, file, (specifier, kind) => {
257
+ // Same scope as the `from`-anchored regex this replaced: side-effect
258
+ // imports stay with the host resolver.
259
+ if (kind === 'side-effect') return undefined;
260
+ if (specifier.startsWith('.') || path.isAbsolute(specifier)) return undefined;
261
+ const aliased = resolveAliasSpecifier(specifier, root);
262
+ if (!aliased) return undefined;
263
+ const target = firstExisting(aliased) ?? aliased;
264
+ // A bare alias target (e.g. `'preact/compat': 'react'`) is not a file path;
265
+ // leave the host resolver to handle it.
266
+ if (!path.isAbsolute(target)) return undefined;
267
+ return JSON.stringify(target);
268
+ });
269
+ }
270
+
271
+ /** `this.getResolve()(context, request)` — alias-aware, extension-probing. */
272
+ function resolveLoaderRequest(root: string, context: string, request: string): Promise<string> {
273
+ const resolved = resolveSpecifier(root, context, request);
274
+ if (resolved && existsSync(resolved)) return Promise.resolve(resolved);
275
+ return Promise.reject(new Error(`Cannot resolve '${request}' from '${context}'`));
276
+ }
277
+
278
+ /**
279
+ * Server bundles compile against materialized COPIES of the app tree, not symlinks, so sibling files do
280
+ * not exist next to the materialized copy. A loader's relative `this.importModule('../x')` /
281
+ * `this.getResolve()` must therefore resolve against the ORIGINAL file's directory. Recover it by
282
+ * rebasing the materialized path onto `config.workspaceRoot`, stripping the cache root and the leading
283
+ * profile segment; out-of-workspace modules are keyed `external/<hash>` and simply fail the existence
284
+ * check, falling through unchanged. `realpathSync` is kept only as a secondary fallback for the
285
+ * symlinked-asset case - it is a no-op for the real copied code-module files this path mainly handles.
286
+ */
287
+ function originalResourcePath(resourcePath: string, config: ResolvedConfig | undefined): string {
288
+ if (config) {
289
+ const cacheRoot = path.join(config.outPath, 'cache', 'server');
290
+ const rel = path.relative(cacheRoot, resourcePath);
291
+ if (rel && !rel.startsWith('..') && !path.isAbsolute(rel)) {
292
+ const segments = rel.split(path.sep);
293
+ segments.shift(); // drop <profileDir>
294
+ const original = path.join(config.workspaceRoot, ...segments);
295
+ if (existsSync(original)) return original;
296
+ }
297
+ }
298
+ try {
299
+ return realpathSync(resourcePath);
300
+ } catch {
301
+ return resourcePath;
302
+ }
303
+ }
304
+
305
+ /**
306
+ * `this.importModule(request)`: resolve (alias + relative/absolute aware) then dynamically import the
307
+ * module in the server/runtime context, letting Bun's onLoad hooks handle the transitive graph. Never
308
+ * available in the client build context.
309
+ *
310
+ * Only the DIRECT request goes through `resolveSpecifier`; the imported module's own imports are
311
+ * resolved by Bun. Those transitive specifiers still need the same resolveAlias and loader-rule
312
+ * treatment, which is exactly what the runtime Bun.plugin hook provides, so ensure it is installed
313
+ * before importing. Registration is idempotent per root, so a build that already did it pays nothing.
314
+ */
315
+ async function importModuleFor(
316
+ root: string,
317
+ resourcePath: string,
318
+ request: string,
319
+ config: ResolvedConfig | undefined,
320
+ ): Promise<unknown> {
321
+ if (config) registerWebpackLoaderRuleRuntime(config);
322
+ const resolved = resolveSpecifier(root, path.dirname(resourcePath), request);
323
+ if (!resolved) {
324
+ throw new Error(`this.importModule: cannot resolve '${request}' from '${resourcePath}'`);
325
+ }
326
+ return import(pathToFileURL(resolved).href);
327
+ }
328
+
329
+ // ---------------------------------------------------------------------------
330
+ // Loader invocation
331
+ // ---------------------------------------------------------------------------
332
+
333
+ /** Invokes a single loader function, bridging its sync/promise/callback return conventions. */
334
+ function runLoader(
335
+ fn: LoaderFn,
336
+ root: string,
337
+ resourcePath: string,
338
+ resourceQuery: string,
339
+ input: string | Buffer,
340
+ target: LoaderContextTarget,
341
+ config: ResolvedConfig | undefined,
342
+ ): Promise<string | Buffer> {
343
+ return new Promise((resolve, reject) => {
344
+ let settled = false;
345
+ const finish = (err: unknown, result?: unknown) => {
346
+ if (settled) return;
347
+ settled = true;
348
+ if (err) reject(err instanceof Error ? err : new Error(JSON.stringify(err)));
349
+ else resolve(result as string | Buffer);
350
+ };
351
+ const ctx: LoaderContext = {
352
+ resourcePath,
353
+ resourceQuery,
354
+ mode: 'production',
355
+ fs: { readFile, readFileSync },
356
+ // eslint-disable-next-line @typescript-eslint/no-empty-function -- loader deps aren't tracked
357
+ addDependency: () => {},
358
+ getOptions: () => ({}),
359
+ async: () => finish,
360
+ callback: finish,
361
+ getResolve: () => (context: string, request: string) => resolveLoaderRequest(root, context, request),
362
+ // eslint-disable-next-line @typescript-eslint/no-empty-function -- emitted files aren't tracked
363
+ emitFile: () => {},
364
+ importModule:
365
+ target === 'server'
366
+ ? (request: string) => importModuleFor(root, resourcePath, request, config)
367
+ : () =>
368
+ Promise.reject(
369
+ new Error('this.importModule() is not supported in the client build context'),
370
+ ),
371
+ };
372
+ try {
373
+ const result = fn.call(ctx, input);
374
+ if (result === undefined) return; // loader signaled async via this.async()/this.callback()
375
+ if (result && typeof (result as Promise<unknown>).then === 'function') {
376
+ (result as Promise<unknown>).then(value => finish(null, value), finish);
377
+ } else {
378
+ finish(null, result);
379
+ }
380
+ } catch (error) {
381
+ finish(error);
382
+ }
383
+ });
384
+ }
385
+
386
+ function resolveLoaderExport(mod: unknown): LoaderFn {
387
+ if (typeof mod === 'function') return mod as LoaderFn;
388
+ const withDefault = mod as { default?: unknown } | null | undefined;
389
+ if (withDefault && typeof withDefault.default === 'function') return withDefault.default as LoaderFn;
390
+ return mod as LoaderFn;
391
+ }
392
+
393
+ // A loader commonly returns CJS-style source on the assumption webpack's CJS/ESM interop will bind a
394
+ // default import to it. esbuild's build() pass does that automatically, but Bun's plugin onLoad - used
395
+ // for the runtime's per-file SSR loading - does not synthesize a default export from a bare
396
+ // `module.exports` assignment, so normalize to a real ESM default export.
397
+ function normalizeToEsmDefault(contents: string): string {
398
+ const match = /^\s*module\.exports\s*=\s*([\s\S]*?);?\s*$/.exec(contents);
399
+ return match ? `export default (${match[1]});` : contents;
400
+ }
401
+
402
+ export async function runLoaderChain(
403
+ root: string,
404
+ rule: WebpackLoaderRule,
405
+ resourcePath: string,
406
+ resourceQuery: string,
407
+ target: LoaderContextTarget,
408
+ config: ResolvedConfig | undefined,
409
+ ): Promise<string> {
410
+ const requireFromRoot = createRequire(path.join(root, 'noop-require.cjs'));
411
+ // esbuild loads the materialized copy, but the loader context (`this.resourcePath`
412
+ // and the base dir for relative `importModule`/`getResolve`) must point at the
413
+ // original source so sibling modules resolve. Reading the materialized copy
414
+ // yields identical bytes.
415
+ const contextPath = originalResourcePath(resourcePath, config);
416
+ let content: string | Buffer = await readFileP(resourcePath);
417
+ for (const specifier of rule.loaders) {
418
+ const resolvedSpecifier = path.isAbsolute(specifier) ? specifier : requireFromRoot.resolve(specifier);
419
+ const mod: unknown = requireFromRoot(resolvedSpecifier);
420
+ const fn = resolveLoaderExport(mod);
421
+ const input = fn.raw ? toBuffer(content) : toStringContent(content);
422
+ content = await runLoader(fn, root, contextPath, resourceQuery, input, target, config);
423
+ }
424
+ return normalizeToEsmDefault(toStringContent(content));
425
+ }
426
+
427
+ // ---------------------------------------------------------------------------
428
+ // `type` asset rules (no loaders)
429
+ // ---------------------------------------------------------------------------
430
+
431
+ /** asset/resource: emit the file under a hashed `/_next/static/media` URL, export the URL string. */
432
+ async function emitAssetResourceModule(
433
+ config: ResolvedConfig,
434
+ resourcePath: string,
435
+ bytes: Uint8Array,
436
+ ): Promise<string> {
437
+ const ext = path.extname(resourcePath).toLowerCase() || '.bin';
438
+ const hash = createHash('sha256').update(bytes).digest('hex').slice(0, 8);
439
+ const base = path.basename(resourcePath, path.extname(resourcePath)).replace(/[^A-Za-z0-9_-]+/g, '-');
440
+ const relative = path.posix.join('_next', 'static', 'media', `${base}.${hash}${ext}`);
441
+ const target = path.join(config.outPath, 'public', ...relative.split('/'));
442
+ await mkdir(path.dirname(target), { recursive: true });
443
+ if (!existsSync(target)) await writeFile(target, bytes);
444
+ const src = `/${relative}`;
445
+ return `const src = ${JSON.stringify(src)};\nexport default src;\nexport { src };\n`;
446
+ }
447
+
448
+ async function loadTypeAssetModule(
449
+ rule: WebpackLoaderRule,
450
+ resourcePath: string,
451
+ config: ResolvedConfig | undefined,
452
+ ): Promise<string> {
453
+ const bytes = new Uint8Array(await readFileP(resourcePath));
454
+ if (rule.assetType === 'bytes') {
455
+ return `export default new Uint8Array(${JSON.stringify(Array.from(bytes))});`;
456
+ }
457
+ if (rule.assetType === 'source') {
458
+ return `export default ${JSON.stringify(Buffer.from(bytes).toString('utf8'))};`;
459
+ }
460
+ // asset/resource — needs a config to know the output tree; without one, fall
461
+ // back to exporting the basename so a build still resolves the import.
462
+ if (config) return emitAssetResourceModule(config, resourcePath, bytes);
463
+ return `export default ${JSON.stringify(`/${path.basename(resourcePath)}`)};`;
464
+ }
465
+
466
+ // ---------------------------------------------------------------------------
467
+ // Rule-module loading (shared by esbuild plugins and the Bun runtime hook)
468
+ // ---------------------------------------------------------------------------
469
+
470
+ interface LoadedRuleModule {
471
+ contents: string;
472
+ loader: 'ts' | 'tsx' | 'jsx' | 'js';
473
+ }
474
+
475
+ async function loadRuleModule(
476
+ root: string,
477
+ rules: WebpackLoaderRule[],
478
+ resourcePath: string,
479
+ resourceQuery: string,
480
+ target: LoaderContextTarget,
481
+ config: ResolvedConfig | undefined,
482
+ ): Promise<LoadedRuleModule | undefined> {
483
+ const rule = findRuleForResource(rules, resourcePath, resourceQuery);
484
+ if (!rule) return undefined;
485
+ if (rule.assetType) {
486
+ return { contents: await loadTypeAssetModule(rule, resourcePath, config), loader: 'js' };
487
+ }
488
+ const contents = await runLoaderChain(root, rule, resourcePath, resourceQuery, target, config);
489
+ return { contents, loader: outputLoader(rule, resourcePath) };
490
+ }
491
+
492
+ function ruleSuffixFilter(rules: WebpackLoaderRule[]): RegExp | undefined {
493
+ const suffixes = [
494
+ ...new Set(
495
+ rules.filter(rule => rule.glob.startsWith('*')).map(rule => stripLeadingWildcard(rule.glob)),
496
+ ),
497
+ ];
498
+ if (suffixes.length === 0) return undefined;
499
+ return new RegExp(`(?:${suffixes.map(escapeRegExp).join('|')})$`);
500
+ }
501
+
502
+ /** Build the alias-key onResolve filter (exact keys + trailing-`/*` prefixes). */
503
+ function aliasSpecifierFilter(): RegExp | undefined {
504
+ const keys = Object.keys(turbopackResolveAlias());
505
+ if (keys.length === 0) return undefined;
506
+ const alternatives = keys.map(key =>
507
+ key.endsWith('/*') ? escapeRegExp(key.slice(0, -1)) : `${escapeRegExp(key)}$`,
508
+ );
509
+ return new RegExp(`^(?:${alternatives.join('|')})`);
510
+ }
511
+
512
+ // A null byte separates a virtual query module's real path from its threaded
513
+ // resourceQuery (real paths never contain a null byte); used for the Bun
514
+ // runtime namespace so two `?query` imports of one file stay distinct modules.
515
+ const QUERY_SEPARATOR = '\0';
516
+ const QUERY_NAMESPACE = 'pnext-webpack-loader-query';
517
+
518
+ /**
519
+ * esbuild plugin applying configured `turbopack.rules` loader chains (plus
520
+ * `resolveAlias` and `?query` threading). Returns `undefined` when the project
521
+ * configures no rules and no aliases, so callers can skip registering it.
522
+ */
523
+ export function webpackLoaderRulesPlugin(
524
+ config: ResolvedConfig,
525
+ target: LoaderContextTarget,
526
+ ): Plugin | undefined {
527
+ const rules = turbopackLoaderRules();
528
+ const filter = ruleSuffixFilter(rules);
529
+ const aliasFilter = aliasSpecifierFilter();
530
+ if (!filter && !aliasFilter) return undefined;
531
+ return {
532
+ name: 'pnext-compat-webpack-loader-rules',
533
+ setup(build) {
534
+ // `?query`-suffixed specifiers whose base matches a loader rule: strip the
535
+ // query, resolve the base (alias-aware), thread the query via esbuild's
536
+ // `suffix` so the two `?query` imports of one file stay distinct modules.
537
+ if (filter) {
538
+ build.onResolve({ filter: /\?/ }, args => {
539
+ const queryIndex = args.path.indexOf('?');
540
+ if (queryIndex === -1) return undefined;
541
+ const base = args.path.slice(0, queryIndex);
542
+ const query = args.path.slice(queryIndex);
543
+ const resolved = resolveSpecifier(config.root, args.resolveDir, base);
544
+ if (!resolved || !findRule(rules, resolved)) return undefined;
545
+ return { path: resolved, suffix: query };
546
+ });
547
+ }
548
+ // `resolveAlias` specifier rewrites (without a query — query handled above).
549
+ if (aliasFilter) {
550
+ build.onResolve({ filter: aliasFilter }, async args => {
551
+ if (args.path.includes('?')) return undefined;
552
+ // A user alias describes the APP's module graph. pnext's own runtime imports preact/compat to
553
+ // build the react shim, so a config alias redirecting preact/compat to react points the shim
554
+ // at the module pnext maps back to the shim - a self-import that throws on client bundle
555
+ // init, killing all client JS. Framework modules resolve unaliased.
556
+ if (isPNextRuntimeImporter(args.importer)) return undefined;
557
+ const resolved = resolveAliasSpecifier(args.path, config.root);
558
+ if (!resolved) return undefined;
559
+ const target = firstExisting(resolved) ?? resolved;
560
+ // A bare alias target (e.g. `'preact/compat': 'react'`) is not a
561
+ // file path — hand it back to esbuild's own resolver.
562
+ if (!path.isAbsolute(target)) {
563
+ if (target === args.path) return undefined;
564
+ return build.resolve(target, {
565
+ resolveDir: args.resolveDir,
566
+ kind: args.kind,
567
+ importer: args.importer,
568
+ });
569
+ }
570
+ return { path: target };
571
+ });
572
+ }
573
+ if (filter) {
574
+ build.onLoad({ filter, namespace: 'file' }, async args => {
575
+ const resourcePath = path.resolve(args.path);
576
+ const resourceQuery = args.suffix ?? '';
577
+ try {
578
+ const loaded = await loadRuleModule(
579
+ config.root,
580
+ rules,
581
+ resourcePath,
582
+ resourceQuery,
583
+ target,
584
+ config,
585
+ );
586
+ return loaded ?? undefined;
587
+ } catch (error) {
588
+ return { errors: [{ text: error instanceof Error ? error.message : String(error) }] };
589
+ }
590
+ });
591
+ }
592
+ },
593
+ };
594
+ }
595
+
596
+ const runtimeRegisteredRoots = new Set<string>();
597
+
598
+ /**
599
+ * Registers a Bun.plugin hook applying `turbopack.rules` loader chains (plus resolveAlias and query
600
+ * threading) for the SERVER runtime's own per-file module loading - the on-demand transform, not an
601
+ * esbuild build() pass. Idempotent per project root; a no-op when nothing is configured.
602
+ */
603
+ export function registerWebpackLoaderRuleRuntime(config: ResolvedConfig): void {
604
+ const rules = turbopackLoaderRules();
605
+ const suffixFilter = ruleSuffixFilter(rules);
606
+ const aliasFilter = aliasSpecifierFilter();
607
+ if (!suffixFilter && !aliasFilter) return;
608
+ const root = path.resolve(config.root);
609
+ if (runtimeRegisteredRoots.has(root)) return;
610
+ runtimeRegisteredRoots.add(root);
611
+ const ruleFileFilter = suffixFilter
612
+ ? new RegExp(`^${escapeRegExp(root)}(?:/.*)?${suffixFilter.source}`)
613
+ : undefined;
614
+ Bun.plugin({
615
+ name: `pnext-compat-webpack-loader-runtime-${root}`,
616
+ setup(plugin) {
617
+ // `?query`-suffixed specifiers whose base matches a loader rule: resolve
618
+ // the base (alias-aware), thread the query through the returned path so
619
+ // Bun keeps each `?query` import a distinct module (keyed by namespace +
620
+ // path). The onLoad below reads the query back off the path.
621
+ if (suffixFilter) {
622
+ plugin.onResolve({ filter: /\?/ }, args => {
623
+ const queryIndex = args.path.indexOf('?');
624
+ if (queryIndex === -1) return undefined;
625
+ const base = args.path.slice(0, queryIndex);
626
+ const query = args.path.slice(queryIndex);
627
+ const fromDir = args.importer ? path.dirname(args.importer) : root;
628
+ const resolved = resolveSpecifier(root, fromDir, base);
629
+ if (!resolved || !findRule(rules, resolved)) return undefined;
630
+ return { path: `${resolved}${QUERY_SEPARATOR}${query}`, namespace: QUERY_NAMESPACE };
631
+ });
632
+ }
633
+ // `resolveAlias` specifier rewrites (without a query — query handled above).
634
+ if (aliasFilter) {
635
+ plugin.onResolve({ filter: aliasFilter }, args => {
636
+ if (args.path.includes('?')) return undefined;
637
+ // See the build-time alias hook: pnext's own runtime graph is exempt
638
+ // from user aliases, or the react shim self-imports.
639
+ if (isPNextRuntimeImporter(args.importer)) return undefined;
640
+ const resolved = resolveAliasSpecifier(args.path, root);
641
+ if (!resolved) return undefined;
642
+ const target = firstExisting(resolved) ?? resolved;
643
+ // Bare alias target: resolve through Bun's resolver relative to the
644
+ // importer so a specifier like 'react' becomes a real file path.
645
+ if (!path.isAbsolute(target)) {
646
+ if (target === args.path) return undefined;
647
+ const fromDir = args.importer ? path.dirname(args.importer) : root;
648
+ try {
649
+ return { path: Bun.resolveSync(target, fromDir) };
650
+ } catch {
651
+ return undefined;
652
+ }
653
+ }
654
+ return { path: target };
655
+ });
656
+ }
657
+ plugin.onLoad({ filter: /.*/, namespace: QUERY_NAMESPACE }, async ({ path: virtualPath }) => {
658
+ const sep = virtualPath.indexOf(QUERY_SEPARATOR);
659
+ const realPath = sep === -1 ? virtualPath : virtualPath.slice(0, sep);
660
+ const resourceQuery = sep === -1 ? '' : virtualPath.slice(sep + 1);
661
+ const loaded = await loadRuleModule(root, rules, realPath, resourceQuery, 'server', config);
662
+ return loaded ?? { contents: await readFileP(realPath, 'utf8'), loader: 'text' };
663
+ });
664
+ // `.mjs` sources are NOT claimed by core's runtime load hook (its filter is `[jt]sx?`), so they
665
+ // never see the registered server source transforms - including the resolveAlias rewrite. Bun then
666
+ // resolves their bare imports natively and an aliased specifier throws, since its onResolve hook is
667
+ // never consulted for bare specifiers. Claim them here; Bun rejects an undefined onLoad return, so
668
+ // the hook always hands the source back. Kept synchronous, and `.cjs` deliberately left out: an
669
+ // onLoad hook turns the module async, which breaks require() of the materialized next.config.
670
+ if (aliasFilter) {
671
+ const esmFilter = new RegExp(`^${escapeRegExp(root)}(?!.*/node_modules/)(?:/.*)?\\.mjs$`);
672
+ plugin.onLoad({ filter: esmFilter }, ({ path: file }) => ({
673
+ contents: rewriteResolveAliasImports(readFileSync(file, 'utf8'), file, root),
674
+ loader: 'js',
675
+ }));
676
+ }
677
+ if (ruleFileFilter) {
678
+ plugin.onLoad({ filter: ruleFileFilter }, async ({ path: file }) => {
679
+ const loaded = await loadRuleModule(root, rules, path.resolve(file), '', 'server', config);
680
+ return loaded ?? { contents: await readFileP(file, 'utf8'), loader: 'text' };
681
+ });
682
+ }
683
+ },
684
+ });
685
+ }