toiljs 0.0.10 → 0.0.11

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 (39) hide show
  1. package/README.md +313 -1
  2. package/assets/logo.svg +37 -0
  3. package/build/cli/.tsbuildinfo +1 -1
  4. package/build/cli/create.js +2 -2
  5. package/build/compiler/.tsbuildinfo +1 -1
  6. package/build/compiler/generate.js +9 -3
  7. package/build/compiler/vite.js +7 -0
  8. package/examples/basic/client/components/Header.tsx +4 -1
  9. package/examples/basic/client/layout.tsx +4 -1
  10. package/examples/basic/client/public/index.html +1 -1
  11. package/examples/basic/client/routes/(legal)/privacy.tsx +19 -0
  12. package/examples/basic/client/routes/(legal)/terms.tsx +16 -0
  13. package/examples/basic/client/routes/about.tsx +8 -5
  14. package/examples/basic/client/routes/blog/[id].tsx +7 -1
  15. package/examples/basic/client/routes/features/actions.tsx +67 -0
  16. package/examples/basic/client/routes/features/error/error.tsx +16 -0
  17. package/examples/basic/client/routes/features/error/index.tsx +27 -0
  18. package/examples/basic/client/routes/features/head.tsx +38 -0
  19. package/examples/basic/client/routes/features/index.tsx +75 -0
  20. package/examples/basic/client/routes/features/realtime.tsx +32 -0
  21. package/examples/basic/client/routes/features/script.tsx +31 -0
  22. package/examples/basic/client/routes/features/seo.tsx +39 -0
  23. package/examples/basic/client/routes/features/template/b.tsx +14 -0
  24. package/examples/basic/client/routes/features/template/index.tsx +20 -0
  25. package/examples/basic/client/routes/features/template/template.tsx +18 -0
  26. package/examples/basic/client/routes/files/[[...slug]].tsx +21 -0
  27. package/examples/basic/client/routes/gallery/@modal/(.)photo/[id].tsx +23 -0
  28. package/examples/basic/client/routes/gallery/index.tsx +42 -0
  29. package/examples/basic/client/routes/gallery/layout.tsx +13 -0
  30. package/examples/basic/client/routes/gallery/photo/[id].tsx +18 -0
  31. package/examples/basic/client/routes/index.tsx +11 -2
  32. package/examples/basic/client/routes/loader-demo/index.tsx +6 -4
  33. package/examples/basic/client/toil.tsx +2 -4
  34. package/package.json +3 -2
  35. package/src/cli/create.ts +2 -2
  36. package/src/compiler/generate.ts +12 -3
  37. package/src/compiler/vite.ts +15 -0
  38. package/test/dom/route-head.test.tsx +34 -0
  39. package/test/slot-layouts.test.ts +69 -0
