toiljs 0.0.115 → 0.0.117

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 (326) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +16 -7
  3. package/TYPESCRIPT_LAW.md +1946 -2073
  4. package/build/backend/.tsbuildinfo +1 -1
  5. package/build/cli/.tsbuildinfo +1 -1
  6. package/build/cli/index.js +2375 -258
  7. package/build/client/.tsbuildinfo +1 -1
  8. package/build/client/dev/devtools.js +1 -1
  9. package/build/client/dev/error-overlay.js +1 -1
  10. package/build/client/routing/mount.js +1 -1
  11. package/build/compiler/.tsbuildinfo +1 -1
  12. package/build/compiler/auth-emails.js +7 -3
  13. package/build/compiler/emails.js +1 -1
  14. package/build/compiler/index.d.ts +1 -0
  15. package/build/compiler/index.js +25 -4
  16. package/build/compiler/pages.js +2 -3
  17. package/build/compiler/prerender.d.ts +3 -8
  18. package/build/compiler/prerender.js +88 -127
  19. package/build/compiler/ssg.js +3 -4
  20. package/build/compiler/template-build.js +3 -4
  21. package/build/compiler/toil-docs.generated.js +6 -5
  22. package/build/compiler/vite.js +3 -6
  23. package/build/devserver/.tsbuildinfo +1 -1
  24. package/build/devserver/db/database.js +7 -3
  25. package/build/devserver/db/types.js +1 -1
  26. package/build/devserver/production.js +4 -3
  27. package/build/devserver/runtime/crypto.d.ts +1 -1
  28. package/build/io/.tsbuildinfo +1 -1
  29. package/build/logger/.tsbuildinfo +1 -1
  30. package/build/shared/.tsbuildinfo +1 -1
  31. package/examples/basic/client/routes/analytics.tsx +7 -5
  32. package/examples/basic/server/tsconfig.json +1 -3
  33. package/package.json +45 -44
  34. package/presets/no-uint8array-tostring.js +120 -153
  35. package/presets/oxlint.d.ts +5 -0
  36. package/presets/oxlint.js +224 -0
  37. package/server/runtime/tsconfig.json +1 -3
  38. package/tsconfig.json +2 -11
  39. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -38
  40. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -90
  41. package/.github/ISSUE_TEMPLATE/config.yml +0 -8
  42. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  43. package/.github/PULL_REQUEST_TEMPLATE.md +0 -43
  44. package/.github/changelog-config.json +0 -45
  45. package/.github/dependabot.yml +0 -27
  46. package/.github/workflows/ci.yml +0 -222
  47. package/.vscode/settings.json +0 -10
  48. package/docs/README.md +0 -107
  49. package/docs/auth/README.md +0 -104
  50. package/docs/auth/configuration.md +0 -105
  51. package/docs/auth/emails.md +0 -92
  52. package/docs/auth/extending.md +0 -202
  53. package/docs/auth/how-it-works.md +0 -138
  54. package/docs/auth/usage.md +0 -188
  55. package/docs/backend/README.md +0 -143
  56. package/docs/backend/data.md +0 -351
  57. package/docs/backend/rest.md +0 -402
  58. package/docs/backend/rpc.md +0 -226
  59. package/docs/background/README.md +0 -114
  60. package/docs/background/daemons.md +0 -276
  61. package/docs/background/derive.md +0 -179
  62. package/docs/cli/README.md +0 -379
  63. package/docs/concepts/ai-guide.md +0 -132
  64. package/docs/concepts/config.md +0 -416
  65. package/docs/concepts/decorators.md +0 -127
  66. package/docs/concepts/security.md +0 -108
  67. package/docs/concepts/tiers.md +0 -166
  68. package/docs/concepts/types.md +0 -216
  69. package/docs/database/README.md +0 -143
  70. package/docs/database/capacity.md +0 -350
  71. package/docs/database/counters.md +0 -174
  72. package/docs/database/documents.md +0 -277
  73. package/docs/database/events.md +0 -307
  74. package/docs/database/membership.md +0 -246
  75. package/docs/database/setup.md +0 -155
  76. package/docs/database/unique.md +0 -216
  77. package/docs/database/views.md +0 -246
  78. package/docs/frontend/README.md +0 -104
  79. package/docs/frontend/components.md +0 -286
  80. package/docs/frontend/data-fetching.md +0 -260
  81. package/docs/frontend/images.md +0 -148
  82. package/docs/frontend/metadata.md +0 -344
  83. package/docs/frontend/navigation.md +0 -296
  84. package/docs/frontend/rendering.md +0 -236
  85. package/docs/frontend/routing.md +0 -346
  86. package/docs/frontend/scripts.md +0 -118
  87. package/docs/frontend/search.md +0 -191
  88. package/docs/frontend/styling.md +0 -147
  89. package/docs/frontend/toil-global.md +0 -216
  90. package/docs/getting-started/README.md +0 -81
  91. package/docs/getting-started/create-project.md +0 -131
  92. package/docs/getting-started/deploy.md +0 -101
  93. package/docs/getting-started/first-app.md +0 -215
  94. package/docs/getting-started/installation.md +0 -124
  95. package/docs/getting-started/migrating.md +0 -125
  96. package/docs/getting-started/project-structure.md +0 -163
  97. package/docs/introduction/README.md +0 -66
  98. package/docs/introduction/design-principles.md +0 -81
  99. package/docs/introduction/distributed.md +0 -91
  100. package/docs/introduction/how-it-works.md +0 -121
  101. package/docs/introduction/hyperscale.md +0 -70
  102. package/docs/introduction/modern-stack.md +0 -85
  103. package/docs/introduction/vs-other-frameworks.md +0 -64
  104. package/docs/introduction/why-toil.md +0 -99
  105. package/docs/llms.txt +0 -95
  106. package/docs/realtime/README.md +0 -102
  107. package/docs/realtime/channels.md +0 -211
  108. package/docs/realtime/streams.md +0 -369
  109. package/docs/services/README.md +0 -60
  110. package/docs/services/analytics.md +0 -279
  111. package/docs/services/caching.md +0 -175
  112. package/docs/services/cookies.md +0 -235
  113. package/docs/services/crypto.md +0 -209
  114. package/docs/services/email.md +0 -289
  115. package/docs/services/environment.md +0 -141
  116. package/docs/services/ratelimit.md +0 -174
  117. package/docs/services/time.md +0 -85
  118. package/eslint.config.js +0 -57
  119. package/examples/basic/client/routes/docs/[...slug].tsx +0 -12
  120. package/presets/eslint.js +0 -88
  121. package/src/backend/index.ts +0 -204
  122. package/src/cli/configure.ts +0 -363
  123. package/src/cli/create.ts +0 -924
  124. package/src/cli/db.ts +0 -158
  125. package/src/cli/diagnostics.ts +0 -665
  126. package/src/cli/doctor.ts +0 -982
  127. package/src/cli/features.ts +0 -170
  128. package/src/cli/index.ts +0 -326
  129. package/src/cli/notify.ts +0 -120
  130. package/src/cli/proc.ts +0 -56
  131. package/src/cli/ui.ts +0 -169
  132. package/src/cli/update.ts +0 -248
  133. package/src/cli/updates.ts +0 -92
  134. package/src/cli/validate.ts +0 -31
  135. package/src/cli/version-check.ts +0 -125
  136. package/src/client/auth.ts +0 -869
  137. package/src/client/channel/channel.ts +0 -146
  138. package/src/client/components/Form.tsx +0 -65
  139. package/src/client/components/Image.tsx +0 -160
  140. package/src/client/components/Script.tsx +0 -113
  141. package/src/client/components/Slot.tsx +0 -21
  142. package/src/client/dev/devtools.tsx +0 -1089
  143. package/src/client/dev/error-overlay.tsx +0 -225
  144. package/src/client/errors.ts +0 -11
  145. package/src/client/head/head.ts +0 -182
  146. package/src/client/head/metadata.ts +0 -110
  147. package/src/client/index.ts +0 -130
  148. package/src/client/navigation/Link.tsx +0 -108
  149. package/src/client/navigation/NavLink.tsx +0 -86
  150. package/src/client/navigation/navigation.ts +0 -235
  151. package/src/client/navigation/prefetch.ts +0 -169
  152. package/src/client/navigation/scroll.ts +0 -53
  153. package/src/client/routing/Router.tsx +0 -216
  154. package/src/client/routing/action.ts +0 -122
  155. package/src/client/routing/error-boundary.tsx +0 -43
  156. package/src/client/routing/hooks.ts +0 -140
  157. package/src/client/routing/lazy.ts +0 -77
  158. package/src/client/routing/loader.ts +0 -381
  159. package/src/client/routing/match.ts +0 -47
  160. package/src/client/routing/mount.tsx +0 -90
  161. package/src/client/routing/params-context.ts +0 -10
  162. package/src/client/routing/slot-context.ts +0 -7
  163. package/src/client/rpc.ts +0 -97
  164. package/src/client/search/search.ts +0 -189
  165. package/src/client/search/use-page-search.ts +0 -73
  166. package/src/client/ssr/markers.tsx +0 -172
  167. package/src/client/stream/client.ts +0 -237
  168. package/src/client/types.ts +0 -73
  169. package/src/compiler/auth-emails.ts +0 -312
  170. package/src/compiler/config.ts +0 -369
  171. package/src/compiler/docs.ts +0 -127
  172. package/src/compiler/email-preview.ts +0 -314
  173. package/src/compiler/emails.ts +0 -404
  174. package/src/compiler/fonts.ts +0 -89
  175. package/src/compiler/generate.ts +0 -471
  176. package/src/compiler/image-blur.ts +0 -42
  177. package/src/compiler/image-report.ts +0 -88
  178. package/src/compiler/index.ts +0 -1362
  179. package/src/compiler/pages.ts +0 -49
  180. package/src/compiler/plugin.ts +0 -391
  181. package/src/compiler/prerender.ts +0 -263
  182. package/src/compiler/routes.ts +0 -170
  183. package/src/compiler/seo.ts +0 -415
  184. package/src/compiler/sri.ts +0 -144
  185. package/src/compiler/ssg.ts +0 -177
  186. package/src/compiler/ssr-codegen.ts +0 -85
  187. package/src/compiler/template-build.ts +0 -676
  188. package/src/compiler/template.ts +0 -262
  189. package/src/compiler/toil-docs.generated.ts +0 -77
  190. package/src/compiler/vite.ts +0 -245
  191. package/src/devserver/analytics/index.ts +0 -317
  192. package/src/devserver/config/dotenv.ts +0 -102
  193. package/src/devserver/config/env.ts +0 -30
  194. package/src/devserver/config/ratelimit.ts +0 -123
  195. package/src/devserver/daemon/catalog.ts +0 -120
  196. package/src/devserver/daemon/cron.ts +0 -87
  197. package/src/devserver/daemon/host.ts +0 -134
  198. package/src/devserver/daemon/index.ts +0 -369
  199. package/src/devserver/daemon/runtime.ts +0 -48
  200. package/src/devserver/db/catalog.ts +0 -109
  201. package/src/devserver/db/database.ts +0 -1834
  202. package/src/devserver/db/derives.ts +0 -121
  203. package/src/devserver/db/index.ts +0 -20
  204. package/src/devserver/db/routeKinds.ts +0 -191
  205. package/src/devserver/db/types.ts +0 -168
  206. package/src/devserver/email/caps.ts +0 -0
  207. package/src/devserver/email/config.ts +0 -141
  208. package/src/devserver/email/console.ts +0 -348
  209. package/src/devserver/email/index.ts +0 -155
  210. package/src/devserver/email/providers.ts +0 -131
  211. package/src/devserver/email/status.ts +0 -23
  212. package/src/devserver/email/validate.ts +0 -37
  213. package/src/devserver/email/wire.ts +0 -55
  214. package/src/devserver/http/cache.ts +0 -0
  215. package/src/devserver/http/envelope.ts +0 -157
  216. package/src/devserver/http/proxy.ts +0 -172
  217. package/src/devserver/http/runtime.ts +0 -387
  218. package/src/devserver/index.ts +0 -43
  219. package/src/devserver/production-ipc.ts +0 -63
  220. package/src/devserver/production-worker.ts +0 -83
  221. package/src/devserver/production.ts +0 -718
  222. package/src/devserver/runtime/crypto.ts +0 -587
  223. package/src/devserver/runtime/host.ts +0 -427
  224. package/src/devserver/runtime/module.ts +0 -442
  225. package/src/devserver/server.ts +0 -251
  226. package/src/devserver/ssr.ts +0 -208
  227. package/src/devserver/stream/catalog.ts +0 -106
  228. package/src/devserver/stream/host.ts +0 -42
  229. package/src/devserver/stream/index.ts +0 -301
  230. package/src/devserver/stream/manager.ts +0 -167
  231. package/src/devserver/stream/router.ts +0 -101
  232. package/src/devserver/stream/wire.ts +0 -58
  233. package/src/devserver/stream/ws.ts +0 -78
  234. package/src/devserver/wasm/sections.ts +0 -59
  235. package/src/devserver/wasm/surface.ts +0 -115
  236. package/src/io/FastMap.ts +0 -151
  237. package/src/io/FastSet.ts +0 -113
  238. package/src/io/codec.ts +0 -362
  239. package/src/io/index.ts +0 -10
  240. package/src/io/lengths.ts +0 -14
  241. package/src/io/types.ts +0 -19
  242. package/src/logger/index.ts +0 -22
  243. package/src/shared/index.ts +0 -46
  244. package/test/analytics-dev.test.ts +0 -113
  245. package/test/assembly/aspect-shim.ts +0 -33
  246. package/test/assembly/cookie.spec.ts +0 -303
  247. package/test/assembly/example.spec.ts +0 -22
  248. package/test/assembly/ssr.spec.ts +0 -95
  249. package/test/assembly.test.ts +0 -59
  250. package/test/auth-emails.test.ts +0 -118
  251. package/test/built-ssr.test.ts +0 -148
  252. package/test/channel.test.ts +0 -21
  253. package/test/config-host-port.test.ts +0 -59
  254. package/test/configure.test.ts +0 -94
  255. package/test/daemon-build.test.ts +0 -278
  256. package/test/daemon-catalog.test.ts +0 -349
  257. package/test/daemon-emulation.test.ts +0 -256
  258. package/test/db.test.ts +0 -0
  259. package/test/devserver-database.test.ts +0 -1102
  260. package/test/devserver-email.test.ts +0 -306
  261. package/test/devserver-pqauth.test.ts +0 -93
  262. package/test/devserver-secrets.test.ts +0 -63
  263. package/test/devserver.test.ts +0 -259
  264. package/test/doctor.test.ts +0 -298
  265. package/test/dom/Image.test.tsx +0 -144
  266. package/test/dom/Link.test.tsx +0 -67
  267. package/test/dom/NavLink.test.tsx +0 -37
  268. package/test/dom/Script.test.tsx +0 -48
  269. package/test/dom/action.test.tsx +0 -146
  270. package/test/dom/error-overlay.test.tsx +0 -44
  271. package/test/dom/loader.test.tsx +0 -121
  272. package/test/dom/navigation.test.ts +0 -59
  273. package/test/dom/revalidate.test.tsx +0 -38
  274. package/test/dom/route-head.test.tsx +0 -56
  275. package/test/dom/router-loading.test.tsx +0 -44
  276. package/test/dom/scroll.test.ts +0 -56
  277. package/test/dom/slot.test.tsx +0 -131
  278. package/test/dom/use-metadata.test.tsx +0 -58
  279. package/test/dom/view-transitions.test.tsx +0 -53
  280. package/test/dynamic-routing.test.ts +0 -132
  281. package/test/email-console.test.ts +0 -86
  282. package/test/email-preview.test.ts +0 -73
  283. package/test/emails.test.ts +0 -58
  284. package/test/errors.test.ts +0 -21
  285. package/test/example-guestbook.test.ts +0 -123
  286. package/test/features.test.ts +0 -149
  287. package/test/fixtures/bignum-wire/spec.ts +0 -25
  288. package/test/fixtures/daemon-app.ts +0 -65
  289. package/test/fixtures/stream-echo.ts +0 -26
  290. package/test/fixtures/stream-gate.ts +0 -28
  291. package/test/fixtures/stream-trap.ts +0 -18
  292. package/test/fixtures/stream-typed.ts +0 -41
  293. package/test/fonts.test.ts +0 -28
  294. package/test/global-setup.ts +0 -17
  295. package/test/head.test.ts +0 -42
  296. package/test/image-blur.test.ts +0 -50
  297. package/test/io.test.ts +0 -117
  298. package/test/metadata.test.ts +0 -40
  299. package/test/navlink.test.ts +0 -28
  300. package/test/pages.test.ts +0 -105
  301. package/test/placeholder.test.ts +0 -9
  302. package/test/pqauth-e2e.test.ts +0 -216
  303. package/test/pqauth-email.test.ts +0 -801
  304. package/test/prerender.test.ts +0 -116
  305. package/test/prettier-plugin.test.ts +0 -46
  306. package/test/routes.test.ts +0 -76
  307. package/test/rpc-bignum-wire.test.ts +0 -164
  308. package/test/rpc-dispatch.test.ts +0 -132
  309. package/test/rpc-kinds.test.ts +0 -18
  310. package/test/rpc.test.ts +0 -66
  311. package/test/search.test.ts +0 -114
  312. package/test/seo.test.ts +0 -175
  313. package/test/slot-layouts.test.ts +0 -69
  314. package/test/sri.test.ts +0 -129
  315. package/test/ssg.test.ts +0 -36
  316. package/test/ssr-hydration.test.tsx +0 -122
  317. package/test/ssr-render.test.ts +0 -197
  318. package/test/ssr-template.test.tsx +0 -482
  319. package/test/stream-emulation.test.ts +0 -479
  320. package/test/ui.test.ts +0 -40
  321. package/test/update.test.ts +0 -58
  322. package/test/validate.test.ts +0 -42
  323. package/test/version-check.test.ts +0 -130
  324. package/tests/data-parity/generated-parity.ts +0 -99
  325. package/tests/data-parity/parity.ts +0 -80
  326. package/tests/data-parity/spec.ts +0 -46
