@wular/pnext 0.0.3 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (392) hide show
  1. package/README.md +11 -11
  2. package/bin/pnext +1 -1
  3. package/config/lint/base.js +7 -7
  4. package/config/ts/base.json +2 -4
  5. package/config/ts/react.json +2 -6
  6. package/package.json +23 -2
  7. package/reference/compat.md +1 -1
  8. package/reference/config.md +2 -2
  9. package/reference/css.md +13 -9
  10. package/reference/dev.md +1 -1
  11. package/reference/metadata.md +7 -7
  12. package/reference/navigation.md +35 -28
  13. package/reference/performance.md +70 -70
  14. package/reference/rendering.md +17 -17
  15. package/reference/routing.md +17 -17
  16. package/reference/typegen.md +12 -8
  17. package/src/api/cache.ts +36 -37
  18. package/src/api/client-cache.ts +2 -2
  19. package/src/api/client-navigation.ts +113 -109
  20. package/src/api/dynamic.tsx +58 -55
  21. package/src/api/link.tsx +49 -52
  22. package/src/api/navigation.ts +59 -58
  23. package/src/api/server.ts +152 -144
  24. package/src/api/suspense.ts +4 -4
  25. package/src/cli/adapters/vercel-warm.ts +126 -121
  26. package/src/cli/adapters/vercel.ts +437 -443
  27. package/src/cli/analyze.ts +333 -144
  28. package/src/cli/{named-bin.ts → boot/named-bin.ts} +37 -37
  29. package/src/cli/{boot-trace.ts → boot/trace.ts} +10 -10
  30. package/src/cli/build.ts +976 -978
  31. package/src/cli/create.ts +71 -63
  32. package/src/cli/dev.ts +116 -116
  33. package/src/cli/index.ts +88 -85
  34. package/src/cli/migrate/package-json.ts +73 -71
  35. package/src/cli/migrate/report.ts +32 -33
  36. package/src/cli/migrate/{index.ts → run.ts} +48 -49
  37. package/src/cli/migrate/scan.ts +46 -46
  38. package/src/cli/migrate/spinner.ts +9 -9
  39. package/src/cli/migrate/tsconfig.ts +35 -35
  40. package/src/cli/{server-entry.ts → serve/entry.ts} +57 -57
  41. package/src/cli/{request-pipeline.ts → serve/pipeline.ts} +386 -394
  42. package/src/cli/{serve-ui.ts → serve/ui.ts} +47 -47
  43. package/src/cli/start.ts +65 -66
  44. package/src/{typegen.ts → cli/typegen.ts} +59 -59
  45. package/src/client/build.ts +794 -768
  46. package/src/client/chunk-fold.ts +245 -240
  47. package/src/client/{paths.ts → chunk-name.ts} +6 -6
  48. package/src/client/entry.ts +162 -147
  49. package/src/client/prebuilt.ts +231 -223
  50. package/src/client/profile.ts +29 -29
  51. package/src/client/react-compiler.ts +20 -15
  52. package/src/client/{compat-surface.ts → react-tier.ts} +64 -64
  53. package/src/client/reference-stub.ts +51 -51
  54. package/src/client/reference.ts +19 -19
  55. package/src/{api → client}/router/events.ts +17 -17
  56. package/src/{api → client}/router/history.ts +16 -16
  57. package/src/{api → client}/router/hub.ts +52 -53
  58. package/src/{api/router.ts → client/router/index.ts} +61 -60
  59. package/src/{api → client}/router/policies.ts +24 -24
  60. package/src/{api → client}/router/runtime.ts +1988 -1961
  61. package/src/{api → client}/router/types.ts +93 -98
  62. package/src/compat/actions/client-plugin.ts +42 -43
  63. package/src/compat/actions/client-stub.ts +14 -14
  64. package/src/compat/actions/{action-client.ts → client.ts} +194 -193
  65. package/src/compat/actions/config.ts +63 -64
  66. package/src/compat/actions/detect.ts +68 -68
  67. package/src/compat/actions/discovery.ts +105 -105
  68. package/src/compat/actions/{action-dispatch.ts → dispatch.ts} +277 -274
  69. package/src/compat/actions/early-submit.ts +1 -1
  70. package/src/compat/actions/endpoint.ts +198 -198
  71. package/src/compat/actions/extensions.ts +811 -0
  72. package/src/compat/actions/flight.ts +23 -23
  73. package/src/compat/actions/form-state.ts +34 -34
  74. package/src/compat/actions/hoist.ts +382 -236
  75. package/src/compat/actions/ids.ts +7 -12
  76. package/src/compat/actions/index.ts +8 -8
  77. package/src/compat/actions/instances.ts +46 -46
  78. package/src/compat/actions/origin.ts +47 -48
  79. package/src/compat/actions/protocol.ts +22 -22
  80. package/src/compat/actions/registry.ts +19 -19
  81. package/src/compat/{misc/action-return.ts → actions/return.ts} +60 -57
  82. package/src/compat/actions/rewrite.ts +125 -125
  83. package/src/compat/actions/{action-router.ts → router.ts} +10 -10
  84. package/src/compat/actions/serve.ts +132 -136
  85. package/src/compat/actions/server-tag.ts +4 -4
  86. package/src/compat/actions/{action-shared.ts → shared.ts} +34 -34
  87. package/src/compat/actions/unrecognized-error.ts +4 -4
  88. package/src/compat/{index.ts → aliases.ts} +112 -109
  89. package/src/compat/bundler/bun-externals.ts +18 -18
  90. package/src/compat/bundler/cjs-exports.ts +271 -223
  91. package/src/compat/bundler/config.ts +116 -111
  92. package/src/compat/bundler/externals.ts +12 -12
  93. package/src/compat/bundler/import-meta-url.ts +19 -19
  94. package/src/compat/bundler/modularize-imports.ts +36 -33
  95. package/src/compat/bundler/new-url-asset.ts +22 -24
  96. package/src/compat/bundler/optimize-package-imports.ts +111 -107
  97. package/src/compat/bundler/polyfill.ts +28 -28
  98. package/src/compat/bundler/react-compiler.ts +35 -29
  99. package/src/compat/bundler/react-profiler.tsx +11 -11
  100. package/src/compat/bundler/relay-transform.ts +48 -47
  101. package/src/compat/bundler/require-context.ts +119 -113
  102. package/src/compat/bundler/resolve-extensions.ts +19 -19
  103. package/src/compat/bundler/source-cache.ts +25 -25
  104. package/src/compat/bundler/static-imports.ts +7 -7
  105. package/src/compat/bundler/symlink-imports.ts +46 -46
  106. package/src/compat/bundler/tsconfig-paths.ts +13 -15
  107. package/src/compat/bundler/wasm.ts +58 -60
  108. package/src/compat/bundler/webpack-loaders.ts +254 -241
  109. package/src/compat/bundler/worker.ts +101 -104
  110. package/src/compat/cache/build-flags.ts +29 -29
  111. package/src/compat/cache/build-prerender-errors.ts +40 -44
  112. package/src/compat/cache/custom-handler.ts +60 -53
  113. package/src/compat/cache/fetch-patch.ts +240 -240
  114. package/src/compat/cache/handler.ts +27 -31
  115. package/src/compat/cache/modern-handler.ts +148 -126
  116. package/src/compat/cache/resume-data-cache.ts +47 -45
  117. package/src/compat/cache/revalidate.ts +233 -232
  118. package/src/compat/cache/runtime-error.ts +35 -35
  119. package/src/compat/cache/use-cache-transform.ts +442 -417
  120. package/src/compat/cache/use-cache.ts +650 -614
  121. package/src/compat/cache-control.ts +140 -142
  122. package/src/compat/client/base-path.ts +21 -20
  123. package/src/compat/client/css-order.ts +18 -18
  124. package/src/compat/client/errors/bare-boundary.ts +11 -11
  125. package/src/compat/client/errors/control-flow.ts +23 -23
  126. package/src/compat/client/errors/error-boundary.ts +61 -61
  127. package/src/compat/client/errors/global-error.ts +101 -91
  128. package/src/compat/client/errors/install.ts +71 -72
  129. package/src/compat/client/errors/lazy.ts +23 -23
  130. package/src/compat/client/errors/primitive-throw.ts +47 -45
  131. package/src/compat/client/errors/soft-refresh.ts +4 -4
  132. package/src/compat/client/link-status.ts +33 -33
  133. package/src/compat/client/{nav-compat-runtime.ts → nav-runtime.ts} +20 -20
  134. package/src/compat/client/{nav-compat.ts → nav.ts} +12 -13
  135. package/src/compat/client/navigation-scroll.ts +63 -63
  136. package/src/compat/client/optimistic-routing.ts +93 -88
  137. package/src/compat/client/prefetch-cache.ts +23 -24
  138. package/src/compat/client/route-announcer.ts +35 -35
  139. package/src/compat/client/segment-cache-policy.ts +20 -20
  140. package/src/compat/client/segment-cache.ts +309 -315
  141. package/src/compat/client/segment-prefetch.ts +127 -132
  142. package/src/compat/client/trailing-slash.ts +5 -4
  143. package/src/compat/css/chunking.ts +113 -116
  144. package/src/compat/css/inline-css.ts +21 -21
  145. package/src/compat/css/lightningcss.ts +37 -38
  146. package/src/compat/css/modules.ts +161 -175
  147. package/src/compat/css/nonce.ts +7 -7
  148. package/src/compat/css/sass-plugin.ts +18 -21
  149. package/src/compat/css/sass.ts +150 -152
  150. package/src/compat/css/styled-jsx-runtime.ts +27 -27
  151. package/src/compat/css/styled-jsx.ts +21 -21
  152. package/src/compat/edge-runtime.ts +27 -27
  153. package/src/compat/{adapter → export}/build-complete.ts +72 -75
  154. package/src/compat/export/client.ts +29 -31
  155. package/src/compat/export/{index.ts → emit.ts} +111 -110
  156. package/src/compat/export/standalone.ts +62 -54
  157. package/src/compat/image-optimizer/cache.ts +51 -49
  158. package/src/compat/image-optimizer/detect.ts +52 -52
  159. package/src/compat/image-optimizer/{index.ts → optimize.ts} +189 -194
  160. package/src/compat/image-optimizer/source.ts +77 -80
  161. package/src/compat/lifecycle/after-scope.ts +26 -26
  162. package/src/compat/lifecycle/after.ts +48 -45
  163. package/src/compat/lifecycle/error-funnel.ts +98 -102
  164. package/src/compat/lifecycle/error-serialize.ts +30 -26
  165. package/src/compat/lifecycle/error-ui.ts +67 -32
  166. package/src/compat/lifecycle/instrumentation-client.ts +36 -38
  167. package/src/compat/lifecycle/instrumentation.ts +85 -85
  168. package/src/compat/lifecycle/node-console.ts +11 -11
  169. package/src/compat/lifecycle/testmode.ts +160 -132
  170. package/src/compat/mdx/compile.ts +60 -57
  171. package/src/compat/mdx/plugin.ts +11 -11
  172. package/src/compat/mdx/{next-mdx-stub.ts → stub.ts} +5 -5
  173. package/src/compat/{metadata-route-artifacts.ts → metadata-artifacts.ts} +195 -191
  174. package/src/compat/metadata.ts +75 -81
  175. package/src/compat/next/cache.ts +68 -69
  176. package/src/compat/next/canonical-url.ts +9 -9
  177. package/src/compat/next/client-cache.ts +19 -19
  178. package/src/compat/next/client-navigation.ts +112 -116
  179. package/src/compat/next/client-only.ts +1 -1
  180. package/src/compat/next/client-script.tsx +99 -93
  181. package/src/compat/next/client-server.ts +10 -10
  182. package/src/compat/next/config-loader.ts +176 -173
  183. package/src/compat/next/config.ts +7 -7
  184. package/src/compat/next/constants.cjs +6 -6
  185. package/src/compat/next/constants.ts +6 -6
  186. package/src/compat/next/custom-server.ts +26 -24
  187. package/src/compat/next/dist/client/components/app-router-headers.ts +21 -21
  188. package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +3 -4
  189. package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -1
  190. package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -1
  191. package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -1
  192. package/src/compat/next/dynamic.tsx +21 -18
  193. package/src/compat/next/error.tsx +52 -52
  194. package/src/compat/next/font/cache.ts +74 -74
  195. package/src/compat/next/font/google.ts +2 -2
  196. package/src/compat/next/font/index.ts +1 -1
  197. package/src/compat/next/font/local.ts +3 -3
  198. package/src/compat/next/font/runtime-client.ts +17 -15
  199. package/src/compat/next/font/runtime.ts +443 -408
  200. package/src/compat/next/font/shared.ts +120 -108
  201. package/src/compat/next/form.tsx +63 -63
  202. package/src/compat/next/head.tsx +2 -2
  203. package/src/compat/next/headers.ts +103 -95
  204. package/src/compat/next/image/client.tsx +220 -0
  205. package/src/compat/next/image/config.ts +56 -58
  206. package/src/compat/next/image/optimizer.ts +40 -36
  207. package/src/compat/next/image/patterns.ts +37 -40
  208. package/src/compat/next/{image-props.ts → image/props.ts} +208 -202
  209. package/src/compat/next/image/shared.ts +65 -57
  210. package/src/compat/next/image/static-metadata.ts +98 -107
  211. package/src/compat/next/image/validate.ts +79 -88
  212. package/src/compat/next/image.tsx +19 -23
  213. package/src/compat/next/index.ts +1 -1
  214. package/src/compat/next/legacy-image.tsx +59 -60
  215. package/src/compat/next/{link-validation-transform.ts → link-transform.ts} +95 -96
  216. package/src/compat/next/link.tsx +158 -158
  217. package/src/compat/next/navigation.cjs +12 -3
  218. package/src/compat/next/navigation.ts +48 -50
  219. package/src/compat/next/offline.ts +27 -27
  220. package/src/compat/next/og.ts +121 -124
  221. package/src/compat/next/preferred-region.ts +13 -14
  222. package/src/compat/next/redirects.ts +58 -56
  223. package/src/compat/next/resource-hints.ts +73 -76
  224. package/src/compat/next/rewrites.ts +130 -133
  225. package/src/compat/next/root-params.ts +45 -45
  226. package/src/compat/next/{optimistic-route-state.ts → route-state.ts} +52 -52
  227. package/src/compat/next/router.cjs +4 -2
  228. package/src/compat/next/router.ts +58 -61
  229. package/src/compat/next/script.tsx +108 -108
  230. package/src/compat/next/server-only.ts +1 -1
  231. package/src/compat/next/server.ts +19 -19
  232. package/src/compat/next/svgr.ts +18 -17
  233. package/src/compat/next/telemetry.ts +24 -24
  234. package/src/compat/next/{image-usage.ts → usage.ts} +70 -39
  235. package/src/compat/next/user-agent.ts +53 -49
  236. package/src/compat/next/web-vitals.ts +22 -24
  237. package/src/compat/otel/api.ts +41 -41
  238. package/src/compat/otel/client-trace-metadata.ts +25 -27
  239. package/src/compat/otel/fetch-span.ts +29 -29
  240. package/src/compat/otel/tracer.ts +331 -331
  241. package/src/compat/pages/api.ts +456 -0
  242. package/src/compat/pages/client-plugin.ts +36 -36
  243. package/src/compat/pages/router-state.ts +34 -34
  244. package/src/compat/pages/{index.ts → router.ts} +130 -135
  245. package/src/compat/ppr/io.ts +12 -12
  246. package/src/compat/ppr/missing-root-params.ts +34 -38
  247. package/src/compat/ppr/root-params-scan.ts +66 -66
  248. package/src/compat/ppr/root-params-transform.ts +24 -26
  249. package/src/compat/ppr/root-params.ts +30 -30
  250. package/src/compat/ppr/segment-config-incompat.ts +6 -7
  251. package/src/compat/protocol.ts +71 -70
  252. package/src/compat/react/action-state.ts +47 -48
  253. package/src/compat/react/client-lite.ts +15 -15
  254. package/src/compat/react/client.ts +4 -4
  255. package/src/compat/react/compiler-runtime.ts +11 -11
  256. package/src/compat/react/dom-client.ts +44 -44
  257. package/src/compat/react/dom-react-server.ts +10 -16
  258. package/src/compat/react/dom-server.ts +10 -10
  259. package/src/compat/react/dom.ts +52 -52
  260. package/src/compat/react/hooks-extra.ts +34 -35
  261. package/src/compat/react/parity.ts +64 -61
  262. package/src/compat/react/preact.ts +81 -82
  263. package/src/compat/react/react-server.ts +28 -28
  264. package/src/compat/react/router-shim.ts +1 -1
  265. package/src/compat/react/server-component-use.ts +8 -8
  266. package/src/compat/react/server-inserted-html.ts +30 -31
  267. package/src/compat/react/server.ts +53 -55
  268. package/src/compat/react/use.ts +32 -32
  269. package/src/compat/react/view-transition.ts +20 -20
  270. package/src/compat/register/actions.ts +35 -824
  271. package/src/compat/register/boot.ts +41 -41
  272. package/src/compat/register/build-tier.ts +5 -5
  273. package/src/compat/register/build.ts +74 -70
  274. package/src/compat/register/bundler.ts +161 -156
  275. package/src/compat/register/cache.ts +20 -20
  276. package/src/compat/register/client-errors.ts +3 -3
  277. package/src/compat/register/config.ts +6 -6
  278. package/src/compat/register/css-extras.ts +30 -34
  279. package/src/compat/register/edge-runtime.ts +3 -3
  280. package/src/compat/register/errors.ts +10 -12
  281. package/src/compat/register/export.ts +16 -16
  282. package/src/compat/register/font.ts +11 -11
  283. package/src/compat/register/hooks.ts +2 -2
  284. package/src/compat/register/image.ts +40 -40
  285. package/src/compat/register/index.ts +59 -62
  286. package/src/compat/register/instrumentation-client.ts +10 -10
  287. package/src/compat/register/lifecycle.ts +25 -28
  288. package/src/compat/register/mdx.ts +10 -10
  289. package/src/compat/register/middleware.ts +226 -16
  290. package/src/compat/register/otel.ts +80 -88
  291. package/src/compat/register/pages-api.ts +10 -463
  292. package/src/compat/register/ppr.ts +16 -16
  293. package/src/compat/register/protocol.ts +17 -18
  294. package/src/compat/register/proxy.ts +49 -51
  295. package/src/compat/register/render.ts +79 -76
  296. package/src/compat/register/routing.ts +162 -159
  297. package/src/compat/register/segment.ts +24 -1887
  298. package/src/compat/register/static-image.ts +3 -3
  299. package/src/compat/register/{misc.ts → taint.ts} +9 -9
  300. package/src/compat/register/typed-routes.ts +14 -14
  301. package/src/compat/register/{usecache.ts → use-cache.ts} +39 -39
  302. package/src/compat/register/validation.ts +18 -18
  303. package/src/compat/segment/loading-boundary.ts +43 -45
  304. package/src/compat/segment/page-slot.ts +69 -69
  305. package/src/compat/segment/serve.ts +1884 -0
  306. package/src/compat/segment/tree.ts +113 -112
  307. package/src/compat/segment/vary-key.ts +38 -38
  308. package/src/compat/segment/vary-params.ts +138 -142
  309. package/src/compat/static-params.ts +14 -12
  310. package/src/compat/tsconfig-defaults.ts +87 -91
  311. package/src/compat/typecheck/{index.ts → check.ts} +234 -212
  312. package/src/compat/typecheck/worker.ts +15 -12
  313. package/src/compat/typed-routes/{index.ts → generate.ts} +36 -36
  314. package/src/compat/typed-routes/manifest.ts +174 -170
  315. package/src/compat/typed-routes/typegen.ts +127 -110
  316. package/src/compat/validation/errors.ts +16 -19
  317. package/src/compat/validation/prerender-diagnostics.ts +521 -504
  318. package/src/compat/validation/{index.ts → validate.ts} +647 -648
  319. package/src/compat-bootstrap.ts +16 -16
  320. package/src/config.ts +69 -69
  321. package/src/css/build.ts +230 -227
  322. package/src/css/postcss.ts +79 -80
  323. package/src/css/worker.ts +14 -15
  324. package/src/dev/client-actions.ts +10 -10
  325. package/src/dev/client-chunk-store.ts +27 -27
  326. package/src/dev/{client-key-cache.ts → restart/client-key.ts} +76 -76
  327. package/src/dev/{restart-cache.ts → restart/enabled.ts} +1 -1
  328. package/src/dev/{global-css-cache.ts → restart/global-css.ts} +91 -83
  329. package/src/dev/{node-module-bundle-cache.ts → restart/node-modules.ts} +24 -24
  330. package/src/dev/{route-bundle-key-cache.ts → restart/route-bundle-key.ts} +53 -53
  331. package/src/dev/{route-facts-cache.ts → restart/route-facts.ts} +82 -82
  332. package/src/dev/server.ts +804 -820
  333. package/src/env.ts +46 -43
  334. package/src/extensions.ts +491 -478
  335. package/src/index.ts +8 -8
  336. package/src/internal.ts +20 -23
  337. package/src/{islands → render}/boundary-error.ts +3 -3
  338. package/src/render/hooks.ts +71 -71
  339. package/src/render/island-context.ts +14 -14
  340. package/src/render/metadata.ts +310 -310
  341. package/src/{ppr-postpone.ts → render/postpone.ts} +5 -5
  342. package/src/{ppr.ts → render/ppr.ts} +244 -245
  343. package/src/render/renderer.ts +2076 -2082
  344. package/src/render/resource-hints.ts +16 -17
  345. package/src/render/slots.tsx +224 -235
  346. package/src/{islands → render}/static-children.ts +9 -12
  347. package/src/{islands → render}/static-slots.ts +37 -37
  348. package/src/{cache/context.ts → request/cache.ts} +20 -20
  349. package/src/request/context.ts +107 -107
  350. package/src/{dynamic/source.ts → resolve/dynamic.ts} +152 -143
  351. package/src/resolve/engine.ts +90 -77
  352. package/src/resolve/imports.ts +475 -463
  353. package/src/resolve/scan-facts.ts +442 -186
  354. package/src/resolve/source-text.ts +37 -37
  355. package/src/{dynamic → resolve}/tree-shake.ts +132 -128
  356. package/src/routing/forwarded.ts +19 -19
  357. package/src/routing/handler.ts +84 -91
  358. package/src/routing/href.ts +69 -70
  359. package/src/routing/{metadata.ts → metadata-files.ts} +403 -401
  360. package/src/{proxy.ts → routing/proxy.ts} +306 -312
  361. package/src/routing/{request-runtime.ts → request-environment.ts} +10 -10
  362. package/src/routing/routes.ts +833 -848
  363. package/src/routing/slots.ts +164 -160
  364. package/src/runtime/loader.ts +952 -0
  365. package/src/{dev → runtime}/module-cache.ts +309 -287
  366. package/src/{dev → runtime}/module-generations.ts +9 -9
  367. package/src/{dev → runtime}/module-transform.ts +81 -72
  368. package/src/{dev/imports.ts → runtime/modules.ts} +934 -847
  369. package/src/runtime/{server.ts → vendor-build.ts} +848 -1696
  370. package/src/runtime/vendor.ts +425 -404
  371. package/src/styles.d.ts +9 -0
  372. package/src/types.ts +320 -335
  373. package/src/utils/ansi.ts +5 -5
  374. package/src/utils/{source.ts → code.ts} +15 -12
  375. package/src/utils/content-type.ts +3 -3
  376. package/src/utils/decode.ts +2 -2
  377. package/src/utils/dev-profile.ts +13 -13
  378. package/src/utils/error-log.ts +6 -6
  379. package/src/utils/esbuild.ts +18 -18
  380. package/src/utils/fs-cache.ts +13 -13
  381. package/src/utils/fs.ts +57 -49
  382. package/src/utils/html.ts +20 -24
  383. package/src/utils/native-require.ts +8 -8
  384. package/src/utils/serialize.ts +139 -146
  385. package/src/utils/verbose.ts +18 -18
  386. package/src/cli/analyze-print.ts +0 -181
  387. package/src/compat/middleware/manifest.ts +0 -210
  388. package/src/compat/next/image-client.tsx +0 -215
  389. package/src/compat/next/link-usage.ts +0 -29
  390. package/src/css/index.ts +0 -2
  391. package/src/render/index.ts +0 -1
  392. package/src/style-modules.d.ts +0 -9