package/README.md CHANGED
@@ -1 +1,313 @@
1
- todo
1
+ <div align="center">
2
+
3
+ <img src="assets/logo.svg" alt="ToilJS" width="128" height="128" />
4
+
5
+ # ToilJS
6
+
7
+ ### Everything React forgot to ship.
8
+
9
+ <sub>Fast by design. Architecture chosen for hyper scale: 50 Gbit/s on commodity hardware.</sub>
10
+
11
+ <br/>
12
+
13
+ [![npm](https://img.shields.io/npm/v/toiljs.svg?color=2563ff&label=npm&labelColor=0e1520)](https://www.npmjs.com/package/toiljs)
14
+ [![types](https://img.shields.io/badge/types-included-2563ff.svg?labelColor=0e1520)](https://www.typescriptlang.org/)
15
+ [![react](https://img.shields.io/badge/react-19-22e3ab.svg?labelColor=0e1520)](https://react.dev/)
16
+ [![server](https://img.shields.io/badge/server-WebAssembly-7c3aed.svg?labelColor=0e1520)](#built-for-scale)
17
+ [![license](https://img.shields.io/badge/license-Apache--2.0-8b9ab4.svg?labelColor=0e1520)](./LICENSE)
18
+
19
+ <br/>
20
+
21
+ <img src="https://img.shields.io/badge/React_19-20232a?style=for-the-badge&logo=react&logoColor=61dafb" alt="React 19" />
22
+ <img src="https://img.shields.io/badge/TypeScript-3178c6?style=for-the-badge&logo=typescript&logoColor=white" alt="TypeScript" />
23
+ <img src="https://img.shields.io/badge/Vite-646cff?style=for-the-badge&logo=vite&logoColor=white" alt="Vite" />
24
+ <img src="https://img.shields.io/badge/WebAssembly-654ff0?style=for-the-badge&logo=webassembly&logoColor=white" alt="WebAssembly" />
25
+
26
+ </div>
27
+
28
+ ---
29
+
30
+ React gives you a renderer and leaves the rest to you: a router, a bundler, data fetching, SEO, an image pipeline, a server. ToilJS is all of it, already wired.
31
+
32
+ ```bash
33
+ npx toiljs create my-app
34
+ cd my-app
35
+ npm run dev
36
+ ```
37
+
38
+ Drop a `.tsx` file in `client/routes/` and it is a route: typed, code-split, prefetched, data loaded before render. The `server/` compiles to WebAssembly and self-hosts on uWebSockets. You configured nothing.
39
+
40
+ ## Built for scale
41
+
42
+ The backend is the point. `server/` is [ToilScript](https://www.npmjs.com/package/toilscript) compiled to a single WebAssembly module (Binaryen), and `toiljs start` self-hosts the app on [hyper-express](https://github.com/kartikk221/hyper-express), backed by [uWebSockets.js](https://github.com/uNetworking/uWebSockets.js), the same core that serves millions of HTTP requests per second.
43
+
44
+ <div align="center">
45
+
46
+ ![throughput](https://img.shields.io/badge/throughput-50_Gbit%2Fs-2563ff?style=for-the-badge)
47
+ ![requests](https://img.shields.io/badge/requests-millions%2Fsec-7c3aed?style=for-the-badge)
48
+ ![compute](https://img.shields.io/badge/compute-native_WASM-22e3ab?style=for-the-badge)
49
+ ![transport](https://img.shields.io/badge/transport-HTTP%2F3_+_WebTransport-654ff0?style=for-the-badge)
50
+
51
+ </div>
52
+
53
+ - **WebAssembly compute.** Your server logic runs as native-speed WASM, not interpreted JS.
54
+ - **Binary on the wire.** The client and server share `BinaryWriter` / `BinaryReader` and `FastMap` / `FastSet`, so you move bytes, not JSON.
55
+ - **HTTP/3 and WebTransport** over QUIC, low-latency streaming without the TCP head-of-line tax.
56
+ - **Built for 50 Gbit/s on commodity hardware** and millions of requests per second, not toy demos.
57
+
58
+ ```ts
59
+ // server/index.ts
60
+ export function add(a: i32, b: i32): i32 {
61
+ return a + b;
62
+ }
63
+ ```
64
+
65
+ ## On by default
66
+
67
+ Every one of these works the moment you run `create`. No plugins to install, no config to write:
68
+
69
+ - **Build-time SEO for an SPA**: prerendered `<head>`, `robots.txt`, `sitemap.xml`, `llms.txt`
70
+ - **AI-crawler rules**: per-bot `robots.txt` plus `llms.txt`, one switch
71
+ - **Image optimization on**: imports and plain `<img>` become resized webp
72
+ - **Fonts preloaded** at build
73
+ - **Typed routes**: `href` and `params` checked against your real files
74
+ - **Loaders and mutations** with caching and revalidation, no data library, no `useEffect` fetching
75
+ - **Parallel and intercepting routes** for modals and dashboards
76
+ - **Instant navigation** and **animated view transitions**
77
+ - **WebAssembly backend** on uWebSockets, with **binary IO** on both sides
78
+
79
+ Anywhere else, that is a dozen packages and their config. Most teams wire three and ship the rest half-done.
80
+
81
+ ## AI-ready
82
+
83
+ ToilJS treats AI crawlers as first-class. Turn on SEO and the build emits an `llms.txt` describing your site for LLMs, and a `robots.txt` with explicit rules for the AI bots, allow or block GPTBot, OAI-SearchBot, ChatGPT-User, ClaudeBot, anthropic-ai, Google-Extended, PerplexityBot, CCBot, Applebot-Extended, Bytespider, Amazonbot, and Meta-ExternalAgent, with one switch.
84
+
85
+ ```ts
86
+ seo: { llms: { instructions: 'Docs live at /docs.' }, robots: { ai: 'disallow' } }
87
+ ```
88
+
89
+ That one line produces a real `robots.txt`:
90
+
91
+ ```
92
+ User-agent: *
93
+ Allow: /
94
+
95
+ User-agent: GPTBot
96
+ Disallow: /
97
+
98
+ User-agent: ClaudeBot
99
+ Disallow: /
100
+
101
+ User-agent: Google-Extended
102
+ Disallow: /
103
+
104
+ Sitemap: https://example.com/sitemap.xml
105
+ ```
106
+
107
+ The `toiljs create` wizard can also scaffold assistant files (CLAUDE.md, AGENTS.md, Cursor and Copilot configs) so your repo is ready for coding agents on day one.
108
+
109
+ ## Everything, at a glance
110
+
111
+ | | |
112
+ | --- | --- |
113
+ | **Routing** | File-based. Dynamic, catch-all, optional catch-all, route groups, nested layouts, templates, parallel slots, and intercepting routes. Every `href` and `params` is typed. |
114
+ | **Data** | A `loader` resolves before render. `useAction` / `<Form>` write then revalidate. Per-route caching. No fetch waterfalls. |
115
+ | **SEO** | Per-route metadata baked into static HTML, plus `robots.txt`, `sitemap.xml`, `llms.txt`, OpenGraph, Twitter, JSON-LD, canonical, theme-color, early hints. |
116
+ | **Assets** | Imported images compressed to webp and resized. Fonts preloaded. React split for caching. Build logs what it saved. |
117
+ | **Realtime** | Built-in WebSocket channels: `connectChannel` / `useChannel`. WebTransport over HTTP/3. |
118
+ | **DX** | HMR, instant navigation, view transitions, typed routes, and a dev error overlay. |
119
+ | **Server** | ToilScript compiled to WebAssembly, self-hosted on uWebSockets with HTTP/3. Binary IO built in. |
120
+ | **Tooling** | Strict TypeScript, ESLint, and Prettier, configured and enforced out of the box. Tailwind v4 optional. |
121
+
122
+ ## Routing
123
+
124
+ The filesystem is the router.
125
+
126
+ | File or folder | Route |
127
+ | --- | --- |
128
+ | `index.tsx` | `/` |
129
+ | `about.tsx` | `/about` |
130
+ | `blog/[id].tsx` | `/blog/:id` |
131
+ | `docs/[...slug].tsx` | catch-all |
132
+ | `docs/[[...slug]].tsx` | optional catch-all |
133
+ | `(marketing)/about.tsx` | route group, adds no URL segment |
134
+ | `layout.tsx` | wraps the segment, persists across navigation |
135
+ | `template.tsx` | a layout that re-mounts on every navigation |
136
+ | `loading.tsx` | Suspense fallback while the route and its data load |
137
+ | `error.tsx` | error boundary for the segment |
138
+ | `global-error.tsx` | catches errors in the root layout itself |
139
+ | `404.tsx` | not-found page |
140
+ | `@modal/...` | parallel slot, placed with `<Toil.Slot name="modal" />` |
141
+ | `@modal/(.)photo/[id]` | intercepting route: modal on soft nav, full page on reload |
142
+
143
+ Navigation comes with it:
144
+
145
+ - **`<Toil.Link>`** and **`<Toil.NavLink>`** (active class + `aria-current`), with `href` checked against your real routes.
146
+ - **`navigate` / `back` / `forward` / `refresh`**, plus **`useRouter`**, **`useNavigate`**, **`useLocation`**, **`usePathname`**, **`useParams`**, **`useSearchParams`**, **`useNavigationPending`**.
147
+ - **Hover and viewport prefetching**, so chunks are warm before you click.
148
+ - **Scroll restoration** on back/forward, scroll-to-`#hash`, and scroll-to-top on new routes.
149
+ - **Instant navigation**: visited pages render synchronously, no flash.
150
+ - **View transitions** (`client.viewTransitions: true`) for animated page changes, respecting `prefers-reduced-motion`.
151
+
152
+ ## Data
153
+
154
+ Read with a `loader`, write with an action. Both keep the UI in sync without manual refetching.
155
+
156
+ ```tsx
157
+ export const loader = async ({ params }: Toil.LoaderArgs) => fetchPost(params.id);
158
+ export const revalidate: Toil.Revalidate = 10; // reuse for 10s, false = forever, omit = every nav
159
+
160
+ function SaveButton({ title }: { title: string }) {
161
+ const save = Toil.useAction((t: string) => api.save(t), { revalidate: true });
162
+ return (
163
+ <button disabled={save.pending} onClick={() => void save.run(title)}>
164
+ {save.pending ? 'Saving' : 'Save'}
165
+ </button>
166
+ );
167
+ }
168
+ ```
169
+
170
+ - **`loader`** resolves in parallel with the route chunk; the page suspends until ready (its `loading.tsx` shows).
171
+ - **`useLoaderData(loader)`** is typed straight from the loader, no generics.
172
+ - **`revalidate`** sets the cache policy per route; **`router.revalidate()`** / **`revalidate(href)`** bust it after a mutation.
173
+ - **`useAction`** and **`<Toil.Form>`** track pending and error state and revalidate on success.
174
+
175
+ ## Components
176
+
177
+ Zero-import, on the `Toil` global:
178
+
179
+ - **`Image`** drops in for `<img>`: reserves space (no layout shift), lazy-loads, async-decodes, `priority` for the LCP image, `fill` + `objectFit`, optional blur placeholder.
180
+ - **`Script`** loads external or inline scripts with a `strategy` (`afterInteractive` / `lazyOnload` / `beforeInteractive`), deduplicated so a script never runs twice.
181
+ - **`Form`** submits to an action without a reload, revalidates on success, exposes pending state, optionally resets fields.
182
+ - **`Slot`** renders a parallel `@slot` route, the basis for modal overlays.
183
+ - **`Head`** / **`useHead`** / **`useTitle`** set the title and `<meta>` / `<link>` tags imperatively and compose across the tree.
184
+
185
+ ## Head and SEO
186
+
187
+ A single-page app serves an empty shell. ToilJS pre-renders each route's `<head>` at build, so Google, Facebook, Discord, Slack, and the AI crawlers see real per-page tags without running your JavaScript.
188
+
189
+ ```ts
190
+ // toil.config.ts
191
+ export default defineConfig({
192
+ client: {
193
+ seo: {
194
+ url: 'https://example.com',
195
+ title: 'My App',
196
+ openGraph: { siteName: 'My App', type: 'website', image: '/og.png' },
197
+ twitter: { card: 'summary_large_image' },
198
+ jsonLd: { '@context': 'https://schema.org', '@type': 'WebSite' },
199
+ themeColor: '#2563ff', // also the Discord / Slack embed accent
200
+ preconnect: ['https://cdn.example.com'],
201
+ robots: { ai: 'allow' },
202
+ },
203
+ },
204
+ });
205
+ ```
206
+
207
+ - **Per-route `metadata`** (or `generateMetadata` derived from the loader's data) wins per page over layout defaults.
208
+ - **Static prerender** writes a `<route>/index.html` for every static route with that route's head baked in.
209
+ - **`robots.txt`**, **`sitemap.xml`**, and **`llms.txt`** generated together.
210
+ - Full **OpenGraph** (image alt/width/height/type, locale), **Twitter card**, **`fb:app_id`**, **JSON-LD**, **canonical**, **theme-color**, and **`preconnect` / `dns-prefetch`** early hints.
211
+ - Output is **XSS-hardened**: attribute values and inline JSON-LD are escaped so injected data can't break out.
212
+
213
+ ## Build and assets
214
+
215
+ ToilJS owns Vite and does the boring optimization for you. The build tells you what it did:
216
+
217
+ ```
218
+ $ npm run build
219
+ ✓ optimized 3 images
220
+ client/hero.png
221
+ → images/hero.webp 148.0 kB → 19.3 kB -87%
222
+ ✓ preloaded 2 fonts
223
+ → fonts/inter-latin.woff2 24.10 kB
224
+ ```
225
+
226
+ - **Images** (`vite-imagetools` + `sharp`): every imported raster is compressed to webp, resize and reformat with `?w=400;800&format=webp&as=srcset`. The build logs the savings.
227
+ - **Fonts**: bundled `@font-face` fonts get a `<link rel="preload">` so text paints sooner, also logged.
228
+ - **Chunking**: React is split into its own long-lived chunk; assets land in tidy `images/`, `fonts/`, and `css/` folders.
229
+ - **Node polyfills** (`Buffer`, `global`, `process`) for libraries that expect them.
230
+ - **Styling**: plain CSS out of the box, with Sass, Less, Stylus, and Tailwind v4 a `toiljs configure` away.
231
+
232
+ ## Realtime
233
+
234
+ A typed WebSocket channel to the server, built in.
235
+
236
+ ```tsx
237
+ const messages = Toil.useChannel<Message>('/chat');
238
+ ```
239
+
240
+ `connectChannel` / `useChannel` / `resolveChannelUrl` handle connection, reconnection, and message decoding.
241
+
242
+ ## Binary IO
243
+
244
+ The same primitives on both sides of the wire, available as globals (and `toiljs/io`): `BinaryWriter`, `BinaryReader`, `FastMap`, `FastSet`. Move structured data without the JSON tax.
245
+
246
+ ## Tooling is the standard
247
+
248
+ ToilJS sets the toolchain so nobody argues about it. Strict TypeScript, ESLint, and Prettier come configured and enforced from the first commit. New apps are wired automatically, nothing to set up, nothing to copy, nothing to bikeshed. This is the standard, not a suggestion.
249
+
250
+ ## CLI
251
+
252
+ ```
253
+ toiljs create [name] scaffold a new app (styling, AI files, package manager)
254
+ toiljs dev dev server with HMR
255
+ toiljs build production build
256
+ toiljs start self-host the build (hyper-express / uWebSockets)
257
+ toiljs configure toggle styling, image, font, and SEO features
258
+ ```
259
+
260
+ ## Tech
261
+
262
+ <div align="center">
263
+
264
+ <img src="https://img.shields.io/badge/React_19-20232a?style=for-the-badge&logo=react&logoColor=61dafb" alt="React 19" />
265
+ <img src="https://img.shields.io/badge/TypeScript-3178c6?style=for-the-badge&logo=typescript&logoColor=white" alt="TypeScript" />
266
+ <img src="https://img.shields.io/badge/Vite-646cff?style=for-the-badge&logo=vite&logoColor=white" alt="Vite" />
267
+ <img src="https://img.shields.io/badge/WebAssembly-654ff0?style=for-the-badge&logo=webassembly&logoColor=white" alt="WebAssembly" />
268
+ <img src="https://img.shields.io/badge/sharp-99cc00?style=for-the-badge&logo=sharp&logoColor=white" alt="sharp" />
269
+ <img src="https://img.shields.io/badge/ESLint-4b32c3?style=for-the-badge&logo=eslint&logoColor=white" alt="ESLint" />
270
+ <img src="https://img.shields.io/badge/Prettier-f7b93e?style=for-the-badge&logo=prettier&logoColor=black" alt="Prettier" />
271
+ <img src="https://img.shields.io/badge/Tailwind_v4-06b6d4?style=for-the-badge&logo=tailwindcss&logoColor=white" alt="Tailwind v4" />
272
+
273
+ </div>
274
+
275
+ React 19, TypeScript, Vite, ToilScript (compiles to WebAssembly, on Binaryen), hyper-express + uWebSockets.js, vite-imagetools + sharp, ESLint (typescript-eslint, react-hooks, react-refresh, @eslint-react), Prettier, Tailwind v4 (optional).
276
+
277
+ ## One file does a lot
278
+
279
+ ```tsx
280
+ // client/routes/posts/[id].tsx -> /posts/:id
281
+ interface Post {
282
+ title: string;
283
+ }
284
+
285
+ export const metadata: Toil.Metadata = { title: 'Post' }; // SEO, baked into the HTML at build
286
+
287
+ export const loader = async ({ params }: Toil.LoaderArgs): Promise<Post> => {
288
+ const res = await fetch(`/api/posts/${params.id}`); // runs before render, no useEffect
289
+ return res.json();
290
+ };
291
+
292
+ export default function PostPage() {
293
+ const post = Toil.useLoaderData(loader); // typed Post, no generics
294
+ return (
295
+ <article>
296
+ <h1>{post.title}</h1>
297
+ <Toil.Link href="/posts">All posts</Toil.Link> {/* href is type-checked */}
298
+ </article>
299
+ );
300
+ }
301
+ ```
302
+
303
+ No imports. `Toil` is a fully-typed global, tree-shaken at build. The page renders with its data already loaded.
304
+
305
+ ## Start
306
+
307
+ ```bash
308
+ npx toiljs create my-app
309
+ ```
310
+
311
+ Everything in this README is already on. You just build the app.
312
+
313
+ <div align="center"><br/><sub>Apache-2.0</sub></div>
@@ -0,0 +1,37 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 500 500">
3
+ <!-- Generator: Adobe Illustrator 30.4.0, SVG Export Plug-In . SVG Version: 2.1.4 Build 226) -->
4
+ <defs>
5
+ <style>
6
+ .st0 {
7
+ fill: #fff;
8
+ }
9
+
10
+ .st1 {
11
+ fill: url(#linear-gradient1);
12
+ }
13
+
14
+ .st2 {
15
+ fill: url(#linear-gradient);
16
+ }
17
+ </style>
18
+ <linearGradient id="linear-gradient" x1="43.27" y1="43.27" x2="467.12" y2="467.12" gradientUnits="userSpaceOnUse">
19
+ <stop offset="0" stop-color="#6990ff"/>
20
+ <stop offset=".03" stop-color="#6479f9"/>
21
+ <stop offset=".08" stop-color="#5d57f0"/>
22
+ <stop offset=".12" stop-color="#583de9"/>
23
+ <stop offset=".17" stop-color="#542ae3"/>
24
+ <stop offset=".23" stop-color="#521ee0"/>
25
+ <stop offset=".28" stop-color="#521be0"/>
26
+ <stop offset=".66" stop-color="#6900f4"/>
27
+ <stop offset="1" stop-color="#7f00f6"/>
28
+ </linearGradient>
29
+ <linearGradient id="linear-gradient1" x1="149.99" y1="355.49" x2="149.99" y2="0" gradientUnits="userSpaceOnUse">
30
+ <stop offset=".15" stop-color="#6990ff" stop-opacity=".6"/>
31
+ <stop offset=".55" stop-color="#531ae1"/>
32
+ </linearGradient>
33
+ </defs>
34
+ <rect class="st2" width="500" height="500" rx="130" ry="130"/>
35
+ <path class="st1" d="M299.98,0L0,355.49v-225.49C0,58.2,58.2,0,130,0h169.98Z"/>
36
+ <path class="st0" d="M106.17,111.11h285.24c9.9,0,16.7,9.96,13.09,19.18l-17.98,45.96c-2.11,5.39-7.31,8.94-13.09,8.94h-74.65c-7.76,0-14.06,6.29-14.06,14.06v214.94c0,7.76-6.29,14.06-14.06,14.06h-45.96c-7.76,0-14.06-6.29-14.06-14.06v-217.25c0-7.76-6.29-14.06-14.06-14.06h-73.66c-5.82,0-11.04-3.59-13.12-9.02l-16.76-43.64c-3.54-9.21,3.26-19.1,13.12-19.1Z"/>
37
+ </svg>