@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,697 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { writeFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { build, type Plugin } from 'esbuild';
5
+ import { ensureDir, readText } from '../utils/fs';
6
+ import { postcssConfigFile, runPostcss } from './postcss';
7
+ import { extraPageExtensions, getCssExtensions } from '../extensions';
8
+ import { resolveImport } from '../resolve/imports';
9
+ import { createVerboseLogger } from '../utils/verbose';
10
+ import type { ClientReference } from '../client/reference';
11
+ import type { ResolvedConfig } from '../config';
12
+ import type { RouteManifestEntry } from '../types';
13
+
14
+ import type { CssWorkerRequest, CssWorkerResponse } from './worker';
15
+
16
+ interface Pending {
17
+ resolve: () => void;
18
+ reject: (error: Error) => void;
19
+ }
20
+
21
+ // Bun's Worker carries node's ref/unref; the DOM lib typing does not.
22
+ type CssWorker = Worker & { ref(): void; unref(): void };
23
+
24
+ let worker: CssWorker | undefined;
25
+ let workerFailed = false;
26
+ let nextId = 0;
27
+ const pending = new Map<number, Pending>();
28
+
29
+ function ensureWorker() {
30
+ if (worker || workerFailed) return worker;
31
+ try {
32
+ worker = new Worker(new URL('./worker.ts', import.meta.url).href, {
33
+ type: 'module',
34
+ }) as CssWorker;
35
+ } catch {
36
+ workerFailed = true;
37
+ return undefined;
38
+ }
39
+ // Idle workers must not hold `pnext build` (or a test run) open; each in-flight
40
+ // request refs it again.
41
+ worker.unref();
42
+ worker.onmessage = (event: MessageEvent<CssWorkerResponse>) => {
43
+ const { id, error } = event.data;
44
+ const entry = pending.get(id);
45
+ if (!entry) return;
46
+ pending.delete(id);
47
+ if (pending.size === 0) worker?.unref();
48
+ if (!error) return entry.resolve();
49
+ const failure = new Error(error.message);
50
+ failure.name = error.name;
51
+ if (error.stack) failure.stack = error.stack;
52
+ entry.reject(failure);
53
+ };
54
+ worker.onerror = () => failWorker(new Error('pnext CSS worker crashed'));
55
+ return worker;
56
+ }
57
+
58
+ // A dead worker takes its processor cache with it; drop it and let the next
59
+ // request fall back in-process rather than hanging every pending build.
60
+ function failWorker(error: Error) {
61
+ workerFailed = true;
62
+ worker = undefined;
63
+ for (const entry of pending.values()) entry.reject(error);
64
+ pending.clear();
65
+ }
66
+
67
+ function send(request: (id: number) => CssWorkerRequest) {
68
+ const active = ensureWorker();
69
+ if (!active) return undefined;
70
+ const id = nextId++;
71
+ return new Promise<void>((resolve, reject) => {
72
+ pending.set(id, { resolve, reject });
73
+ active.ref();
74
+ active.postMessage(request(id));
75
+ });
76
+ }
77
+
78
+ /** Run the app's postcss pipeline over a built stylesheet, off the event loop. */
79
+ export async function runPostcssOffThread(
80
+ config: Pick<ResolvedConfig, 'root' | 'outPath'>,
81
+ cssFile: string,
82
+ options: { dev?: boolean },
83
+ from?: string,
84
+ ) {
85
+ const { root, outPath: outDir } = config;
86
+ const dev = options.dev ?? false;
87
+ const sent = send(id => ({ kind: 'process', id, root, dev, outDir, cssFile, from }));
88
+ if (sent) return sent;
89
+ return runPostcss(root, cssFile, { dev, outDir }, from);
90
+ }
91
+
92
+ /**
93
+ * Spawn the worker and load the app's postcss config ahead of the first
94
+ * stylesheet, so a cold Tailwind boot overlaps with the rest of dev startup.
95
+ */
96
+ export function warmCssWorker(
97
+ config: Pick<ResolvedConfig, 'root' | 'outPath'>,
98
+ options: { dev?: boolean } = {},
99
+ ) {
100
+ send(id => ({
101
+ kind: 'warm',
102
+ id,
103
+ root: config.root,
104
+ dev: options.dev ?? false,
105
+ outDir: config.outPath,
106
+ }))?.catch(() => {
107
+ // A broken postcss config surfaces on the real build; warming stays quiet.
108
+ });
109
+ }
110
+
111
+ interface CssBuildOptions {
112
+ dev?: boolean;
113
+ verbose?: boolean;
114
+ }
115
+
116
+ let cssRuntimeRegistered = false;
117
+
118
+ export function globalCssSource(config: ResolvedConfig) {
119
+ return globalCssSources(config)[0];
120
+ }
121
+
122
+ export function globalCssSources(config: Pick<ResolvedConfig, 'root' | 'appPath'>) {
123
+ return globalCssSourcesForPaths(config.root, config.appPath);
124
+ }
125
+
126
+ // The walk below reads and resolves the ROOT LAYOUT'S WHOLE IMPORT GRAPH, and every render asks it
127
+ // (via globalCssHref) whether the app has global CSS at all. Memoize per root+appPath; dev clears
128
+ // this on save (invalidateDevCaches), prod sources never change in-process.
129
+ const globalCssSourceCache = new Map<string, string[]>();
130
+
131
+ /**
132
+ * Restart half of the walk above: the store persists the answer keyed by every
133
+ * file the walk *read*, so a restart re-states it instead of re-reading the
134
+ * graph. `undefined` (prod, or the cache off) simply means always walk.
135
+ */
136
+ export interface GlobalCssSourceStore {
137
+ load(key: string): string[] | undefined;
138
+ save(key: string, sources: string[], visited: string[], missing: string[]): void;
139
+ }
140
+
141
+ let globalCssStore: GlobalCssSourceStore | undefined;
142
+
143
+ export function setGlobalCssSourceStore(store: GlobalCssSourceStore | undefined) {
144
+ globalCssStore = store;
145
+ }
146
+
147
+ export function globalCssSourcesForPaths(root: string, appPath: string) {
148
+ const key = `${root}\0${appPath}`;
149
+ const cached = globalCssSourceCache.get(key);
150
+ if (cached) return cached;
151
+
152
+ const stored = globalCssStore?.load(key);
153
+ if (stored) {
154
+ globalCssSourceCache.set(key, stored);
155
+ return stored;
156
+ }
157
+
158
+ const sources = new Set<string>();
159
+ const visited = new Set<string>();
160
+ const missing = new Set<string>();
161
+ collectGlobalCssSources(root, rootLayoutFile(appPath, missing), sources, visited, missing);
162
+
163
+ const resolved = [...sources].map(file => path.resolve(file));
164
+ globalCssSourceCache.set(key, resolved);
165
+ // The answer depends on every file the walk READ (a new `import './x.css'`
166
+ // anywhere in the layout graph changes its importer's bytes) and on every
167
+ // path it found ABSENT (restoring one while the server was down would add an
168
+ // edge no importer's bytes record).
169
+ globalCssStore?.save(key, resolved, [...visited], [...missing]);
170
+ return resolved;
171
+ }
172
+
173
+ /** Drop the memoized layout-graph walk (dev save invalidation). */
174
+ export function clearGlobalCssSourceCache() {
175
+ globalCssSourceCache.clear();
176
+ }
177
+
178
+ function collectGlobalCssSources(
179
+ root: string,
180
+ file: string,
181
+ sources: Set<string>,
182
+ visited: Set<string>,
183
+ missing?: Set<string>,
184
+ ) {
185
+ const resolvedFile = path.resolve(file);
186
+ if (visited.has(resolvedFile)) return;
187
+ if (!existsSync(resolvedFile)) {
188
+ missing?.add(resolvedFile);
189
+ return;
190
+ }
191
+ visited.add(resolvedFile);
192
+
193
+ for (const specifier of moduleSpecifiers(readFileSync(resolvedFile, 'utf8'))) {
194
+ const resolved = resolveImport(root, resolvedFile, specifier);
195
+ if (!resolved) continue;
196
+ if (isCssFile(resolved)) {
197
+ sources.add(resolved);
198
+ } else {
199
+ collectGlobalCssSources(root, resolved, sources, visited, missing);
200
+ }
201
+ }
202
+ }
203
+
204
+ function rootLayoutFile(appPath: string, missing?: Set<string>) {
205
+ for (const extension of ['tsx', 'ts', 'jsx', 'js', 'mjs', ...extraPageExtensions()]) {
206
+ const file = path.join(appPath, `layout.${extension}`);
207
+ if (existsSync(file)) return file;
208
+ missing?.add(file);
209
+ }
210
+ return path.join(appPath, 'layout.tsx');
211
+ }
212
+
213
+ export function globalCssHref(config: ResolvedConfig) {
214
+ return globalCssSources(config).length > 0
215
+ ? withAssetPrefix(config, '/assets/global.css')
216
+ : undefined;
217
+ }
218
+
219
+ export function routeCssHref(
220
+ route: RouteManifestEntry,
221
+ config?: Pick<ResolvedConfig, 'assetPrefix'>,
222
+ ): string | string[] | undefined {
223
+ if (route.cssImports.length === 0) return undefined;
224
+ if (route.cssAssets?.length) {
225
+ return route.cssAssets.map(asset => withAssetPrefix(config, `/assets/${asset}`));
226
+ }
227
+ return withAssetPrefix(config, `/assets/${route.id}.css`);
228
+ }
229
+
230
+ // Prepend the configured assetPrefix (a CDN origin or path) to an app-absolute
231
+ // asset URL. Link hrefs use basePath instead and must NOT go through here.
232
+ export function withAssetPrefix(
233
+ config: Pick<ResolvedConfig, 'assetPrefix'> | undefined,
234
+ href: string,
235
+ ) {
236
+ const prefix = config?.assetPrefix;
237
+ if (!prefix) return href;
238
+ return `${prefix.replace(/\/$/, '')}${href}`;
239
+ }
240
+
241
+ /**
242
+ * Spawn the CSS worker and load the app's postcss config before any stylesheet
243
+ * is asked for, so Tailwind's cold boot overlaps with the rest of startup
244
+ * instead of landing on the first request. No-op without a postcss config.
245
+ */
246
+ export function warmCssPipeline(
247
+ config: Pick<ResolvedConfig, 'root' | 'outPath'>,
248
+ options: CssBuildOptions = {},
249
+ ) {
250
+ if (!hasPostcssConfig(config.root)) return;
251
+ warmCssWorker(config, options);
252
+ }
253
+
254
+ export async function buildGlobalCss(config: ResolvedConfig, options: CssBuildOptions = {}) {
255
+ const log = createVerboseLogger(options.verbose ?? false, 'css');
256
+ const sources = globalCssSources(config);
257
+ if (sources.length === 0) {
258
+ log.log('global css: no side-effect css imports in root layout, skipping');
259
+ return undefined;
260
+ }
261
+
262
+ const outDir = options.dev
263
+ ? path.join(config.outPath, 'cache', 'assets')
264
+ : path.join(config.outPath, 'public', 'assets');
265
+ const outfile = path.join(outDir, 'global.css');
266
+ await ensureDir(outDir);
267
+
268
+ log.log(
269
+ `global css: ${sources.length} source${sources.length === 1 ? '' : 's'} — ${sources
270
+ .map(file => path.relative(config.root, file))
271
+ .join(', ')}`,
272
+ );
273
+
274
+ await log.step('global css: esbuild bundle', () =>
275
+ build({
276
+ stdin: {
277
+ contents: sources.map(file => `@import ${JSON.stringify(file)};`).join('\n'),
278
+ loader: 'css',
279
+ resolveDir: config.root,
280
+ sourcefile: 'global.css',
281
+ },
282
+ outfile,
283
+ bundle: true,
284
+ minify: !options.dev,
285
+ conditions: ['style', 'browser'],
286
+ assetNames: 'files/[name]-[hash]',
287
+ plugins: getCssExtensions().cssChunkPlugins(),
288
+ loader: {
289
+ '.css': 'css',
290
+ '.module.css': 'css',
291
+ ...cssLoaderEntries(),
292
+ '.woff': 'file',
293
+ '.woff2': 'file',
294
+ '.ttf': 'file',
295
+ '.otf': 'file',
296
+ '.eot': 'file',
297
+ '.png': 'file',
298
+ '.svg': 'file',
299
+ '.ico': 'file',
300
+ },
301
+ }),
302
+ );
303
+ if (hasPostcssConfig(config.root)) {
304
+ await rewriteTailwindConfigDirectives(outfile, sources);
305
+ await log.step('global css: postcss', () =>
306
+ runPostcssOffThread(config, outfile, options, sources[0]),
307
+ );
308
+ }
309
+
310
+ log.log(`global css → ${path.relative(config.root, outfile)}`);
311
+ return outfile;
312
+ }
313
+
314
+ function moduleSpecifiers(source: string) {
315
+ const specifiers: string[] = [];
316
+ const pattern = /(?:import|export)\s+(?:[^'";]+?\s+from\s+)?['"]([^'"]+)['"]/g;
317
+ let match: RegExpExecArray | null;
318
+
319
+ while ((match = pattern.exec(source))) {
320
+ if (match[1]) specifiers.push(match[1]);
321
+ }
322
+
323
+ return specifiers;
324
+ }
325
+
326
+ function cssFileExtensions() {
327
+ return ['.css', ...getCssExtensions().extraCssExtensions()];
328
+ }
329
+
330
+ export function isCssFile(file: string) {
331
+ return cssFileExtensions().some(extension => file.endsWith(extension));
332
+ }
333
+
334
+ function cssLoaderEntries() {
335
+ return Object.fromEntries(
336
+ getCssExtensions().extraCssExtensions().map(extension => [extension, 'css'] as const),
337
+ );
338
+ }
339
+
340
+ // The compat CSS-chunk plan (Next `experimental.cssChunking`), computed once
341
+ // across all routes by prepareRouteCssChunks before the per-route build loop.
342
+ // Pure-core apps never populate it, so every route keeps its single chunk.
343
+ const routeCssPlan = new Map<string, string[][]>();
344
+
345
+ // Partition each route's ordered cssImports into chunk slices via the compat
346
+ // registry (Next's CSS chunking). Must run before buildRouteCss so the loop can
347
+ // emit one asset per slice; a no-op for pure-core apps (empty plan).
348
+ export function prepareRouteCssChunks(
349
+ routes: Pick<RouteManifestEntry, 'id' | 'cssImports'>[],
350
+ ) {
351
+ routeCssPlan.clear();
352
+ const plan = getCssExtensions().planRouteCssChunks(
353
+ routes.map(route => ({ id: route.id, cssImports: route.cssImports })),
354
+ );
355
+ for (const [id, segments] of plan) routeCssPlan.set(id, segments);
356
+ }
357
+
358
+ export async function buildRouteCss(
359
+ config: ResolvedConfig,
360
+ route: RouteManifestEntry,
361
+ options: CssBuildOptions = {},
362
+ ) {
363
+ if (route.cssImports.length === 0) return undefined;
364
+ const segments = routeCssPlan.get(route.id);
365
+ if (segments && segments.length > 1) {
366
+ const assetNames = segments.map((_, index) => `${route.id}-${index}.css`);
367
+ route.cssAssets = assetNames;
368
+ const files: string[] = [];
369
+ for (const [index, segment] of segments.entries()) {
370
+ files.push(
371
+ await bundleCssChunk(
372
+ config,
373
+ segment,
374
+ assetNames[index]!,
375
+ `route css ${route.route} #${index + 1}`,
376
+ options,
377
+ ),
378
+ );
379
+ }
380
+ return files;
381
+ }
382
+ route.cssAssets = undefined;
383
+ return bundleCssChunk(config, route.cssImports, `${route.id}.css`, `route css ${route.route}`, options);
384
+ }
385
+
386
+ // The synthetic document conventions are created at render time and never
387
+ // appear in the scanned manifest, so their stylesheet chunks aren't emitted by
388
+ // the per-route CSS build loop. Build them explicitly:
389
+ // an unmatched URL rendering the root not-found ships `/assets/not-found.css`
390
+ // (its own CSS) alongside `/assets/global.css` (the root layout's CSS).
391
+ export async function buildNotFoundCss(config: ResolvedConfig, options: CssBuildOptions = {}) {
392
+ const { collectFileCss } = await import('../routing/routes');
393
+ const targets: { id: string; files: string[]; includeGlobalCss?: boolean }[] = [
394
+ {
395
+ id: 'not-found',
396
+ files: conventionCandidates(config.appPath, 'not-found'),
397
+ },
398
+ {
399
+ id: 'global-not-found',
400
+ files: conventionCandidates(config.appPath, 'global-not-found'),
401
+ // global-not-found replaces the whole document (no root layout), so CSS
402
+ // it shares with the root layout must ship in its own chunk — the global
403
+ // sheet is not linked on that document.
404
+ includeGlobalCss: true,
405
+ },
406
+ {
407
+ id: 'global-error',
408
+ files: conventionCandidates(config.appPath, 'global-error'),
409
+ },
410
+ ];
411
+ for (const target of targets) {
412
+ const cssImports = await collectFileCss(config.appPath, target.files, {
413
+ includeGlobalCss: target.includeGlobalCss ?? false,
414
+ });
415
+ if (cssImports.length === 0) continue;
416
+ await bundleCssChunk(config, cssImports, `${target.id}.css`, `route css /${target.id}`, options);
417
+ }
418
+ }
419
+
420
+ function conventionCandidates(appPath: string, base: string) {
421
+ return ['tsx', 'ts', 'jsx', 'js'].map(ext => path.join(appPath, `${base}.${ext}`));
422
+ }
423
+
424
+ // CSS reachable only through a non-SSR dynamic island ships as its own chunk
425
+ // (assets/<reference-id>.css), fetched by the client entry when the island
426
+ // loads instead of blocking the route stylesheet.
427
+ export async function buildClientReferenceCss(
428
+ config: ResolvedConfig,
429
+ reference: ClientReference,
430
+ options: CssBuildOptions = {},
431
+ ) {
432
+ if (!reference.cssImports || reference.cssImports.length === 0) return undefined;
433
+ return bundleCssChunk(
434
+ config,
435
+ reference.cssImports,
436
+ `${reference.id}.css`,
437
+ `island css ${reference.id}`,
438
+ options,
439
+ );
440
+ }
441
+
442
+ async function bundleCssChunk(
443
+ config: ResolvedConfig,
444
+ cssImports: string[],
445
+ assetName: string,
446
+ label: string,
447
+ options: CssBuildOptions,
448
+ ) {
449
+ const log = createVerboseLogger(options.verbose ?? false, 'css');
450
+
451
+ const outDir = options.dev
452
+ ? path.join(config.outPath, 'cache', 'assets')
453
+ : path.join(config.outPath, 'public', 'assets');
454
+ const outfile = path.join(outDir, assetName);
455
+ await ensureDir(outDir);
456
+
457
+ log.log(`${label}: ${cssImports.length} import${cssImports.length === 1 ? '' : 's'}`);
458
+
459
+ await build({
460
+ stdin: {
461
+ contents: cssImports.map(file => `@import ${JSON.stringify(file)};`).join('\n'),
462
+ loader: 'css',
463
+ resolveDir: config.root,
464
+ sourcefile: assetName,
465
+ },
466
+ outfile,
467
+ bundle: true,
468
+ minifyWhitespace: !options.dev,
469
+ minifySyntax: !options.dev,
470
+ minifyIdentifiers: false,
471
+ conditions: ['style', 'browser'],
472
+ assetNames: 'files/[name]-[hash]',
473
+ // Lazy registry read: compat (sass) plugins register at bootstrap.
474
+ plugins: [...getCssExtensions().cssChunkPlugins(), cssModuleBuildPlugin()],
475
+ loader: {
476
+ '.css': 'css',
477
+ '.module.css': 'css',
478
+ ...cssLoaderEntries(),
479
+ '.woff': 'file',
480
+ '.woff2': 'file',
481
+ '.ttf': 'file',
482
+ '.otf': 'file',
483
+ '.eot': 'file',
484
+ '.png': 'file',
485
+ '.svg': 'file',
486
+ '.ico': 'file',
487
+ },
488
+ });
489
+
490
+ if (hasPostcssConfig(config.root)) {
491
+ await rewriteTailwindConfigDirectives(outfile, cssImports);
492
+ await log.step(`${label}: postcss`, () => runPostcssOffThread(config, outfile, options));
493
+ }
494
+
495
+ return outfile;
496
+ }
497
+
498
+ export function registerCssRuntime() {
499
+ if (cssRuntimeRegistered) return;
500
+ cssRuntimeRegistered = true;
501
+
502
+ Bun.plugin({
503
+ name: 'pnext-css-runtime',
504
+ setup(plugin) {
505
+ plugin.onLoad({ filter: /\.(?:css|scss|sass)$/ }, async ({ path: file }) =>
506
+ isCssModuleFile(file)
507
+ ? {
508
+ exports: { default: await cssModuleMapping(file) },
509
+ loader: 'object',
510
+ }
511
+ : {
512
+ contents: 'export default undefined;',
513
+ loader: 'js',
514
+ },
515
+ );
516
+ },
517
+ });
518
+ }
519
+
520
+ // Sass is unsupported (no compiler); its imports still must not break the
521
+ // bundle, so .scss/.sass load as empty modules with module class-name maps.
522
+ function isCssModuleFile(file: string) {
523
+ return /\.module\.(?:css|scss|sass)$/.test(file);
524
+ }
525
+
526
+ export function cssModuleClientPlugin(): Plugin {
527
+ return {
528
+ name: 'pnext-css-modules',
529
+ setup(plugin) {
530
+ plugin.onLoad({ filter: /\.(?:css|scss|sass)$/ }, async ({ path: file }) => ({
531
+ contents: isCssModuleFile(file)
532
+ ? `export default ${JSON.stringify(await cssModuleMapping(file))};`
533
+ : 'export default undefined;',
534
+ loader: 'js',
535
+ }));
536
+ },
537
+ };
538
+ }
539
+
540
+ function cssModuleBuildPlugin(): Plugin {
541
+ return {
542
+ name: 'pnext-css-module-build',
543
+ setup(plugin) {
544
+ plugin.onLoad({ filter: /\.module\.css$/ }, async ({ path: file }) => ({
545
+ contents: transformCssModule(await readText(file), file),
546
+ loader: 'css',
547
+ }));
548
+ },
549
+ };
550
+ }
551
+
552
+ async function cssModuleMapping(file: string) {
553
+ // Compat handles *.module.{scss,sass} (sass compiles + scopes); core keeps
554
+ // plain .module.css. Lazy read: the registry populates at compat bootstrap.
555
+ const compatMapping = getCssExtensions().resolveCssModule(file);
556
+ if (compatMapping) return compatMapping;
557
+ const source = await readText(file);
558
+ const classNames = cssClassNames(source);
559
+
560
+ return Object.fromEntries(
561
+ [...classNames].map(className => [className, cssModuleClassName(file, className)]),
562
+ );
563
+ }
564
+
565
+ function cssClassNames(source: string) {
566
+ const classNames = new Set<string>();
567
+ const classPattern = /(^|[^\\])\.(-?[_a-zA-Z][\w-]*)/g;
568
+ let match: RegExpExecArray | null;
569
+
570
+ while ((match = classPattern.exec(source))) {
571
+ if (match[2]) classNames.add(match[2]);
572
+ }
573
+
574
+ return classNames;
575
+ }
576
+
577
+ function transformCssModule(source: string, file: string) {
578
+ let css = source;
579
+ for (const className of cssClassNames(source)) {
580
+ css = rewriteClassSelector(css, cssModuleClassName(file, className), className);
581
+ }
582
+ css = scopeKeyframes(css, file);
583
+ return css;
584
+ }
585
+
586
+ // CSS-module `@keyframes` names are scoped like class names, so a local animation `example` from two
587
+ // modules must not collide. The `@keyframes` declaration AND every `animation-name`/`animation` shorthand
588
+ // reference to it are rewritten to the scoped name. `grid-area` names are NOT identifiers of this kind and
589
+ // are left untouched.
590
+ function scopeKeyframes(css: string, file: string) {
591
+ const names = keyframeNames(css);
592
+ if (names.size === 0) return css;
593
+
594
+ let out = css;
595
+ for (const name of names) {
596
+ const scoped = cssModuleClassName(file, name);
597
+ // Rename the @keyframes / @-webkit-keyframes block name.
598
+ out = out.replace(
599
+ new RegExp(`(@(?:-webkit-)?keyframes\\s+)${escapeRegex(name)}(?![-_a-zA-Z0-9])`, 'g'),
600
+ `$1${scoped}`,
601
+ );
602
+ // Rename `animation-name: name`.
603
+ out = out.replace(
604
+ new RegExp(
605
+ `(animation-name\\s*:\\s*)${escapeRegex(name)}(?![-_a-zA-Z0-9])`,
606
+ 'g',
607
+ ),
608
+ `$1${scoped}`,
609
+ );
610
+ // Rename the name inside an `animation:` shorthand (token-bounded).
611
+ out = out.replace(
612
+ new RegExp(
613
+ `(animation\\s*:[^;{}]*?(?<![-_a-zA-Z0-9]))${escapeRegex(name)}(?![-_a-zA-Z0-9])`,
614
+ 'g',
615
+ ),
616
+ `$1${scoped}`,
617
+ );
618
+ }
619
+ return out;
620
+ }
621
+
622
+ function keyframeNames(css: string) {
623
+ const names = new Set<string>();
624
+ const pattern = /@(?:-webkit-)?keyframes\s+(-?[_a-zA-Z][\w-]*)/g;
625
+ let match: RegExpExecArray | null;
626
+ while ((match = pattern.exec(css))) {
627
+ if (match[1]) names.add(match[1]);
628
+ }
629
+ return names;
630
+ }
631
+
632
+ function cssModuleClassName(file: string, className: string) {
633
+ const base = path
634
+ .basename(file)
635
+ .replace(/\.module\.css$/, '')
636
+ .replace(/[^_a-zA-Z0-9]/g, '_');
637
+ const hash = pathHash(cssModuleScopePath(file));
638
+ return `${base}_${className}_${hash}`;
639
+ }
640
+
641
+ function cssModuleScopePath(file: string) {
642
+ const normalized = file.split(path.sep).join('/');
643
+ const appIndex = normalized.lastIndexOf('/app/');
644
+ if (appIndex !== -1) return normalized.slice(appIndex + 1);
645
+ return normalized.split('/').slice(-3).join('/');
646
+ }
647
+
648
+ function rewriteClassSelector(css: string, scopedClassName: string, originalClassName: string) {
649
+ const esbuildNamePattern = new RegExp(
650
+ `\\.${escapeRegex(originalClassName)}(?![-_a-zA-Z0-9])`,
651
+ 'g',
652
+ );
653
+ return css.replace(esbuildNamePattern, `.${scopedClassName}`);
654
+ }
655
+
656
+ function pathHash(file: string) {
657
+ let hash = 5381;
658
+ for (const char of file) hash = ((hash << 5) + hash) ^ char.charCodeAt(0);
659
+ return (hash >>> 0).toString(36).slice(0, 5);
660
+ }
661
+
662
+ function escapeRegex(value: string) {
663
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
664
+ }
665
+
666
+ function hasPostcssConfig(root: string) {
667
+ return postcssConfigFile(root) !== undefined;
668
+ }
669
+
670
+ // Bundling moves the CSS away from the directory its `@config` paths were
671
+ // written against, and Tailwind resolves them against postcss's `from` (the
672
+ // original source, not the outfile). Absolute paths sidestep both bases.
673
+ export async function rewriteTailwindConfigDirectives(cssFile: string, sources: string[]) {
674
+ const candidateDirs = [path.dirname(cssFile), ...sources.map(file => path.dirname(file))];
675
+ let css = await readText(cssFile);
676
+ let changed = false;
677
+
678
+ css = css.replace(
679
+ /@config\s+(['"])([^'"]+)\1\s*;/g,
680
+ (match, quote: string, specifier: string) => {
681
+ if (!isRelativeSpecifier(specifier)) return match;
682
+ const resolved = candidateDirs
683
+ .map(dir => path.resolve(dir, specifier))
684
+ .find(file => existsSync(file));
685
+ if (!resolved) return match;
686
+
687
+ changed = true;
688
+ return `@config ${quote}${resolved.split(path.sep).join('/')}${quote};`;
689
+ },
690
+ );
691
+
692
+ if (changed) await writeFile(cssFile, css);
693
+ }
694
+
695
+ function isRelativeSpecifier(specifier: string) {
696
+ return specifier.startsWith('./') || specifier.startsWith('../');
697
+ }
@@ -0,0 +1,2 @@
1
+ // Public surface of the CSS pipeline; implementation in ./build.
2
+ export * from './build';