@@ -1,86 +1,92 @@
1
- import { existsSync } from 'node:fs';
2
- import { readFile } from 'node:fs/promises';
3
- import path from 'node:path';
4
- import zlib from 'node:zlib';
5
- import { loadConfig } from '../config';
6
- import { matchRoute } from '../routing/routes';
7
- import { listFiles } from '../utils/fs';
8
- import type { BuildManifest, RouteManifestEntry } from '../types';
1
+ import { existsSync } from 'node:fs'
2
+ import { readFile } from 'node:fs/promises'
3
+ import path from 'node:path'
4
+ import zlib from 'node:zlib'
5
+ import { loadConfig } from '../config'
6
+ import { matchRoute } from '../routing/routes'
7
+ import { bold, cyan, dim } from '../utils/ansi'
8
+ import { listFiles } from '../utils/fs'
9
+ import type { BuildManifest, RouteManifestEntry } from '../types'
9
10
 
10
11
  export interface AnalyzeResult {
11
- mode: 'production' | 'development';
12
- root: string;
13
- compression: AnalyzeCompression;
14
- files: AnalyzeFile[];
15
- routeBundles: AnalyzeRouteBundle[];
12
+ mode: 'production' | 'development'
13
+ root: string
14
+ compression: AnalyzeCompression
15
+ files: AnalyzeFile[]
16
+ routeBundles: AnalyzeRouteBundle[]
16
17
  totals: {
17
- rawBytes: number;
18
- compressedBytes: number;
19
- };
18
+ rawBytes: number
19
+ compressedBytes: number
20
+ }
20
21
  }