@@ -1,471 +0,0 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
-
4
- import { type ResolvedToilConfig } from './config.js';
5
- import { writeDocs } from './docs.js';
6
- import { buildPageIndex, pagesModuleSource } from './pages.js';
7
- import { type ScannedRoute, scanRoutes } from './routes.js';
8
- import { llmsTxt, robotsTxt, sitemapXml } from './seo.js';
9
-
10
- /**
11
- * Contents of the root `toil-env.d.ts`: ambient global types so `new DataWriter()` etc. resolve
12
- * in the IDE without an import. Script-mode declaration (no top-level import/export → the
13
- * `declare const`s are truly global, and it's not a module that could confuse ESLint's project
14
- * service); the inline `import('toiljs/io')` type only needs the normal `toiljs/io` export.
15
- * Lives at the project root because TypeScript's `include` globs skip dot-directories.
16
- * Exported so `toiljs create` can write it during scaffolding, before the first dev/build.
17
- */
18
- /** Side-effect style imports (e.g. `import './styles/main.css'`). */
19
- const STYLE_EXTENSIONS = ['css', 'scss', 'sass', 'less', 'styl', 'stylus', 'pcss', 'sss'];
20
- /** Asset imports whose default export is the resolved URL string (e.g. `import logo from './logo.svg'`). */
21
- const ASSET_EXTENSIONS = ['svg', 'png', 'jpg', 'jpeg', 'gif', 'webp', 'avif', 'ico', 'bmp', 'apng'];
22
-
23
- const STYLE_MODULES = STYLE_EXTENSIONS.map((ext) => `declare module '*.${ext}' {}`).join('\n');
24
- const ASSET_MODULES = ASSET_EXTENSIONS.map(
25
- (ext) => `declare module '*.${ext}' {\n const src: string;\n export default src;\n}`,
26
- ).join('\n');
27
-
28
- /**
29
- * Types for vite-imagetools query imports (e.g. `import src from './hero.png?w=800&as=srcset'`).
30
- * Declared inline rather than via `/// <reference types="vite-imagetools/client" />` because
31
- * vite-imagetools v10 ships no `client` types entry, and the package lives in toiljs's own
32
- * node_modules (unresolvable from a symlinked consumer). One `*` wildcard each, matching any
33
- * specifier ending in the directive.
34
- */
35
- const IMAGETOOLS_MODULES = [
36
- `declare module '*as=srcset' {\n const src: string;\n export default src;\n}`,
37
- `declare module '*as=url' {\n const src: string;\n export default src;\n}`,
38
- `declare module '*as=metadata' {\n const metadata: { src: string; width?: number; height?: number; format?: string }[];\n export default metadata;\n}`,
39
- // `import hero from './hero.webp?toil'` -> a Toil.Image source carrying an auto-generated blur LQIP
40
- // and intrinsic size (see compiler/image-blur.ts). Pass it straight to `<Toil.Image src={hero} />`.
41
- `declare module '*?toil' {\n const image: { src: string; width: number; height: number; blurDataURL: string };\n export default image;\n}`,
42
- ].join('\n');
43
-
44
- export const TOIL_ENV_DTS =
45
- `// AUTO-GENERATED by toil, do not edit.\n` +
46
- `declare const Toil: typeof import('toiljs/client');\n` +
47
- `declare namespace Toil {\n` +
48
- ` type LoaderArgs = import('toiljs/client').LoaderArgs;\n` +
49
- ` type LoaderFunction<T = unknown> = import('toiljs/client').LoaderFunction<T>;\n` +
50
- ` type Revalidate = import('toiljs/client').Revalidate;\n` +
51
- ` type Metadata = import('toiljs/client').Metadata;\n` +
52
- ` type GenerateMetadata<T = unknown> = import('toiljs/client').GenerateMetadata<T>;\n` +
53
- ` type GenerateStaticParams = import('toiljs/client').GenerateStaticParams;\n` +
54
- ` type StaticParams = import('toiljs/client').StaticParams;\n` +
55
- ` type RouteErrorProps = import('toiljs/client').RouteErrorProps;\n` +
56
- ` type Href = import('toiljs/client').Href;\n` +
57
- ` type RoutePath = import('toiljs/client').RoutePath;\n` +
58
- ` type PageMeta = import('toiljs/client').PageMeta;\n` +
59
- ` type SearchHints = import('toiljs/client').SearchHints;\n` +
60
- `}\n` +
61
- `declare const FastMap: typeof import('toiljs/io').FastMap;\n` +
62
- `declare const FastSet: typeof import('toiljs/io').FastSet;\n` +
63
- `declare const DataWriter: typeof import('toiljs/io').DataWriter;\n` +
64
- `declare const DataReader: typeof import('toiljs/io').DataReader;\n` +
65
- `declare const parseError: typeof import('toiljs/client').parseError;\n` +
66
- `\n` +
67
- `${STYLE_MODULES}\n` +
68
- `\n` +
69
- `${ASSET_MODULES}\n` +
70
- `\n` +
71
- `${IMAGETOOLS_MODULES}\n` +
72
- `\n` +
73
- `declare module 'toiljs/routes' {\n` +
74
- ` export const routes: import('toiljs/client').RouteDef[];\n` +
75
- ` export const layout: import('toiljs/client').LayoutLoader;\n` +
76
- ` export const notFound: import('toiljs/client').NotFoundLoader;\n` +
77
- ` export const globalError: import('toiljs/client').ErrorComponentLoader;\n` +
78
- ` export const slots: Record<string, import('toiljs/client').RouteDef[]>;\n` +
79
- ` export const pages: import('toiljs/client').PageMeta[];\n` +
80
- `}\n`;
81
-
82
- /**
83
- * Contents of `<serverDir>/toil-server-env.d.ts`: editor-only ambient
84
- * declarations for the toiljs server-runtime globals. ALIASES the real runtime
85
- * types (the same trick the IO globals use above), so a global-built `Cookie`
86
- * is the exact type the runtime APIs (`Response.setCookie`, `SecureCookies.seal`)
87
- * expect; redeclaring them as standalone classes makes a second, nominally-
88
- * incompatible `Cookie` (private fields). Regenerated by `buildServer` on every
89
- * build/dev so existing projects auto-migrate. Keep the declarations in sync
90
- * with the `toiljs create` scaffold (`src/cli/create.ts`).
91
- */
92
- export const TOIL_SERVER_ENV_DTS =
93
- `// AUTO-GENERATED by toil, do not edit. Editor-only ambient declarations for\n` +
94
- `// the toiljs server-runtime globals (Cookie, Cookies, SecureCookies, the\n` +
95
- `// cookie enums): @global in the runtime, used with no import. These alias the\n` +
96
- `// real runtime types so a global-built Cookie is exactly what Response.setCookie\n` +
97
- `// / SecureCookies.seal expect. The toilscript compiler registers them itself.\n` +
98
- `declare const SameSite: typeof import('toiljs/server/runtime/http/cookie').SameSite;\n` +
99
- `type SameSite = import('toiljs/server/runtime/http/cookie').SameSite;\n` +
100
- `declare const CookieEncoding: typeof import('toiljs/server/runtime/http/cookie').CookieEncoding;\n` +
101
- `type CookieEncoding = import('toiljs/server/runtime/http/cookie').CookieEncoding;\n` +
102
- `declare const CookiePrefix: typeof import('toiljs/server/runtime/http/cookie').CookiePrefix;\n` +
103
- `type CookiePrefix = import('toiljs/server/runtime/http/cookie').CookiePrefix;\n` +
104
- `declare const CookieValidation: typeof import('toiljs/server/runtime/http/cookie').CookieValidation;\n` +
105
- `type CookieValidation = import('toiljs/server/runtime/http/cookie').CookieValidation;\n` +
106
- `declare const Cookie: typeof import('toiljs/server/runtime/http/cookie').Cookie;\n` +
107
- `type Cookie = import('toiljs/server/runtime/http/cookie').Cookie;\n` +
108
- `declare const CookieMap: typeof import('toiljs/server/runtime/http/cookies').CookieMap;\n` +
109
- `type CookieMap = import('toiljs/server/runtime/http/cookies').CookieMap;\n` +
110
- `declare const Cookies: typeof import('toiljs/server/runtime/http/cookies').Cookies;\n` +
111
- `type Cookies = import('toiljs/server/runtime/http/cookies').Cookies;\n` +
112
- `declare const SecureCookies: typeof import('toiljs/server/runtime/http/securecookies').SecureCookies;\n` +
113
- `type SecureCookies = import('toiljs/server/runtime/http/securecookies').SecureCookies;\n` +
114
- `declare const Time: typeof import('toiljs/server/runtime/time').Time;\n` +
115
- `// Email, rate-limit, 2FA, and auth globals (server/globals/*), hand-declared\n` +
116
- `// because their AssemblyScript source can't be type-aliased from tsc.\n` +
117
- `declare enum EmailStatus { Sent, Disabled, Budget, RecipientCapped, Deduped, TryLater, BadRecipient, ProviderError }\n` +
118
- `declare namespace EmailService { function send(to: string, subject: string, body: string, purpose?: string, html?: string): EmailStatus; }\n` +
119
- `declare class RenderedEmail { subject: string; body: string; html: string; constructor(subject: string, body: string, html: string); }\n` +
120
- `declare class EmailTemplate { constructor(subject: string, body: string, html?: string); render(vars: Map<string, string>): RenderedEmail; send(to: string, vars: Map<string, string>, purpose?: string): EmailStatus; }\n` +
121
- // `RateLimit` (the @ratelimit strategy enum) is declared by toilscript's std d.ts,
122
- // which owns the @ratelimit decorator; declaring it here too would collide.
123
- `declare namespace RateLimitService { function guard(routeId: i32, strategy: i32, limit: i32, window: i32): import('toiljs/server/runtime/response').Response | null; function guardKeyed(routeId: i32, strategy: i32, limit: i32, window: i32, key: string): import('toiljs/server/runtime/response').Response | null; }\n` +
124
- `declare namespace Environment { function get(key: string): string | null; function getSecure(key: string): string | null; }\n` +
125
- `declare class TwoFactorIssue { code: string; token: string; constructor(code: string, token: string); }\n` +
126
- `declare class TwoFactorChallenge { token: string; status: EmailStatus; constructor(token: string, status: EmailStatus); }\n` +
127
- `declare namespace TwoFactor { const DEFAULT_TTL_SECS: u64; const DEFAULT_DIGITS: i32; function setSecret(secret: Uint8Array): void; function issue(recipient: string, purpose: string, ttlSecs?: u64, digits?: i32): TwoFactorIssue; function send(recipient: string, purpose: string, ttlSecs?: u64, digits?: i32): TwoFactorChallenge; function verify(token: string, recipient: string, code: string): bool; }\n` +
128
- `declare namespace AuthService { const SESSION_COOKIE: string; const USER_COOKIE: string; const LOGIN_CONTEXT: string; const PUBLIC_KEY_LEN: i32; const SIGNATURE_LEN: i32; const DEFAULT_SESSION_TTL_SECS: u64; function setSecret(secret: Uint8Array): void; function hasSession(): bool; function getSessionBytes(): Uint8Array | null; function getUser(): __ToilAuthUser | null; function userId(): ToilUserId | null; function enable(): void; function mintSession(userData: Uint8Array, ttlSecs?: u64): Cookie; function clearSession(): Cookie; function userCookie(userData: Uint8Array, ttlSecs?: u64): Cookie; function clearUserCookie(): Cookie; function buildLoginMessage(sub: string, aud: string, cid: Uint8Array, nonce: Uint8Array, iat: u64, exp: u64, ciphertext: Uint8Array, memKiB: u32, iterations: u32, parallelism: u32, serverKemKeyId: Uint8Array): Uint8Array; function verifyLogin(publicKey: Uint8Array, message: Uint8Array, signature: Uint8Array): bool; const KEM_CIPHERTEXT_LEN: i32; const KEM_SECRET_KEY_LEN: i32; const KEM_PUBLIC_KEY_LEN: i32; const SHARED_SECRET_LEN: i32; const OPRF_ELEMENT_LEN: i32; const OPRF_SEED_LEN: i32; const SESSION_KEY_LABEL: string; const SERVER_CONFIRM_LABEL: string; function setOprfSeed(seed: Uint8Array): void; function setServerKemSecretKey(secretKey: Uint8Array): void; function setServerKemPublicKey(publicKey: Uint8Array): void; function serverKemKeyId(): Uint8Array; function oprfEvaluate(username: string, blinded: Uint8Array): Uint8Array; function mlkemDecapsulate(ciphertext: Uint8Array): Uint8Array; function sha256(data: Uint8Array): Uint8Array; function deriveSessionKey(sharedSecret: Uint8Array, transcriptHash: Uint8Array): Uint8Array; function serverConfirmTag(sessionKey: Uint8Array, transcriptHash: Uint8Array): Uint8Array; const REGISTER_CONTEXT: string; function buildRegisterMessage(username: string, publicKey: Uint8Array): Uint8Array; function verifyRegister(publicKey: Uint8Array, message: Uint8Array, signature: Uint8Array): bool; }\n` +
129
- `interface __ToilAuthUser {}\n` +
130
- `declare class ToilUserId { w0: u64; w1: u64; w2: u64; w3: u64; constructor(w0?: u64, w1?: u64, w2?: u64, w3?: u64); static derive(mldsaPublicKey: Uint8Array, identifier: string, domain: string): ToilUserId; static fromBytes(b: Uint8Array): ToilUserId; toBytes(): Uint8Array; toHex(): string; isZero(): bool; equals(other: ToilUserId): bool; }\n` +
131
- // Built-in auth (server.auth): injected by --authUser. Mints a session for the program's single @user
132
- // (built-in or the app's extended one): constructs it, sets the reserved identity, encodes it.
133
- `declare function __toilEncodeAuthUser(toilUserId: Uint8Array, username: string): Uint8Array;\n`;
134
-
135
- /**
136
- * Returns a `./`-prefixed, **extensionless** POSIX module specifier from `.toil` to `abs`, for use
137
- * in generated `import(...)` calls. Extensionless so TypeScript doesn't demand
138
- * `allowImportingTsExtensions` (TS5097) when the generated files are checked; Vite still resolves it.
139
- */
140
- function relFromToil(cfg: ResolvedToilConfig, abs: string): string {
141
- let rel = path
142
- .relative(cfg.toilDir, abs)
143
- .replace(/\\/g, '/')
144
- .replace(/\.(tsx|jsx)$/, '');
145
- if (!rel.startsWith('.')) rel = './' + rel;
146
- return rel;
147
- }
148
-
149
- export function findLayout(cfg: ResolvedToilConfig): string | undefined {
150
- return ['layout.tsx', 'layout.jsx']
151
- .map((name) => path.join(cfg.clientAbsDir, name))
152
- .find((p) => fs.existsSync(p));
153
- }
154
-
155
- /** Finds an optional custom not-found page at `client/404.{tsx,jsx}`. */
156
- function findNotFound(cfg: ResolvedToilConfig): string | undefined {
157
- return ['404.tsx', '404.jsx']
158
- .map((name) => path.join(cfg.clientAbsDir, name))
159
- .find((p) => fs.existsSync(p));
160
- }
161
-
162
- /** Finds an optional root error boundary at `client/global-error.{tsx,jsx}`. */
163
- function findGlobalError(cfg: ResolvedToilConfig): string | undefined {
164
- return ['global-error.tsx', 'global-error.jsx']
165
- .map((name) => path.join(cfg.clientAbsDir, name))
166
- .find((p) => fs.existsSync(p));
167
- }
168
-
169
- /**
170
- * Builds the `RoutePath` union for typed `Link`/`navigate` hrefs: static routes as string literals,
171
- * dynamic/catch-all as `` `…/${string}` `` templates (optional catch-all also emits its bare prefix).
172
- */
173
- function routePathUnion(routes: ScannedRoute[]): string {
174
- const members = new Set<string>();
175
- for (const route of routes) {
176
- const segments = route.pattern.split('/').filter(Boolean);
177
- const isDynamic = segments.some((s) => s.startsWith(':') || s.startsWith('*'));
178
- if (!isDynamic) {
179
- members.add(`'${route.pattern}'`);
180
- continue;
181
- }
182
- const parts = segments.map((s) =>
183
- s.startsWith(':') || s.startsWith('*') ? '${string}' : s,
184
- );
185
- members.add('`/' + parts.join('/') + '`');
186
- const optionalIdx = segments.findIndex((s) => s.startsWith('**'));
187
- if (optionalIdx !== -1) {
188
- const prefix = '/' + segments.slice(0, optionalIdx).join('/');
189
- members.add(`'${prefix}'`);
190
- }
191
- }
192
- return members.size ? [...members].join(' | ') : 'string';
193
- }
194
-
195
- /**
196
- * The `toil-routes.d.ts` contents: a module augmentation registering the project's route paths so
197
- * `Link`/`navigate`/`useRouter` hrefs are type-checked. Regenerated each dev/build.
198
- */
199
- function routesDts(cfg: ResolvedToilConfig, routes: ScannedRoute[]): string {
200
- // Type-only namespace import of every route module (erased at build) so editors don't flag a
201
- // route's `loader` / `metadata` / `generateMetadata` / `revalidate` / `default` exports as unused,
202
- // the compiler consumes them via dynamic `import()`, which editors don't count as a reference.
203
- const refs = routes.map((route, i) => {
204
- let rel = path
205
- .relative(cfg.root, route.file)
206
- .replace(/\\/g, '/')
207
- .replace(/\.(tsx|jsx)$/, '');
208
- if (!rel.startsWith('.')) rel = `./${rel}`;
209
- return { name: `_toilRoute${String(i)}`, rel };
210
- });
211
- const imports = refs
212
- .map((m) => `import type * as ${m.name} from ${JSON.stringify(m.rel)};\n`)
213
- .join('');
214
- const referenced = refs.length
215
- ? `export type _ToilRouteModules = [${refs.map((m) => `typeof ${m.name}`).join(', ')}];\n`
216
- : `export {};\n`;
217
- return (
218
- `// AUTO-GENERATED by toil, do not edit.\n` +
219
- imports +
220
- referenced +
221
- `declare module 'toiljs/client' {\n` +
222
- ` interface Register {\n` +
223
- ` routePath: ${routePathUnion(routes)};\n` +
224
- ` }\n` +
225
- `}\n`
226
- );
227
- }
228
-
229
- /** Finds the user-owned app entry at `client/toil.{tsx,jsx}` (where `mount` is called). */
230
- function findEntry(cfg: ResolvedToilConfig): string | undefined {
231
- return ['toil.tsx', 'toil.jsx']
232
- .map((name) => path.join(cfg.clientAbsDir, name))
233
- .find((p) => fs.existsSync(p));
234
- }
235
-
236
- /** A `<base>.{tsx,jsx}` in `dir`, or undefined. */
237
- function specialIn(dir: string, base: string): string | undefined {
238
- return [`${base}.tsx`, `${base}.jsx`]
239
- .map((name) => path.join(dir, name))
240
- .find((p) => fs.existsSync(p));
241
- }
242
-
243
- /**
244
- * Chain of `<base>.{tsx,jsx}` files wrapping a route, shallowest → deepest: the routes root and each
245
- * ancestor directory down to the file's own. With `includeClientRoot`, `client/<base>` is prepended
246
- * as the outermost (used by templates; the root `client/layout.tsx` is instead the top-level layout).
247
- */
248
- export function findSpecialChain(
249
- cfg: ResolvedToilConfig,
250
- routeFile: string,
251
- base: string,
252
- includeClientRoot: boolean,
253
- ): string[] {
254
- const chain: string[] = [];
255
- const relDir = path.dirname(path.relative(cfg.routesAbsDir, routeFile));
256
- const segments = relDir === '.' ? [] : relDir.split(path.sep);
257
- // A parallel-slot route (one under an `@slot` segment) is rendered INTO a parent layout's
258
- // `<Slot>`. Its own layout/template chain must therefore start at the `@slot` directory, not the
259
- // routes root: the parent segments' layouts already wrap the slot, so re-including them here
260
- // would nest the slot inside itself and recurse. For non-slot routes this is the full chain.
261
- const slotIdx = segments.findIndex((s) => s.startsWith('@'));
262
- const startAt = slotIdx < 0 ? 0 : slotIdx + 1;
263
- if (includeClientRoot && slotIdx < 0) {
264
- const root = specialIn(cfg.clientAbsDir, base);
265
- if (root) chain.push(root);
266
- }
267
- let dir = cfg.routesAbsDir;
268
- for (let i = 0; i <= segments.length; i++) {
269
- if (i > 0) dir = path.join(dir, segments[i - 1]);
270
- if (i < startAt) continue;
271
- const found = specialIn(dir, base);
272
- if (found) chain.push(found);
273
- }
274
- return chain;
275
- }
276
-
277
- /** Nearest special file named `base` (e.g. `loading`/`error`) from the route's dir up to the routes root. */
278
- function findNearest(cfg: ResolvedToilConfig, routeFile: string, base: string): string | undefined {
279
- const root = path.resolve(cfg.routesAbsDir);
280
- let dir = path.dirname(routeFile);
281
- for (;;) {
282
- const found = [`${base}.tsx`, `${base}.jsx`]
283
- .map((name) => path.join(dir, name))
284
- .find((p) => fs.existsSync(p));
285
- if (found) return found;
286
- if (path.resolve(dir) === root) return undefined;
287
- const parent = path.dirname(dir);
288
- if (parent === dir) return undefined;
289
- dir = parent;
290
- }
291
- }
292
-
293
- /**
294
- * Generates the `.toil/` working dir (routes table, mount entry, the HTML entry built from the
295
- * project's `public/index.html` template, and mirrored `public/` assets) and returns the scanned
296
- * routes. Called before every dev/build and on route add/remove during dev.
297
- */
298
- export function generate(cfg: ResolvedToilConfig): ScannedRoute[] {
299
- const routes = scanRoutes(cfg.routesAbsDir);
300
- fs.mkdirSync(cfg.toilDir, { recursive: true });
301
-
302
- const layoutFile = findLayout(cfg);
303
- const notFoundFile = findNotFound(cfg);
304
- const globalErrorFile = findGlobalError(cfg);
305
- const imp = (f: string): string => `() => import(${JSON.stringify(relFromToil(cfg, f))})`;
306
- const routeObj = (r: ScannedRoute): string => {
307
- const layouts = findSpecialChain(cfg, r.file, 'layout', false).map(imp).join(', ');
308
- const templates = findSpecialChain(cfg, r.file, 'template', true).map(imp).join(', ');
309
- const parts = [
310
- `pattern: ${JSON.stringify(r.pattern)}`,
311
- `load: ${imp(r.file)}`,
312
- `layouts: [${layouts}]`,
313
- ];
314
- if (templates) parts.push(`templates: [${templates}]`);
315
- const loadingFile = findNearest(cfg, r.file, 'loading');
316
- if (loadingFile) parts.push(`loading: ${imp(loadingFile)}`);
317
- const errorFile = findNearest(cfg, r.file, 'error');
318
- if (errorFile) parts.push(`errorComponent: ${imp(errorFile)}`);
319
- if (r.intercept) parts.push(`intercept: true`);
320
- return `{ ${parts.join(', ')} }`;
321
- };
322
- const mainRoutes = routes.filter((r) => r.slot === undefined);
323
- const slotNames = [...new Set(routes.flatMap((r) => (r.slot ? [r.slot] : [])))];
324
- const slotsBody = slotNames
325
- .map((name) => {
326
- const items = routes
327
- .filter((r) => r.slot === name)
328
- .map((r) => ` ${routeObj(r)},`)
329
- .join('\n');
330
- return ` ${JSON.stringify(name)}: [\n${items}\n ],`;
331
- })
332
- .join('\n');
333
- const pages = buildPageIndex(cfg.root, routes);
334
- const routesSrc =
335
- `// @ts-nocheck\n` +
336
- `// AUTO-GENERATED by toil, do not edit.\n` +
337
- `import type { RouteDef, LayoutLoader, NotFoundLoader, PageMeta } from 'toiljs/client';\n\n` +
338
- `export const routes: RouteDef[] = [\n${mainRoutes.map((r) => ` ${routeObj(r)},`).join('\n')}\n];\n\n` +
339
- `export const slots: Record<string, RouteDef[]> = {\n${slotsBody}\n};\n\n` +
340
- `export const layout: LayoutLoader = ${layoutFile ? `() => import(${JSON.stringify(relFromToil(cfg, layoutFile))})` : 'null'};\n` +
341
- `export const notFound: NotFoundLoader = ${notFoundFile ? `() => import(${JSON.stringify(relFromToil(cfg, notFoundFile))})` : 'null'};\n` +
342
- `export const globalError = ${globalErrorFile ? `() => import(${JSON.stringify(relFromToil(cfg, globalErrorFile))})` : 'null'};\n\n` +
343
- pagesModuleSource(pages);
344
- fs.writeFileSync(path.join(cfg.toilDir, 'routes.ts'), routesSrc);
345
-
346
- const globalsSrc =
347
- `// @ts-nocheck\n` +
348
- `// AUTO-GENERATED by toil, do not edit.\n` +
349
- `import * as Toil from 'toiljs/client';\n` +
350
- `import { FastMap, FastSet, DataWriter, DataReader } from 'toiljs/io';\n` +
351
- `import { pages } from './routes';\n\n` +
352
- `Object.assign(globalThis, { Toil, FastMap, FastSet, DataWriter, DataReader, Server: Toil.Server, parseError: Toil.parseError });\n` +
353
- `Toil.setViewTransitions(${String(cfg.viewTransitions)});\n` +
354
- `Toil.setTransitions(${String(cfg.transitions)});\n` +
355
- `Toil.registerPages(pages);\n`;
356
- fs.writeFileSync(path.join(cfg.toilDir, 'globals.ts'), globalsSrc);
357
-
358
- const entryFile = findEntry(cfg);
359
- const entrySrc = entryFile
360
- ? `// @ts-nocheck\n` +
361
- `// AUTO-GENERATED by toil, do not edit.\n` +
362
- `import './globals';\n` +
363
- `import ${JSON.stringify(relFromToil(cfg, entryFile))};\n`
364
- : `// @ts-nocheck\n` +
365
- `// AUTO-GENERATED by toil, do not edit.\n` +
366
- `import './globals';\n` +
367
- `import { mount } from 'toiljs/client';\n` +
368
- `import { routes, layout, notFound, globalError, slots } from './routes';\n\n` +
369
- `mount(routes, layout, notFound, globalError, slots);\n`;
370
- fs.writeFileSync(path.join(cfg.toilDir, 'entry.tsx'), entrySrc);
371
-
372
- fs.writeFileSync(path.join(cfg.root, 'toil-env.d.ts'), TOIL_ENV_DTS);
373
- fs.writeFileSync(path.join(cfg.root, 'toil-routes.d.ts'), routesDts(cfg, routes));
374
-
375
- fs.writeFileSync(path.join(cfg.toilDir, 'index.html'), buildHtml(cfg));
376
- syncPublicAssets(cfg);
377
- writeSeoFiles(cfg, routes);
378
- writeDocs(cfg.toilDir);
379
-
380
- return routes;
381
- }
382
-
383
- /**
384
- * Writes the build-time SEO crawler files into `.toil/public` (Vite's publicDir, served in dev and
385
- * copied to the output root in build): `robots.txt` (incl. AI-crawler directives), `sitemap.xml`
386
- * (static routes), and `llms.txt` (AI guidance). No-op when SEO isn't configured.
387
- */
388
- function writeSeoFiles(cfg: ResolvedToilConfig, routes: ScannedRoute[]): void {
389
- if (!cfg.seo) return;
390
- const dest = path.join(cfg.toilDir, 'public');
391
- const files: [string, string][] = [
392
- ['robots.txt', robotsTxt(cfg.seo)],
393
- ['sitemap.xml', sitemapXml(cfg.seo, routes)],
394
- ['llms.txt', llmsTxt(cfg.seo, routes)],
395
- ];
396
- const present = files.filter(([, content]) => content !== '');
397
- if (present.length === 0) return;
398
- fs.mkdirSync(dest, { recursive: true });
399
- for (const [name, content] of present) fs.writeFileSync(path.join(dest, name), content);
400
- }
401
-
402
- /** Fallback HTML when the project has no `public/index.html` template. The entry script is added
403
- * by {@link buildHtml}. */
404
- const DEFAULT_HTML =
405
- `<!doctype html>\n<html lang="en">\n <head>\n <meta charset="utf-8" />\n` +
406
- ` <meta name="viewport" content="width=device-width, initial-scale=1" />\n` +
407
- ` <meta name="description" content="" />\n` +
408
- ` <title>Toil App</title>\n </head>\n <body>\n <div id="root"></div>\n` +
409
- ` </body>\n</html>\n`;
410
-
411
- /** The module entry that boots the app, injected into the HTML (resolved relative to `.toil`). */
412
- const ENTRY_SCRIPT = `<script type="module" src="./entry.tsx"></script>`;
413
-
414
- /** Toil's component base CSS, baked into the shell `<head>` so built-in components (the `Image`
415
- * `fill`/blur layout) style via overridable classes instead of forced-inline styles, and stay
416
- * SSR-safe (present before JS). The app can restyle `.toil-img-*` freely. */
417
- const TOIL_BASE_STYLE =
418
- `<style id="toil-base">` +
419
- `.toil-img-fill-box{display:block;overflow:hidden}` +
420
- `.toil-img-fill{display:block;width:100%;height:100%;object-fit:cover}` +
421
- `.toil-img-blur{background-size:cover;background-position:center;filter:blur(20px)}` +
422
- `.toil-img-skeleton{background:linear-gradient(90deg,#ececed 25%,#f4f4f5 37%,#ececed 63%);background-size:400% 100%;animation:toil-img-shimmer 1.4s ease infinite}` +
423
- `@keyframes toil-img-shimmer{0%{background-position:100% 50%}100%{background-position:0 50%}}` +
424
- `@media (prefers-reduced-motion:reduce){.toil-img-skeleton{animation:none}}` +
425
- `</style>`;
426
-
427
- /**
428
- * Produces the `.toil/index.html` Vite entry from the project's `public/index.html` template (or
429
- * the built-in default if absent), ensuring the generated module entry script is present. Users
430
- * own the template, toil only guarantees the entry is wired, so it stays the SPA root.
431
- */
432
- function buildHtml(cfg: ResolvedToilConfig): string {
433
- const templatePath = path.join(cfg.publicDir, 'index.html');
434
- let html = fs.existsSync(templatePath) ? fs.readFileSync(templatePath, 'utf8') : DEFAULT_HTML;
435
- // Inject the entry only if the template doesn't already reference it as a module script
436
- // (matching the literal filename anywhere in the file would be too eager).
437
- if (!/src=["']\.\/entry\.tsx["']/.test(html)) {
438
- html = html.includes('</body>')
439
- ? html.replace('</body>', ` ${ENTRY_SCRIPT}\n </body>`)
440
- : `${html}\n${ENTRY_SCRIPT}\n`;
441
- }
442
- // Ship toil's component base CSS once, so `Image` `fill`/blur lay out via overridable classes
443
- // instead of inline styles. Idempotent (keyed on the `toil-base` id).
444
- if (!html.includes('id="toil-base"')) {
445
- html = html.includes('</head>')
446
- ? html.replace('</head>', ` ${TOIL_BASE_STYLE}\n </head>`)
447
- : `${TOIL_BASE_STYLE}\n${html}`;
448
- }
449
- return html;
450
- }
451
-
452
- /**
453
- * Mirrors the project's `public/` assets into `.toil/public/` (Vite's publicDir under the `.toil`
454
- * root), excluding the `index.html` template, that is processed into the entry above, and copying
455
- * it here would clobber the built, asset-hashed page. Cleared each run so deletions propagate.
456
- */
457
- function syncPublicAssets(cfg: ResolvedToilConfig): void {
458
- const dest = path.join(cfg.toilDir, 'public');
459
- fs.rmSync(dest, { recursive: true, force: true });
460
- if (!fs.existsSync(cfg.publicDir)) return;
461
-
462
- let copied = 0;
463
- for (const entry of fs.readdirSync(cfg.publicDir, { withFileTypes: true })) {
464
- if (entry.name === 'index.html') continue;
465
- fs.cpSync(path.join(cfg.publicDir, entry.name), path.join(dest, entry.name), {
466
- recursive: true,
467
- });
468
- copied++;
469
- }
470
- if (copied === 0) fs.rmSync(dest, { recursive: true, force: true });
471
- }
@@ -1,42 +0,0 @@
1
- import type { Plugin } from 'vite';
2
-
3
- /** An image import carrying the `?toil` flag: `import hero from './hero.webp?toil'`. */
4
- const TOIL_QUERY = /[?&]toil(?:&|$)/;
5
- /** Raster formats sharp can downscale into an LQIP. (SVG/animated stay as-is — no blur.) */
6
- const RASTER = /\.(?:png|jpe?g|webp|avif|gif|tiff?)(?:\?|$)/i;
7
-
8
- /**
9
- * Auto-generates a tiny blurred base64 LQIP (low-quality image placeholder) for raster images imported
10
- * with the `?toil` flag, and returns a `{ src, width, height, blurDataURL }` object that `Toil.Image`
11
- * consumes to auto-fill its `blurDataURL` (`placeholder="blur"`) and its aspect-ratio — the way Next.js
12
- * bakes a `blurDataURL` for static image imports. Runs in dev and build (Vite `load`); `sharp` is loaded
13
- * lazily so it only costs anything when a `?toil` import is actually resolved.
14
- *
15
- * Usage: `import hero from './hero.webp?toil'` then `<Toil.Image src={hero} placeholder="blur" />`.
16
- * The bare `src` is re-imported with no query, so Vite/imagetools still optimize + hash the real asset
17
- * (this plugin only adds the placeholder + intrinsic size); it skips itself on that bare import.
18
- */
19
- export function imageBlurPlugin(): Plugin {
20
- return {
21
- name: 'toil:image-blur',
22
- enforce: 'pre',
23
- async load(id) {
24
- if (!TOIL_QUERY.test(id) || !RASTER.test(id)) return undefined;
25
- const file = id.replace(/\?.*$/, '');
26
- const { default: sharp } = await import('sharp');
27
- const meta = await sharp(file).metadata();
28
- // ~24px longest edge, blurred, webp — a few hundred base64 bytes, inlined as a data URI.
29
- const lqip = await sharp(file)
30
- .resize(24, 24, { fit: 'inside' })
31
- .blur()
32
- .webp({ quality: 40 })
33
- .toBuffer();
34
- const blurDataURL = `data:image/webp;base64,${lqip.toString('base64')}`;
35
- return (
36
- `import src from ${JSON.stringify(file)};\n` +
37
- `export default { src, width: ${meta.width ?? 0}, height: ${meta.height ?? 0}, ` +
38
- `blurDataURL: ${JSON.stringify(blurDataURL)} };\n`
39
- );
40
- },
41
- };
42
- }
@@ -1,88 +0,0 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
-
4
- import pc from 'picocolors';
5
- import type { Logger, Plugin } from 'vite';
6
-
7
- /** Raster/vector outputs the image pipeline may emit. */
8
- const IMAGE_RE = /\.(png|jpe?g|webp|avif|gif|tiff|svg)$/i;
9
-
10
- /** Formats a byte count like Vite's asset table (kB, base 1000). */
11
- function kb(bytes: number): string {
12
- return `${(bytes / 1000).toFixed(2)} kB`;
13
- }
14
-
15
- interface Variant {
16
- readonly out: string;
17
- readonly outSize: number;
18
- }
19
-
20
- /**
21
- * Build-only plugin that reports which imported images the pipeline optimized, each source image,
22
- * its emitted variant(s), and the size saved. `public/` assets (copied as-is) never enter the
23
- * bundle, so they don't appear here. Logs nothing when no images were processed.
24
- *
25
- * `viteRoot` is Vite's root (the `.toil` dir) that emitted assets' `originalFileNames` are relative
26
- * to; `projectRoot` is used only to print friendly source paths.
27
- */
28
- export function imageReportPlugin(projectRoot: string, viteRoot: string): Plugin {
29
- let logger: Logger | undefined;
30
- return {
31
- name: 'toil:image-report',
32
- apply: 'build',
33
- configResolved(config) {
34
- logger = config.logger;
35
- },
36
- writeBundle(_options, bundle) {
37
- // Group emitted image assets by their source file.
38
- const bySource = new Map<
39
- string,
40
- { label: string; inSize: number | null; variants: Variant[] }
41
- >();
42
- for (const file of Object.values(bundle)) {
43
- if (file.type !== 'asset' || !IMAGE_RE.test(file.fileName)) continue;
44
- const source = file.originalFileNames[0];
45
- const key = source ?? file.fileName;
46
- const outSize =
47
- typeof file.source === 'string'
48
- ? Buffer.byteLength(file.source)
49
- : file.source.byteLength;
50
-
51
- let entry = bySource.get(key);
52
- if (!entry) {
53
- let inSize: number | null = null;
54
- let label = '(generated)';
55
- if (source !== undefined) {
56
- const abs = path.resolve(viteRoot, source);
57
- label = path.relative(projectRoot, abs);
58
- try {
59
- inSize = fs.statSync(abs).size;
60
- } catch {
61
- inSize = null;
62
- }
63
- }
64
- entry = { label, inSize, variants: [] };
65
- bySource.set(key, entry);
66
- }
67
- entry.variants.push({ out: file.fileName, outSize });
68
- }
69
-
70
- if (bySource.size === 0 || !logger) return;
71
-
72
- const count = bySource.size;
73
- logger.info('');
74
- logger.info(pc.green(` ✓ optimized ${String(count)} image${count === 1 ? '' : 's'}`));
75
- for (const { label, inSize, variants } of bySource.values()) {
76
- logger.info(` ${pc.dim(label)}`);
77
- for (const v of variants) {
78
- const saved =
79
- inSize && inSize > 0
80
- ? pc.green(` -${String(Math.round((1 - v.outSize / inSize) * 100))}%`)
81
- : '';
82
- const from = inSize ? `${kb(inSize)} → ` : '';
83
- logger.info(` ${pc.dim('→')} ${v.out} ${from}${kb(v.outSize)}${saved}`);
84
- }
85
- }
86
- },
87
- };
88
- }