21
22
 
22
23
  export interface AnalyzeFile {
23
- path: string;
24
- type: 'html' | 'css' | 'js' | 'asset';
25
- rawBytes: number;
26
- compressedBytes: number;
24
+ path: string
25
+ type: 'html' | 'css' | 'js' | 'asset'
26
+ rawBytes: number
27
+ compressedBytes: number
27
28
  }
28
29
 
29
30
  export interface AnalyzeRouteBundle {
30
- route: string;
31
- page: 'server' | 'client';
32
- initial: AnalyzeBundleFile[];
33
- visibleDynamic: AnalyzeDynamicBundleFile[];
34
- dynamic: AnalyzeDynamicBundleFile[];
35
- lazyShared: AnalyzeBundleFile[];
31
+ route: string
32
+ page: 'server' | 'client'
33
+ initial: AnalyzeBundleFile[]
34
+ visibleDynamic: AnalyzeDynamicBundleFile[]
35
+ dynamic: AnalyzeDynamicBundleFile[]
36
+ lazyShared: AnalyzeBundleFile[]
36
37
  }
37
38
 
38
39
  export interface AnalyzeBundleFile {
39
- path: string;
40
- rawBytes: number;
41
- compressedBytes: number;
40
+ path: string
41
+ rawBytes: number
42
+ compressedBytes: number
42
43
  }
43
44
 
44
45
  export interface AnalyzeDynamicBundleFile extends AnalyzeBundleFile {
45
- exportName: string;
46
- load: 'render' | 'visible';
46
+ exportName: string
47
+ load: 'render' | 'visible'
47
48
  }
48
49
 
49
- export type AnalyzeCompression = 'gzip' | 'brotli';
50
+ export type AnalyzeCompression = 'gzip' | 'brotli'
50
51
 
51
52
  interface AnalyzeOptions {
52
- compression?: AnalyzeCompression;
53
- route?: string;
53
+ compression?: AnalyzeCompression
54
+ route?: string
54
55
  }
55
56
 
56
- export async function analyzeProject(root?: string, options: AnalyzeOptions = {}): Promise<AnalyzeResult> {
57
- const config = await loadConfig(root);
57
+ export async function analyzeProject(
58
+ root?: string,
59
+ options: AnalyzeOptions = {},
60
+ ): Promise<AnalyzeResult> {
61
+ const config = await loadConfig(root)
58
62
  if (!existsSync(config.outPath)) {
59
- throw new Error(`No PNext output found at ${config.outPath}. Run pnext build first.`);
63
+ throw new Error(`No PNext output found at ${config.outPath}. Run pnext build first.`)
60
64
  }
61
65
 
62
- const target = analyzeTarget(config.outPath);
66
+ const target = analyzeTarget(config.outPath)
63
67
  if (!target) {
64
- throw new Error(`No PNext build or dev output found at ${config.outPath}. Run pnext build or pnext dev first.`);
68
+ throw new Error(
69
+ `No PNext build or dev output found at ${config.outPath}. Run pnext build or pnext dev first.`,
70
+ )
65
71
  }
66
72
 
67
- const compression = options.compression ?? 'gzip';
68
- const files = await listFiles(target.root);
73
+ const compression = options.compression ?? 'gzip'
74
+ const files = await listFiles(target.root)
69
75
  const rows = await Promise.all(
70
76
  files.map(async file => {
71
- const bytes = await readFile(file);
77
+ const bytes = await readFile(file)
72
78
  return {
73
79
  path: path.relative(target.root, file),
74
80
  type: fileType(file),
75
81
  rawBytes: bytes.length,
76
82
  compressedBytes: compressedSize(bytes, compression),
77
- };
83
+ }
78
84
  }),
79
- );
85
+ )
80
86
  const routeBundles =
81
87
  target.mode === 'production'
82
88
  ? await analyzeRouteBundles(config.outPath, target.root, rows, options.route)
83
- : [];
89
+ : []
84
90
 
85
91
  return {
86
92
  mode: target.mode,
@@ -95,7 +101,7 @@ export async function analyzeProject(root?: string, options: AnalyzeOptions = {}
95
101
  }),
96
102
  { rawBytes: 0, compressedBytes: 0 },
97
103
  ),
98
- };
104
+ }
99
105
  }
100
106
 
101
107
  async function analyzeRouteBundles(
@@ -104,51 +110,51 @@ async function analyzeRouteBundles(
104
110
  files: AnalyzeFile[],
105
111
  routeFilter?: string,
106
112
  ) {
107
- const manifestPath = path.join(outPath, 'manifest.json');
108
- if (!existsSync(manifestPath)) return [];
113
+ const manifestPath = path.join(outPath, 'manifest.json')
114
+ if (!existsSync(manifestPath)) return []
109
115
 
110
- const manifest = JSON.parse(await readFile(manifestPath, 'utf8')) as BuildManifest;
111
- const pages = manifest.routes.filter(route => route.kind === 'page');
112
- const selected = routeFilter ? filterAnalyzeRoutes(pages, routeFilter) : pages;
116
+ const manifest = JSON.parse(await readFile(manifestPath, 'utf8')) as BuildManifest
117
+ const pages = manifest.routes.filter(route => route.kind === 'page')
118
+ const selected = routeFilter ? filterAnalyzeRoutes(pages, routeFilter) : pages
113
119
  if (routeFilter && selected.length === 0) {
114
120
  throw new Error(
115
121
  `No route matches ${routeFilter}. Routes:\n ${pages
116
122
  .map(route => publicRoutePath(route.route))
117
123
  .join('\n ')}`,
118
- );
124
+ )
119
125
  }
120
- const sizes = new Map(files.map(file => [toPosix(file.path), file]));
121
- const contents = new Map<string, string>();
126
+ const sizes = new Map(files.map(file => [toPosix(file.path), file]))
127
+ const contents = new Map<string, string>()
122
128
 
123
129
  async function js(pathname: string) {
124
- const normalized = toPosix(pathname);
125
- if (contents.has(normalized)) return contents.get(normalized) ?? '';
126
- const text = await readFile(path.join(publicPath, normalized), 'utf8');
127
- contents.set(normalized, text);
128
- return text;
130
+ const normalized = toPosix(pathname)
131
+ if (contents.has(normalized)) return contents.get(normalized) ?? ''
132
+ const text = await readFile(path.join(publicPath, normalized), 'utf8')
133
+ contents.set(normalized, text)
134
+ return text
129
135
  }
130
136
 
131
- const bundles: AnalyzeRouteBundle[] = [];
137
+ const bundles: AnalyzeRouteBundle[] = []
132
138
  for (const route of selected) {
133
- bundles.push(await analyzeRouteBundle(route, sizes, js));
139
+ bundles.push(await analyzeRouteBundle(route, sizes, js))
134
140
  }
135
- return bundles;
141
+ return bundles
136
142
  }
137
143
 
138
144
  // Accepts the route in ':id' or '[id]' template form, or a concrete pathname
139
145
  // (e.g. /users/ada) matched through the route patterns.
140
146
  function filterAnalyzeRoutes(pages: RouteManifestEntry[], filter: string) {
141
- const normalized = filter === '/' ? filter : filter.replace(/\/+$/, '');
147
+ const normalized = filter === '/' ? filter : filter.replace(/\/+$/, '')
142
148
  const direct = pages.filter(
143
149
  route => route.route === normalized || publicRoutePath(route.route) === normalized,
144
- );
145
- if (direct.length > 0) return direct;
146
- const matched = matchRoute(pages, normalized);
147
- return matched ? [matched.route] : [];
150
+ )
151
+ if (direct.length > 0) return direct
152
+ const matched = matchRoute(pages, normalized)
153
+ return matched ? [matched.route] : []
148
154
  }
149
155
 
150
156
  function publicRoutePath(route: string) {
151
- return route.replace(/:([a-zA-Z0-9_]+)\*/g, '[...$1]').replace(/:([a-zA-Z0-9_]+)/g, '[$1]');
157
+ return route.replace(/:([a-zA-Z0-9_]+)\*/g, '[...$1]').replace(/:([a-zA-Z0-9_]+)/g, '[$1]')
152
158
  }
153
159
 
154
160
  async function analyzeRouteBundle(
@@ -156,51 +162,52 @@ async function analyzeRouteBundle(
156
162
  sizes: Map<string, AnalyzeFile>,
157
163
  js: (path: string) => Promise<string>,
158
164
  ): Promise<AnalyzeRouteBundle> {
159
- const initial = new Set<string>();
160
- addIfExists(initial, sizes, routeHtmlPath(route));
161
- addIfExists(initial, sizes, 'assets/global.css');
162
- addIfExists(initial, sizes, `assets/${route.id}.css`);
165
+ const initial = new Set<string>()
166
+ addIfExists(initial, sizes, routeHtmlPath(route))
167
+ addIfExists(initial, sizes, 'assets/global.css')
168
+ addIfExists(initial, sizes, `assets/${route.id}.css`)
163
169
 
164
170
  if (route.clientEntry) {
165
- addIfExists(initial, sizes, route.clientEntry);
166
- for (const dependency of await staticDependencies(route.clientEntry, sizes, js)) initial.add(dependency);
171
+ addIfExists(initial, sizes, route.clientEntry)
172
+ for (const dependency of await staticDependencies(route.clientEntry, sizes, js))
173
+ initial.add(dependency)
167
174
  }
168
175
 
169
- const visibleDynamic: AnalyzeDynamicBundleFile[] = [];
170
- const dynamic: AnalyzeDynamicBundleFile[] = [];
171
- const dynamicTargets = new Set<string>();
172
- const lazyShared = new Set<string>();
176
+ const visibleDynamic: AnalyzeDynamicBundleFile[] = []
177
+ const dynamic: AnalyzeDynamicBundleFile[] = []
178
+ const dynamicTargets = new Set<string>()
179
+ const lazyShared = new Set<string>()
173
180
 
174
181
  if (route.clientEntry && sizes.has(toPosix(route.clientEntry))) {
175
- const entrySource = await js(route.clientEntry);
176
- const imports = dynamicImports(entrySource, route.clientEntry);
182
+ const entrySource = await js(route.clientEntry)
183
+ const imports = dynamicImports(entrySource, route.clientEntry)
177
184
  for (const imported of imports) {
178
- const reference = route.clientReferences.find(item => item.exportName === imported.exportName);
185
+ const reference = route.clientReferences.find(item => item.exportName === imported.exportName)
179
186
  const load: AnalyzeDynamicBundleFile['load'] =
180
- reference?.dynamic?.load === 'visible' ? 'visible' : 'render';
181
- const row = sizes.get(imported.path);
182
- if (!row) continue;
183
- dynamicTargets.add(imported.path);
187
+ reference?.dynamic?.load === 'visible' ? 'visible' : 'render'
188
+ const row = sizes.get(imported.path)
189
+ if (!row) continue
190
+ dynamicTargets.add(imported.path)
184
191
  const item: AnalyzeDynamicBundleFile = {
185
192
  path: imported.path,
186
193
  exportName: imported.exportName,
187
194
  load,
188
195
  rawBytes: row.rawBytes,
189
196
  compressedBytes: row.compressedBytes,
190
- };
191
- if (load === 'visible') visibleDynamic.push(item);
192
- else dynamic.push(item);
197
+ }
198
+ if (load === 'visible') visibleDynamic.push(item)
199
+ else dynamic.push(item)
193
200
 
194
201
  for (const dependency of await staticDependencies(imported.path, sizes, js)) {
195
- if (!initial.has(dependency) && !dynamicTargets.has(dependency)) lazyShared.add(dependency);
202
+ if (!initial.has(dependency) && !dynamicTargets.has(dependency)) lazyShared.add(dependency)
196
203
  }
197
204
  }
198
205
 
199
206
  for (const importedPath of dynamicImportPaths(entrySource, route.clientEntry)) {
200
- if (initial.has(importedPath) || dynamicTargets.has(importedPath)) continue;
201
- if (sizes.has(importedPath)) lazyShared.add(importedPath);
207
+ if (initial.has(importedPath) || dynamicTargets.has(importedPath)) continue
208
+ if (sizes.has(importedPath)) lazyShared.add(importedPath)
202
209
  for (const dependency of await staticDependencies(importedPath, sizes, js)) {
203
- if (!initial.has(dependency) && !dynamicTargets.has(dependency)) lazyShared.add(dependency);
210
+ if (!initial.has(dependency) && !dynamicTargets.has(dependency)) lazyShared.add(dependency)
204
211
  }
205
212
  }
206
213
  }
@@ -212,7 +219,7 @@ async function analyzeRouteBundle(
212
219
  visibleDynamic: visibleDynamic.sort((a, b) => b.compressedBytes - a.compressedBytes),
213
220
  dynamic: dynamic.sort((a, b) => b.compressedBytes - a.compressedBytes),
214
221
  lazyShared: bundleFiles([...lazyShared], sizes),
215
- };
222
+ }
216
223
  }
217
224
 
218
225
  async function staticDependencies(
@@ -221,108 +228,290 @@ async function staticDependencies(
221
228
  js: (path: string) => Promise<string>,
222
229
  seen = new Set<string>(),
223
230
  ) {
224
- const dependencies = new Set<string>();
225
- const normalized = toPosix(entry);
226
- if (seen.has(normalized) || !sizes.has(normalized)) return dependencies;
227
- seen.add(normalized);
231
+ const dependencies = new Set<string>()
232
+ const normalized = toPosix(entry)
233
+ if (seen.has(normalized) || !sizes.has(normalized)) return dependencies
234
+ seen.add(normalized)
228
235
 
229
236
  for (const specifier of staticImports(await js(normalized))) {
230
- const dependency = resolveBuiltImport(normalized, specifier);
231
- if (!dependency || !sizes.has(dependency)) continue;
232
- dependencies.add(dependency);
233
- for (const transitive of await staticDependencies(dependency, sizes, js, seen)) dependencies.add(transitive);
237
+ const dependency = resolveBuiltImport(normalized, specifier)
238
+ if (!dependency || !sizes.has(dependency)) continue
239
+ dependencies.add(dependency)
240
+ for (const transitive of await staticDependencies(dependency, sizes, js, seen))
241
+ dependencies.add(transitive)
234
242
  }
235
243
 
236
- return dependencies;
244
+ return dependencies
237
245
  }
238
246
 
239
247
  function dynamicImports(source: string, from: string) {
240
- const imports: { path: string; exportName: string }[] = [];
241
- const pattern = /import\("([^"]+)"\)\.then\([A-Za-z_$][\w$]*=>[A-Za-z_$][\w$]*\.([A-Za-z_$][\w$]*)\)/g;
242
- let match: RegExpExecArray | null;
248
+ const imports: { path: string; exportName: string }[] = []
249
+ const pattern =
250
+ /import\("([^"]+)"\)\.then\([A-Za-z_$][\w$]*=>[A-Za-z_$][\w$]*\.([A-Za-z_$][\w$]*)\)/g
251
+ let match: RegExpExecArray | null
243
252
  while ((match = pattern.exec(source))) {
244
- const specifier = match[1];
245
- const exportName = match[2];
246
- if (!specifier || !exportName) continue;
247
- const resolved = resolveBuiltImport(from, specifier);
248
- if (resolved) imports.push({ path: resolved, exportName });
253
+ const specifier = match[1]
254
+ const exportName = match[2]
255
+ if (!specifier || !exportName) continue
256
+ const resolved = resolveBuiltImport(from, specifier)
257
+ if (resolved) imports.push({ path: resolved, exportName })
249
258
  }
250
- return imports;
259
+ return imports
251
260
  }
252
261
 
253
262
  function dynamicImportPaths(source: string, from: string) {
254
- const imports: string[] = [];
255
- const pattern = /import\("([^"]+)"\)/g;
256
- let match: RegExpExecArray | null;
263
+ const imports: string[] = []
264
+ const pattern = /import\("([^"]+)"\)/g
265
+ let match: RegExpExecArray | null
257
266
  while ((match = pattern.exec(source))) {
258
- const specifier = match[1];
259
- if (!specifier) continue;
260
- const resolved = resolveBuiltImport(from, specifier);
261
- if (resolved) imports.push(resolved);
267
+ const specifier = match[1]
268
+ if (!specifier) continue
269
+ const resolved = resolveBuiltImport(from, specifier)
270
+ if (resolved) imports.push(resolved)
262
271
  }
263
- return imports;
272
+ return imports
264
273
  }
265
274
 
266
275
  function staticImports(source: string) {
267
- const imports: string[] = [];
268
- const pattern = /import\s*(?:[^'"]*?from\s*)?["']([^'"]+)["']/g;
269
- let match: RegExpExecArray | null;
276
+ const imports: string[] = []
277
+ const pattern = /import\s*(?:[^'"]*?from\s*)?["']([^'"]+)["']/g
278
+ let match: RegExpExecArray | null
270
279
  while ((match = pattern.exec(source))) {
271
- if (match[1]) imports.push(match[1]);
280
+ if (match[1]) imports.push(match[1])
272
281
  }
273
- return imports;
282
+ return imports
274
283
  }
275
284
 
276
285
  function resolveBuiltImport(from: string, specifier: string) {
277
- if (!specifier.startsWith('.')) return null;
278
- return toPosix(path.posix.normalize(path.posix.join(path.posix.dirname(toPosix(from)), specifier)));
286
+ if (!specifier.startsWith('.')) return null
287
+ return toPosix(
288
+ path.posix.normalize(path.posix.join(path.posix.dirname(toPosix(from)), specifier)),
289
+ )
279
290
  }
280
291
 
281
292
  function addIfExists(items: Set<string>, sizes: Map<string, AnalyzeFile>, pathname: string) {
282
- const normalized = toPosix(pathname);
283
- if (sizes.has(normalized)) items.add(normalized);
293
+ const normalized = toPosix(pathname)
294
+ if (sizes.has(normalized)) items.add(normalized)
284
295
  }
285
296
 
286
297
  function bundleFiles(paths: string[], sizes: Map<string, AnalyzeFile>) {
287
298
  return paths
288
299
  .map(pathname => {
289
- const row = sizes.get(pathname);
290
- return row ? { path: pathname, rawBytes: row.rawBytes, compressedBytes: row.compressedBytes } : null;
300
+ const row = sizes.get(pathname)
301
+ return row
302
+ ? { path: pathname, rawBytes: row.rawBytes, compressedBytes: row.compressedBytes }
303
+ : null
291
304
  })
292
305
  .filter((file): file is AnalyzeBundleFile => Boolean(file))
293
- .sort((a, b) => b.compressedBytes - a.compressedBytes);
306
+ .sort((a, b) => b.compressedBytes - a.compressedBytes)
294
307
  }
295
308
 
296
309
  function routeHtmlPath(route: RouteManifestEntry) {
297
- return route.route === '/' ? 'index.html' : `${route.route.replace(/^\/+/, '')}/index.html`;
310
+ return route.route === '/' ? 'index.html' : `${route.route.replace(/^\/+/, '')}/index.html`
298
311
  }
299
312
 
300
313
  function analyzeTarget(outPath: string) {
301
- const publicPath = path.join(outPath, 'public');
302
- if (existsSync(publicPath)) return { mode: 'production' as const, root: publicPath };
314
+ const publicPath = path.join(outPath, 'public')
315
+ if (existsSync(publicPath)) return { mode: 'production' as const, root: publicPath }
303
316
 
304
- const cachePath = path.join(outPath, 'cache');
305
- if (existsSync(cachePath)) return { mode: 'development' as const, root: cachePath };
317
+ const cachePath = path.join(outPath, 'cache')
318
+ if (existsSync(cachePath)) return { mode: 'development' as const, root: cachePath }
306
319
 
307
- return null;
320
+ return null
308
321
  }
309
322
 
310
323
  function compressedSize(bytes: Buffer, compression: AnalyzeCompression) {
311
324
  if (compression === 'brotli') {
312
325
  return zlib.brotliCompressSync(bytes, {
313
326
  params: { [zlib.constants.BROTLI_PARAM_QUALITY]: 11 },
314
- }).length;
327
+ }).length
315
328
  }
316
- return zlib.gzipSync(bytes, { level: 9 }).length;
329
+ return zlib.gzipSync(bytes, { level: 9 }).length
317
330
  }
318
331
 
319
332
  function fileType(file: string): 'html' | 'css' | 'js' | 'asset' {
320
- if (file.endsWith('.html')) return 'html';
321
- if (file.endsWith('.css')) return 'css';
322
- if (file.endsWith('.js')) return 'js';
323
- return 'asset';
333
+ if (file.endsWith('.html')) return 'html'
334
+ if (file.endsWith('.css')) return 'css'
335
+ if (file.endsWith('.js')) return 'js'
336
+ return 'asset'
324
337
  }
325
338
 
326
339
  function toPosix(value: string) {
327
- return value.split(path.sep).join('/');
340
+ return value.split(path.sep).join('/')
341
+ }
342
+
343
+ export function printAnalyzeResult(
344
+ result: AnalyzeResult,
345
+ options: { route?: string; files: boolean },
346
+ ) {
347
+ console.log(
348
+ `PNext analyze: ${result.mode === 'production' ? 'production build' : 'dev server cache'} at ${dim(result.root)} (${result.compression})`,
349
+ )
350
+ printRouteBundles(result)
351
+ // With a route filter, the remaining files are mostly other routes' bundles —
352
+ // listing them as "other" would misread as unowned weight.
353
+ if (!options.route) printAnalyzeFiles(result, { details: options.files })
354
+ }
355
+
356
+ function printRouteBundles(result: AnalyzeResult) {
357
+ if (result.routeBundles.length === 0) return
358
+
359
+ console.log(`\n${bold('Routes')}`)
360
+ for (const bundle of result.routeBundles) {
361
+ console.log(`\n${cyan(bundle.route)} ${dim(`(${bundle.page} page)`)}`)
362
+ printBundleGroup('initial', bundle.initial, result.compression)
363
+ printDynamicBundleGroup('visible dynamic', bundle.visibleDynamic, result.compression)
364
+ printDynamicBundleGroup('dynamic', bundle.dynamic, result.compression)
365
+ printBundleGroup('shared after dynamic', bundle.lazyShared, result.compression)
366
+ }
367
+ console.log('')
368
+ }
369
+
370
+ function printBundleGroup(
371
+ label: string,
372
+ files: AnalyzeResult['routeBundles'][number]['initial'],
373
+ compression: AnalyzeCompression,
374
+ ) {
375
+ if (files.length === 0) return
376
+ console.log(`\n ${bold(label.padEnd(22))} ${formatTotalSize(files, compression)}`)
377
+ printBundleFiles(files, compression, ' ', rowColumns(files))
378
+ }
379
+
380
+ function printDynamicBundleGroup(
381
+ label: string,
382
+ files: AnalyzeResult['routeBundles'][number]['visibleDynamic'],
383
+ compression: AnalyzeCompression,
384
+ ) {
385
+ if (files.length === 0) return
386
+ console.log(`\n ${bold(label)}`)
387
+ const columns = rowColumns(files)
388
+ const componentWidth = Math.max(...files.map(file => file.exportName.length))
389
+ const pathWidth = Math.max(...files.map(file => file.path.length))
390
+ for (const file of files) {
391
+ console.log(
392
+ ` ${file.exportName.padEnd(componentWidth)} ${dim(file.path.padEnd(pathWidth))} ${formatFileSize(file, compression, columns)}`,
393
+ )
394
+ }
395
+ }
396
+
397
+ function formatTotalSize(
398
+ files: { rawBytes: number; compressedBytes: number }[],
399
+ compression: AnalyzeCompression,
400
+ ) {
401
+ const rawBytes = files.reduce((total, file) => total + file.rawBytes, 0)
402
+ const compressedBytes = files.reduce((total, file) => total + file.compressedBytes, 0)
403
+ return `${formatBytes(compressedBytes)} ${compression} ${dim(`${formatBytes(rawBytes)} raw`)}`
404
+ }
405
+
406
+ function printAnalyzeFiles(result: AnalyzeResult, options: { details: boolean }) {
407
+ const routeFiles = new Set(
408
+ result.routeBundles.flatMap(bundle => [
409
+ ...bundle.initial.map(file => file.path),
410
+ ...bundle.visibleDynamic.map(file => file.path),
411
+ ...bundle.dynamic.map(file => file.path),
412
+ ...bundle.lazyShared.map(file => file.path),
413
+ ]),
414
+ )
415
+ const sorted = sortedAnalyzeFiles(result.files).filter(file => !routeFiles.has(file.path))
416
+ if (sorted.length === 0) return
417
+
418
+ if (!options.details) {
419
+ console.log(
420
+ `Other files: ${formatFileCount(sorted)} ${formatTotalSize(sorted, result.compression)}`,
421
+ )
422
+ console.log('Run pnext analyze --files to list them.')
423
+ return
424
+ }
425
+
426
+ console.log(`${bold('Other files')}\n`)
427
+ for (const type of ['html', 'css', 'js', 'asset'] as const) {
428
+ const files = sorted.filter(file => file.type === type)
429
+ if (files.length === 0) continue
430
+ console.log(bold(type))
431
+ printBundleFiles(files, result.compression, ' ', rowColumns(files))
432
+ console.log('')
433
+ }
434
+ }
435
+
436
+ function printBundleFiles(
437
+ files: { path: string; rawBytes: number; compressedBytes: number }[],
438
+ compression: AnalyzeCompression,
439
+ indent: string,
440
+ columns: RowColumns,
441
+ ) {
442
+ const pathWidth = Math.max(...files.map(file => file.path.length))
443
+ for (const file of files) {
444
+ console.log(
445
+ `${indent}${dim(file.path.padEnd(pathWidth))} ${formatFileSize(file, compression, columns)}`,
446
+ )
447
+ }
448
+ }
449
+
450
+ interface RowColumns {
451
+ compressedWidth: number
452
+ rawWidth: number
453
+ }
454
+
455
+ function rowColumns(files: { rawBytes: number; compressedBytes: number }[]): RowColumns {
456
+ return {
457
+ compressedWidth: Math.max(...files.map(file => formatBytes(file.compressedBytes).length)),
458
+ rawWidth: Math.max(...files.map(file => formatBytes(file.rawBytes).length)),
459
+ }
460
+ }
461
+
462
+ function formatFileSize(
463
+ file: { rawBytes: number; compressedBytes: number },
464
+ compression: AnalyzeCompression,
465
+ columns: RowColumns,
466
+ ) {
467
+ const compressed = formatBytes(file.compressedBytes).padStart(columns.compressedWidth)
468
+ const raw = `${formatBytes(file.rawBytes).padStart(columns.rawWidth)} raw`
469
+ return `${compressed} ${compression} ${dim(raw)}`
470
+ }
471
+
472
+ function formatFileCount(files: AnalyzeResult['files']) {
473
+ const counts = new Map<string, number>()
474
+ for (const file of files) counts.set(file.type, (counts.get(file.type) ?? 0) + 1)
475
+ return [...counts].map(([type, count]) => `${count} ${pluralFileType(type, count)}`).join(', ')
476
+ }
477
+
478
+ function pluralFileType(type: string, count: number) {
479
+ if (count === 1) return type
480
+ if (type === 'js' || type === 'css' || type === 'html') return type
481
+ return `${type}s`
482
+ }
483
+
484
+ function sortedAnalyzeFiles(files: AnalyzeResult['files']) {
485
+ return [...files].sort((a, b) => {
486
+ const typeDelta = analyzeTypeRank(a.type) - analyzeTypeRank(b.type)
487
+ if (typeDelta) return typeDelta
488
+ const sizeDelta = b.compressedBytes - a.compressedBytes
489
+ if (sizeDelta) return sizeDelta
490
+ return a.path.localeCompare(b.path)
491
+ })
492
+ }
493
+
494
+ function analyzeTypeRank(type: AnalyzeResult['files'][number]['type']) {
495
+ switch (type) {
496
+ case 'html':
497
+ return 0
498
+ case 'css':
499
+ return 1
500
+ case 'js':
501
+ return 2
502
+ case 'asset':
503
+ return 3
504
+ }
505
+ }
506
+
507
+ function formatBytes(bytes: number) {
508
+ if (bytes < 1024) return `${bytes} B`
509
+ const units = ['KB', 'MB', 'GB', 'TB'] as const
510
+ let value = bytes / 1024
511
+ let unitIndex = 0
512
+ while (value >= 1024 && unitIndex < units.length - 1) {
513
+ value /= 1024
514
+ unitIndex += 1
515
+ }
516
+ return `${value >= 10 ? value.toFixed(1) : value.toFixed(2)} ${units[unitIndex]}`
328
517
  }