@zerotal/arch 1.7.0
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.
- package/CHANGELOG.md +31 -0
- package/LICENSE +21 -0
- package/README.md +67 -0
- package/api-surface.md +283 -0
- package/docs/about.md +676 -0
- package/docs/admin/actions.md +183 -0
- package/docs/admin/auth.md +89 -0
- package/docs/admin/dashboard.md +155 -0
- package/docs/admin/extending-ui.md +229 -0
- package/docs/admin/extending.md +205 -0
- package/docs/admin/forms.md +155 -0
- package/docs/admin/index.md +144 -0
- package/docs/admin/operations.md +224 -0
- package/docs/admin/references.md +174 -0
- package/docs/admin/resources.md +132 -0
- package/docs/admin/structure.md +158 -0
- package/docs/admin/tables.md +185 -0
- package/docs/admin/testing.md +138 -0
- package/docs/ai.md +373 -0
- package/docs/application.md +435 -0
- package/docs/arch.md +263 -0
- package/docs/assets.md +755 -0
- package/docs/audit.md +367 -0
- package/docs/authentication.md +1000 -0
- package/docs/authorization.md +529 -0
- package/docs/broadcasting/channels.md +170 -0
- package/docs/broadcasting/client.md +108 -0
- package/docs/broadcasting/events.md +190 -0
- package/docs/broadcasting/index.md +163 -0
- package/docs/broadcasting/references.md +84 -0
- package/docs/broadcasting/testing.md +132 -0
- package/docs/cache.md +387 -0
- package/docs/carbon.md +830 -0
- package/docs/changelog.md +465 -0
- package/docs/client/auth.md +113 -0
- package/docs/client/errors.md +139 -0
- package/docs/client/files.md +118 -0
- package/docs/client/index.md +128 -0
- package/docs/client/references.md +58 -0
- package/docs/client/requests.md +131 -0
- package/docs/client/resilience.md +141 -0
- package/docs/client/testing.md +146 -0
- package/docs/commands.md +450 -0
- package/docs/components.md +2427 -0
- package/docs/config-system.md +258 -0
- package/docs/container.md +596 -0
- package/docs/context.md +579 -0
- package/docs/contributing.md +164 -0
- package/docs/controllers.md +354 -0
- package/docs/conventions.md +466 -0
- package/docs/cookies.md +101 -0
- package/docs/csrf.md +206 -0
- package/docs/database.md +476 -0
- package/docs/deployment.md +434 -0
- package/docs/devtools.md +1013 -0
- package/docs/email-verification.md +320 -0
- package/docs/encryption.md +380 -0
- package/docs/errors.md +317 -0
- package/docs/events.md +420 -0
- package/docs/flow/components.md +846 -0
- package/docs/flow/decorators.md +458 -0
- package/docs/flow/events.md +420 -0
- package/docs/flow/forms.md +551 -0
- package/docs/flow/index.md +568 -0
- package/docs/flow/layouts.md +793 -0
- package/docs/flow/lifecycle.md +336 -0
- package/docs/flow/pagination.md +402 -0
- package/docs/flow/performance.md +262 -0
- package/docs/flow/references.md +317 -0
- package/docs/flow/routing.md +550 -0
- package/docs/flow/testing.md +447 -0
- package/docs/getting-started.md +286 -0
- package/docs/health.md +269 -0
- package/docs/helpers.md +423 -0
- package/docs/i18n.md +414 -0
- package/docs/index.md +65 -0
- package/docs/inertia/build.md +95 -0
- package/docs/inertia/devtools.md +197 -0
- package/docs/inertia/index.md +267 -0
- package/docs/inertia/middleware.md +81 -0
- package/docs/inertia/props.md +507 -0
- package/docs/inertia/references.md +144 -0
- package/docs/inertia/rendering.md +189 -0
- package/docs/inertia/ssr.md +102 -0
- package/docs/inspirations.md +257 -0
- package/docs/lifecycle.md +331 -0
- package/docs/lock.md +454 -0
- package/docs/logger.md +517 -0
- package/docs/media.md +582 -0
- package/docs/middleware.md +509 -0
- package/docs/migrations.md +529 -0
- package/docs/monitor.md +377 -0
- package/docs/notifications.md +848 -0
- package/docs/orm/casts.md +365 -0
- package/docs/orm/factories.md +231 -0
- package/docs/orm/index.md +663 -0
- package/docs/orm/lifecycle.md +421 -0
- package/docs/orm/queries.md +692 -0
- package/docs/orm/relationships.md +440 -0
- package/docs/orm/serialization.md +251 -0
- package/docs/package-development.md +536 -0
- package/docs/pagination.md +340 -0
- package/docs/password-reset.md +335 -0
- package/docs/providers.md +447 -0
- package/docs/query-builder.md +465 -0
- package/docs/queue.md +549 -0
- package/docs/rate-limiting.md +253 -0
- package/docs/responses.md +336 -0
- package/docs/roles-and-2fa.md +418 -0
- package/docs/routing.md +1008 -0
- package/docs/scaffolding.md +335 -0
- package/docs/scheduler.md +533 -0
- package/docs/seeding.md +229 -0
- package/docs/session.md +451 -0
- package/docs/social.md +524 -0
- package/docs/storage.md +622 -0
- package/docs/structure.md +230 -0
- package/docs/support-policy.md +114 -0
- package/docs/telemetry.md +505 -0
- package/docs/tenancy.md +608 -0
- package/docs/testing/browser.md +153 -0
- package/docs/testing/console.md +130 -0
- package/docs/testing/database.md +238 -0
- package/docs/testing/flow-browser.md +216 -0
- package/docs/testing/http.md +441 -0
- package/docs/testing/index.md +240 -0
- package/docs/testing/mocking.md +410 -0
- package/docs/upgrade.md +192 -0
- package/docs/validator.md +549 -0
- package/docs/view.md +453 -0
- package/package.json +60 -0
- package/src/bin/mcp.ts +73 -0
- package/src/config.ts +63 -0
- package/src/errors.ts +27 -0
- package/src/index.ts +56 -0
- package/src/install/ArchInstallCommand.ts +189 -0
- package/src/install/detect.ts +98 -0
- package/src/install/guidelines.ts +290 -0
- package/src/install/markers.ts +77 -0
- package/src/install/mcpConfig.ts +81 -0
- package/src/mcp/index.ts +27 -0
- package/src/mcp/jsonrpc.ts +133 -0
- package/src/mcp/server.ts +307 -0
- package/src/mcp/stdio.ts +111 -0
- package/src/mcp/types.ts +146 -0
- package/src/probe/ArchProbeCommand.ts +48 -0
- package/src/probe/sentinel.ts +13 -0
- package/src/probe/topics.ts +291 -0
- package/src/provider/ArchProvider.ts +101 -0
- package/src/tools/_probe.ts +164 -0
- package/src/tools/apiSurface.ts +241 -0
- package/src/tools/appInfo.ts +88 -0
- package/src/tools/baselines.ts +210 -0
- package/src/tools/context.ts +37 -0
- package/src/tools/doctor.ts +93 -0
- package/src/tools/index.ts +50 -0
- package/src/tools/logs.ts +265 -0
- package/src/tools/routes.ts +133 -0
- package/src/tools/schema.ts +119 -0
- package/src/tools/searchDocs.ts +345 -0
package/docs/assets.md
ADDED
|
@@ -0,0 +1,755 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Assets
|
|
3
|
+
description: Compile and serve your CSS and JavaScript with Bun's native bundler and hot reload — no separate build tool to configure.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Assets
|
|
7
|
+
|
|
8
|
+
Zerotal compiles and serves your frontend assets — CSS and JavaScript — with Bun's
|
|
9
|
+
native bundler, so there's no separate build tool to configure. Each stack (Flow,
|
|
10
|
+
View, Inertia) has its own pipeline and hot-reload behaviour; all three use
|
|
11
|
+
[Tailwind CSS v4](https://tailwindcss.com/docs/v4-beta).
|
|
12
|
+
|
|
13
|
+
Asset compilation is built into the framework — there's no package to install or
|
|
14
|
+
provider to register for the pipeline itself. You only add Tailwind and wire up a
|
|
15
|
+
static route, then `bun zt serve --dev` handles build-and-reload for you.
|
|
16
|
+
|
|
17
|
+
> **Note** — Projects ship a `zt.ts` entry and a `zt` package script,
|
|
18
|
+
> so commands are written `bun zt <command>` (e.g. `bun zt serve --dev`).
|
|
19
|
+
|
|
20
|
+
## Getting Started
|
|
21
|
+
|
|
22
|
+
Asset building is part of `@zerotal/core` — nothing to install and no provider
|
|
23
|
+
to register. `bun zt serve --dev` builds on change, and `bun zt css:build`
|
|
24
|
+
produces a production bundle:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# in your project root
|
|
28
|
+
bun zt serve --dev # watch and rebuild
|
|
29
|
+
bun zt css:build # one production build
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Tailwind is the only optional piece; [add it](#tailwind-css) when you want it.
|
|
33
|
+
|
|
34
|
+
### Which build command should I use?
|
|
35
|
+
|
|
36
|
+
Pick by stack — the dev server runs the right pipeline automatically:
|
|
37
|
+
|
|
38
|
+
- **Flow or View** (server-rendered, one stylesheet): `bun zt css:build` for
|
|
39
|
+
production. Dev rebuilds are wired up by `FlowProvider`.
|
|
40
|
+
- **Inertia** (React or Vue SPA, JS + CSS bundle): `bun zt inertia:build` for
|
|
41
|
+
production. Dev rebuilds are wired up by `InertiaProvider`.
|
|
42
|
+
- **Any stack, development**: `bun zt serve --dev` — initial build, file watch,
|
|
43
|
+
rebuild on save, and a browser reload over WebSocket.
|
|
44
|
+
|
|
45
|
+
## Tailwind CSS
|
|
46
|
+
|
|
47
|
+
Install Tailwind v4 and Bun's native Tailwind plugin:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# in your project root
|
|
51
|
+
bun add -d tailwindcss bun-plugin-tailwind
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Create `resources/css/app.css`:
|
|
55
|
+
|
|
56
|
+
```css
|
|
57
|
+
/* resources/css/app.css */
|
|
58
|
+
@import "tailwindcss";
|
|
59
|
+
|
|
60
|
+
/* Extend Tailwind's theme with @theme */
|
|
61
|
+
@theme {
|
|
62
|
+
--color-brand: oklch(0.6 0.2 260);
|
|
63
|
+
--font-sans: "Inter", sans-serif;
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
That's it — no `tailwind.config.js`, no PostCSS config, no purge list. Tailwind
|
|
68
|
+
v4 detects your templates automatically via content scanning.
|
|
69
|
+
|
|
70
|
+
> **Tip** — Why `bun-plugin-tailwind` and not PostCSS?
|
|
71
|
+
> Bun has native CSS support built in. `bun-plugin-tailwind` is a first-party
|
|
72
|
+
> Bun plugin that processes Tailwind v4 without PostCSS as an intermediary —
|
|
73
|
+
> it's faster and requires no extra peer dependencies. PostCSS is not needed.
|
|
74
|
+
|
|
75
|
+
### How the build finds Tailwind
|
|
76
|
+
|
|
77
|
+
Zerotal looks for `bun-plugin-tailwind` in your project's `node_modules` at build
|
|
78
|
+
time. When it's installed, the plugin runs inside Bun's bundler and processes
|
|
79
|
+
every `@import "tailwindcss"` directive natively — no PostCSS, no extra peer
|
|
80
|
+
dependencies.
|
|
81
|
+
|
|
82
|
+
When it isn't, the build falls back to spawning `bunx @tailwindcss/cli` as a
|
|
83
|
+
subprocess, which needs only `tailwindcss`. Your stylesheet is built either way;
|
|
84
|
+
installing the plugin keeps the work in-process and faster.
|
|
85
|
+
|
|
86
|
+
## CSS builds for Flow and View
|
|
87
|
+
|
|
88
|
+
Flow and View apps ship a single compiled stylesheet — the entry lives in
|
|
89
|
+
`resources/`, the built output in `public/`:
|
|
90
|
+
|
|
91
|
+
```text
|
|
92
|
+
# project layout (Flow / View)
|
|
93
|
+
resources/
|
|
94
|
+
css/
|
|
95
|
+
app.css ← Tailwind entry point
|
|
96
|
+
public/
|
|
97
|
+
css/
|
|
98
|
+
app.css ← Built output (git-ignored)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Linking the built CSS
|
|
102
|
+
|
|
103
|
+
Reference the built stylesheet with a plain `<link>` pointing at `/css/app.css`. In a
|
|
104
|
+
Flow layout it goes in the `static head`; in a View controller it's a tag in the
|
|
105
|
+
returned HTML:
|
|
106
|
+
|
|
107
|
+
```html
|
|
108
|
+
<!-- in a Flow layout head, or a View controller's HTML -->
|
|
109
|
+
<link rel="stylesheet" href="/css/app.css" />
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Dev mode
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# in your project root
|
|
116
|
+
bun zt serve --dev
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
When `resources/css/app.css` exists, `FlowProvider.onBooted()` registers a
|
|
120
|
+
combined CSS/JS build hook. The `DevOrchestrator` then:
|
|
121
|
+
|
|
122
|
+
1. Runs an initial CSS build before starting the server
|
|
123
|
+
2. Watches `resources/css/` for changes
|
|
124
|
+
3. Rebuilds CSS on every save (using `bun-plugin-tailwind` if installed,
|
|
125
|
+
falling back to `bunx @tailwindcss/cli`)
|
|
126
|
+
4. Signals the browser to reload over WebSocket (`/__dev/ws`)
|
|
127
|
+
|
|
128
|
+
The browser receives the reload signal and does a full page refresh — the
|
|
129
|
+
updated CSS is served from `public/css/app.css`.
|
|
130
|
+
|
|
131
|
+
> **Note** — The same `onBooted` hook also bundles `resources/js/app.js` →
|
|
132
|
+
> `public/js/app.js` when that entry exists, so Flow apps with a small client
|
|
133
|
+
> bundle get the same watch-and-rebuild treatment as their CSS.
|
|
134
|
+
|
|
135
|
+
### Production build
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
# in your project root
|
|
139
|
+
bun zt css:build
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Override the defaults with flags:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
# in your project root
|
|
146
|
+
bun zt css:build --input resources/css/app.css --output public/css --minify
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
| Flag | Short | Default | Description |
|
|
150
|
+
| ---------- | ----- | ----------------------- | ---------------------------- |
|
|
151
|
+
| `--input` | `-i` | `resources/css/app.css` | Path to the CSS entry point. |
|
|
152
|
+
| `--output` | `-o` | `public/css` | Output directory. |
|
|
153
|
+
| `--minify` | `-m` | `true` | Minify the output. |
|
|
154
|
+
|
|
155
|
+
All flags default to sensible values; the short form with no flags is
|
|
156
|
+
sufficient for most apps.
|
|
157
|
+
|
|
158
|
+
### Static file serving
|
|
159
|
+
|
|
160
|
+
Add `Router.static` to serve `public/` at the root:
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
// routes/index.ts
|
|
164
|
+
Router.static("/", "public");
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Or target `public/css/` specifically:
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
// routes/index.ts
|
|
171
|
+
Router.static("/css", "public/css");
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Every file present when the server starts is registered as its own route and
|
|
175
|
+
served without entering JavaScript, which is why a static asset costs
|
|
176
|
+
essentially nothing per request. The trade-off is that the list is fixed at
|
|
177
|
+
startup, so a file written afterwards has no route. That is fine for a deployed
|
|
178
|
+
app, where the build finishes before the server boots. Where it isn't — a
|
|
179
|
+
directory whose contents change while the server runs — pass `eager: false` to
|
|
180
|
+
fall back to a per-request disk lookup. Dev mode applies that fallback to every
|
|
181
|
+
static directory automatically, which is how a freshly-built chunk is served the
|
|
182
|
+
moment it lands.
|
|
183
|
+
|
|
184
|
+
### The asset helper
|
|
185
|
+
|
|
186
|
+
Hard-coding `/css/app.css` into a template works right up until you move the
|
|
187
|
+
build output or serve it from a CDN prefix. `asset()` builds the URL for you
|
|
188
|
+
from the configured `app.assets.prefix`, so the template stops caring where the
|
|
189
|
+
file actually lives:
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
import { asset } from "zerotal/assets";
|
|
193
|
+
|
|
194
|
+
asset("app.css"); // → "/css/app.css"
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Reach for it in layouts and templates you write by hand — the Flow scaffold
|
|
198
|
+
uses it for exactly this. You do not need it for Inertia's `resources/app.html`,
|
|
199
|
+
which already gets its URLs rewritten for you.
|
|
200
|
+
|
|
201
|
+
In dev it also appends `?v=<build token>`, so a rebuilt file is refetched rather
|
|
202
|
+
than served from cache. In production it returns the clean path unchanged — a
|
|
203
|
+
deployed build is immutable, so cache invalidation belongs to your deploy (a
|
|
204
|
+
CDN purge, or the Inertia asset version described below) rather than to a query
|
|
205
|
+
string that would change on every boot.
|
|
206
|
+
|
|
207
|
+
## JavaScript builds for Inertia
|
|
208
|
+
|
|
209
|
+
The React and Vue stacks share one build pipeline — same bundler, dev reload, and
|
|
210
|
+
output. The only differences are the page file extension (`.tsx` vs `.vue`) and the
|
|
211
|
+
JS entry point.
|
|
212
|
+
|
|
213
|
+
### Inertia directory layout
|
|
214
|
+
|
|
215
|
+
```text
|
|
216
|
+
# project layout (Inertia)
|
|
217
|
+
resources/
|
|
218
|
+
app.html ← HTML shell template
|
|
219
|
+
css/
|
|
220
|
+
app.css ← Tailwind entry (imported in app.tsx)
|
|
221
|
+
js/
|
|
222
|
+
app.tsx ← JS entry point
|
|
223
|
+
env.d.ts ← ambient module decls (*.css, *.vue, images)
|
|
224
|
+
pages.generated.ts ← auto-generated page registry (do not edit)
|
|
225
|
+
Layouts/
|
|
226
|
+
AppLayout.tsx ← (.vue for Vue)
|
|
227
|
+
pages/ ← page components live here by default
|
|
228
|
+
home.tsx ← (.vue for Vue)
|
|
229
|
+
Posts/
|
|
230
|
+
Index.tsx
|
|
231
|
+
Show.tsx
|
|
232
|
+
public/
|
|
233
|
+
assets/
|
|
234
|
+
app.js ← Built bundle (git-ignored)
|
|
235
|
+
app.css ← Extracted CSS (git-ignored)
|
|
236
|
+
chunk-*.js ← Per-page chunks (code splitting, git-ignored)
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### JS entry point
|
|
240
|
+
|
|
241
|
+
`resources/js/app.tsx` is the bundler's entry point. It imports your CSS (so Tailwind
|
|
242
|
+
runs as part of the JS build) and boots the Inertia client, resolving each page from
|
|
243
|
+
the generated registry:
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
// resources/js/app.tsx (React)
|
|
247
|
+
import { createInertiaApp, type ResolvedComponent } from "@inertiajs/react";
|
|
248
|
+
import { createRoot } from "react-dom/client";
|
|
249
|
+
import { pages } from "./pages.generated.ts";
|
|
250
|
+
import "../css/app.css"; // ← imports CSS into the JS bundle
|
|
251
|
+
|
|
252
|
+
createInertiaApp({
|
|
253
|
+
resolve: async (name): Promise<ResolvedComponent> => {
|
|
254
|
+
const page = pages[name];
|
|
255
|
+
if (!page) throw new Error(`Inertia page not found: "${name}"`);
|
|
256
|
+
return (await page()).default as ResolvedComponent;
|
|
257
|
+
},
|
|
258
|
+
setup({ el, App, props }) {
|
|
259
|
+
createRoot(el).render(<App {...props} />);
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
// resources/js/app.tsx (Vue)
|
|
266
|
+
import { createInertiaApp } from "@inertiajs/vue3";
|
|
267
|
+
import { createApp, h, type DefineComponent } from "vue";
|
|
268
|
+
import { pages } from "./pages.generated.ts";
|
|
269
|
+
import "../css/app.css";
|
|
270
|
+
|
|
271
|
+
createInertiaApp({
|
|
272
|
+
resolve: async (name): Promise<DefineComponent> => {
|
|
273
|
+
const page = pages[name];
|
|
274
|
+
if (!page) throw new Error(`Inertia page not found: "${name}"`);
|
|
275
|
+
return (await page()).default as DefineComponent;
|
|
276
|
+
},
|
|
277
|
+
setup({ el, App, props, plugin }) {
|
|
278
|
+
createApp({ render: () => h(App, props) })
|
|
279
|
+
.use(plugin)
|
|
280
|
+
.mount(el as Element);
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Tailwind is processed because `app.tsx` imports `app.css`, and `Bun.build()`
|
|
286
|
+
passes all CSS through `bun-plugin-tailwind` before emitting
|
|
287
|
+
`public/assets/app.css`. For Vue, the build also runs `@vue/compiler-sfc` via the
|
|
288
|
+
Inertia Vue plugin so `.vue` single-file components compile.
|
|
289
|
+
|
|
290
|
+
> **Warning** — `tsconfig.json` must set the right JSX runtime. React pages need
|
|
291
|
+
> `"jsxImportSource": "react"` and Vue pages need `"jsxImportSource": "vue"`.
|
|
292
|
+
> Using a server-JSX runtime instead (`"zerotal"` for views, `"@zerotal/flow"` for Flow) produces a runtime
|
|
293
|
+
> "Invalid hook call" / null-dispatcher error.
|
|
294
|
+
|
|
295
|
+
### Inertia dev mode
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
# in your project root
|
|
299
|
+
bun zt serve --dev
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
`InertiaProvider.onBooted()` registers Inertia's build routine under its own
|
|
303
|
+
name. Each view layer registers its own, and every one of them runs on a change
|
|
304
|
+
— an Inertia app that also installs `@zerotal/monitor` gets Flow alongside
|
|
305
|
+
it, and both bundles keep rebuilding. The orchestrator:
|
|
306
|
+
|
|
307
|
+
1. Runs `generatePageRegistry()` + `Bun.build()` on startup
|
|
308
|
+
2. Watches `resources/js/` (which includes `resources/js/pages/`) and
|
|
309
|
+
`resources/css/`
|
|
310
|
+
3. Regenerates the page registry and rebuilds the bundle on changes
|
|
311
|
+
4. Signals the browser over WebSocket at `/__dev/ws`
|
|
312
|
+
|
|
313
|
+
Code splitting is enabled (`splitting: true`), so each page becomes its own
|
|
314
|
+
lazy-loaded chunk. Only the entry bundle (with the shared framework runtime) is
|
|
315
|
+
loaded on first navigation; each subsequent page chunk is fetched on demand.
|
|
316
|
+
|
|
317
|
+
A rebuild that changes the shape of the module graph — adding a page, adding an
|
|
318
|
+
import — emits chunk filenames the previous build never had. Those are served
|
|
319
|
+
as soon as they are written: in dev the server falls back to a disk lookup for
|
|
320
|
+
any path its startup scan didn't already know about, so a fresh chunk never
|
|
321
|
+
404s while waiting for a restart.
|
|
322
|
+
|
|
323
|
+
#### Asset cache-busting in dev
|
|
324
|
+
|
|
325
|
+
To stop the browser serving a stale bundle after a rebuild, dev mode busts the
|
|
326
|
+
cache two ways (no-op in production):
|
|
327
|
+
|
|
328
|
+
- **Versioned URLs.** The served HTML rewrites local `/assets/*.js` and `*.css`
|
|
329
|
+
URLs to `?v=<file mtime>`. After a rebuild the mtime changes → the URL changes
|
|
330
|
+
→ the browser fetches fresh; when nothing changed the URL is stable, so the
|
|
331
|
+
browser keeps using its cached copy. This works even on frontend-only rebuilds,
|
|
332
|
+
where the server is never restarted.
|
|
333
|
+
- **A per-build token on `asset()`.** URLs built by the
|
|
334
|
+
[`asset()` helper](#the-asset-helper) instead carry `?v=<build token>` — one
|
|
335
|
+
token for the whole build, reissued on every rebuild and handed to the server
|
|
336
|
+
over the same channel that triggers the browser reload. Templates that call
|
|
337
|
+
`asset()` therefore bust every URL at once, rather than file by file.
|
|
338
|
+
- **`Cache-Control: no-cache`** is set on static assets served from `public/`, so
|
|
339
|
+
the browser always revalidates.
|
|
340
|
+
|
|
341
|
+
In production, asset URLs are served as-is. Use `ASSET_VERSION` (surfaced as
|
|
342
|
+
`inertia.version`) to set the Inertia asset version — on a mismatch the client
|
|
343
|
+
triggers a full reload (HTTP 409), which is how you invalidate clients after a
|
|
344
|
+
deploy.
|
|
345
|
+
|
|
346
|
+
### Inertia production build
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
# in your project root
|
|
350
|
+
bun zt inertia:build
|
|
351
|
+
bun zt inertia:build --production # minified, no source maps
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
The build:
|
|
355
|
+
|
|
356
|
+
1. Regenerates `pages.generated.ts`
|
|
357
|
+
2. Runs `Bun.build()` with `splitting: true` (plus minify / no source maps for
|
|
358
|
+
`--production`)
|
|
359
|
+
3. Outputs all assets to `public/assets/`
|
|
360
|
+
4. Deletes what the previous build left behind
|
|
361
|
+
|
|
362
|
+
Run it before `bun zt serve` in production (it is wired to the `build` script in
|
|
363
|
+
the scaffolded `package.json`).
|
|
364
|
+
|
|
365
|
+
#### Why the output directory needs sweeping
|
|
366
|
+
|
|
367
|
+
Code-splitting names each shared chunk after its content — `chunk-3f9a2c.js` —
|
|
368
|
+
so a rebuild that changes one page emits a new set of chunk names and abandons
|
|
369
|
+
the old ones. Nothing overwrites them. Left alone, a long dev session buries the
|
|
370
|
+
output directory in hundreds of dead chunks, all of them registered as static
|
|
371
|
+
routes at startup and all of them shipped in the next deploy.
|
|
372
|
+
|
|
373
|
+
Every build that splits therefore sweeps up after itself, in dev and in
|
|
374
|
+
production alike. It removes two things and nothing else: files the previous
|
|
375
|
+
build recorded as its own output, and files named the way the bundler names
|
|
376
|
+
chunks. Anything else in the directory is left untouched, which matters because
|
|
377
|
+
an output directory is often `public/`, where your images and favicon live
|
|
378
|
+
alongside the bundle. The record of what each build wrote is kept in
|
|
379
|
+
`.zerotal/build/`, outside the served directory.
|
|
380
|
+
|
|
381
|
+
A directory that has been collecting chunks since before this existed is cleaned
|
|
382
|
+
by the next build, since chunks are recognised by name and not only by the
|
|
383
|
+
record.
|
|
384
|
+
|
|
385
|
+
## Controlling how files are loaded
|
|
386
|
+
|
|
387
|
+
`url()` references in your CSS are resolved at build time, and Bun inlines small
|
|
388
|
+
files as `data:` URIs. That is the right default for an icon and the wrong one for a
|
|
389
|
+
font: the bytes move **into the stylesheet**, which blocks first paint. Nine woff2
|
|
390
|
+
subsets can turn a 36 KB stylesheet into 260 KB that must download before anything
|
|
391
|
+
renders — the opposite of what `font-display: swap` is for, and expensive on exactly
|
|
392
|
+
the connections that need it most.
|
|
393
|
+
|
|
394
|
+
Set a per-extension loader to emit them as separate files instead:
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
// config/app.ts
|
|
398
|
+
export default AppConfig({
|
|
399
|
+
assets: {
|
|
400
|
+
entrypoint: "resources/css/app.css",
|
|
401
|
+
loader: { ".woff2": "file", ".woff": "file" },
|
|
402
|
+
},
|
|
403
|
+
});
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
`file` copies the asset to `outDir` and rewrites the `url()` to point at it, so the
|
|
407
|
+
stylesheet stays small and the fonts load in parallel. The other accepted values are
|
|
408
|
+
`dataurl` (force inlining), `base64`, `text`, `json`, and `toml`.
|
|
409
|
+
|
|
410
|
+
## Hot reload
|
|
411
|
+
|
|
412
|
+
Both pipelines above are driven by the same dev process. `bun zt serve --dev`
|
|
413
|
+
builds once, spawns the server as a child process, and watches the filesystem;
|
|
414
|
+
what happens on a change depends on which directory the change landed in:
|
|
415
|
+
|
|
416
|
+
```text
|
|
417
|
+
# bun zt serve --dev process model
|
|
418
|
+
┌──────────────────────────────────────────────────────┐
|
|
419
|
+
│ bun zt serve --dev │
|
|
420
|
+
│ │
|
|
421
|
+
│ Process 1: DevOrchestrator │
|
|
422
|
+
│ ├─ Initial build (CSS or JS+CSS) │
|
|
423
|
+
│ ├─ Spawn Process 2 (server) │
|
|
424
|
+
│ └─ Watch filesystem │
|
|
425
|
+
│ │ │
|
|
426
|
+
│ ├─ Backend change (app/, routes/, bootstrap/) │
|
|
427
|
+
│ │ └─ Rebuild, then kill + respawn (150 ms) │
|
|
428
|
+
│ │ │
|
|
429
|
+
│ └─ Frontend change (resources/css/, js/) │
|
|
430
|
+
│ └─ Rebuild assets (80 ms debounce) │
|
|
431
|
+
│ └─ Write "reload:<token>" to │
|
|
432
|
+
│ Process 2 stdin │
|
|
433
|
+
│ │
|
|
434
|
+
│ Process 2: Zerotal server (--dev-worker) │
|
|
435
|
+
│ ├─ Serves app routes │
|
|
436
|
+
│ ├─ Sends "version:<token>" on every WS connect │
|
|
437
|
+
│ ├─ Broadcasts "reload" to WS clients │
|
|
438
|
+
│ └─ WebSocket at /__dev/ws │
|
|
439
|
+
│ │ │
|
|
440
|
+
│ ▼ │
|
|
441
|
+
│ Browser (JS client) │
|
|
442
|
+
│ connects on load │
|
|
443
|
+
│ reloads on "reload" message │
|
|
444
|
+
│ reloads when the token changed │
|
|
445
|
+
│ reconnects on close (1 s delay) │
|
|
446
|
+
└──────────────────────────────────────────────────────┘
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
### What triggers a restart, and what only rebuilds
|
|
450
|
+
|
|
451
|
+
| Changed path | Action |
|
|
452
|
+
| ------------------------------------------ | -------------------------------------- |
|
|
453
|
+
| `app/`, `routes/`, `bootstrap/`, `config/` | Server restart (150 ms) |
|
|
454
|
+
| `resources/css/`, `resources/js/` | Asset rebuild + browser reload (80 ms) |
|
|
455
|
+
| `public/` | Ignored (output dir) |
|
|
456
|
+
| `node_modules/`, `.git/` | Ignored |
|
|
457
|
+
|
|
458
|
+
Inertia pages live under `resources/js/pages/`, so they're covered by the
|
|
459
|
+
`resources/js/` watch and rebuild rather than restart.
|
|
460
|
+
|
|
461
|
+
### Browser client
|
|
462
|
+
|
|
463
|
+
Under `--dev-worker`, `DevReloadMiddleware` injects this script into every
|
|
464
|
+
`text/html` response, so live reload works for any view layer — Flow, View,
|
|
465
|
+
plain HTML — with no wiring on your part:
|
|
466
|
+
|
|
467
|
+
```html
|
|
468
|
+
<!-- injected before </body> in --dev-worker mode -->
|
|
469
|
+
<script>
|
|
470
|
+
(function () {
|
|
471
|
+
var proto = location.protocol === "https:" ? "wss:" : "ws:";
|
|
472
|
+
var build = null;
|
|
473
|
+
function connect() {
|
|
474
|
+
var ws = new WebSocket(proto + "//" + location.host + "/__dev/ws");
|
|
475
|
+
ws.onmessage = function (e) {
|
|
476
|
+
var d = e.data;
|
|
477
|
+
if (d === "reload") {
|
|
478
|
+
location.reload();
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
if (d.indexOf("version:") === 0) {
|
|
482
|
+
var v = d.slice(8);
|
|
483
|
+
if (!v) return;
|
|
484
|
+
if (build === null) build = v;
|
|
485
|
+
else if (build !== v) location.reload();
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
ws.onclose = function () {
|
|
489
|
+
setTimeout(connect, 1000);
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
connect();
|
|
493
|
+
})();
|
|
494
|
+
</script>
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
The two signals cover two different situations. A frontend change pushes
|
|
498
|
+
`reload` down a socket that is already open. A backend change cannot: it
|
|
499
|
+
restarts the server, and the restart closes every socket, so a reload pushed at
|
|
500
|
+
that moment reaches nobody — the tab does not reconnect for another second. The
|
|
501
|
+
build token closes that gap. The server states its token whenever a socket
|
|
502
|
+
opens; the tab remembers the first one it sees and reloads when a reconnect
|
|
503
|
+
reports a different one. That is how a rebuild triggered by a backend edit still
|
|
504
|
+
reaches the browser.
|
|
505
|
+
|
|
506
|
+
Inertia is the one exception to where the script comes from: it bakes the same
|
|
507
|
+
client into its cached HTML template at boot rather than receiving it from the
|
|
508
|
+
middleware, because injecting into the response body means buffering it, and
|
|
509
|
+
`Inertia.stream()` exists precisely to avoid that. The middleware skips any page
|
|
510
|
+
that already carries a `/__dev/ws` client, so the two never collide.
|
|
511
|
+
|
|
512
|
+
## Styling
|
|
513
|
+
|
|
514
|
+
Zerotal is unopinionated about CSS — the Tailwind pipeline above is the default, but
|
|
515
|
+
nothing forces it. For state-driven classes inside Flow components, see
|
|
516
|
+
[reactive classes and attributes](/docs/flow#reactive-classes-and-attributes); the
|
|
517
|
+
patterns below are the styling techniques you'll reach for most, whichever stack you
|
|
518
|
+
picked.
|
|
519
|
+
|
|
520
|
+
### CSS Modules
|
|
521
|
+
|
|
522
|
+
Bun's bundler understands CSS Modules out of the box. Import a `.module.css` file and
|
|
523
|
+
use the generated class names — styles are scoped to the component, so names never
|
|
524
|
+
collide across files:
|
|
525
|
+
|
|
526
|
+
```tsx
|
|
527
|
+
// app/components/Button.tsx
|
|
528
|
+
import styles from "./Button.module.css";
|
|
529
|
+
|
|
530
|
+
export function Button({ label }: { label: string }) {
|
|
531
|
+
return <button class={styles.button}>{label}</button>;
|
|
532
|
+
}
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
```css
|
|
536
|
+
/* app/components/Button.module.css */
|
|
537
|
+
.button {
|
|
538
|
+
padding: 0.5rem 1rem;
|
|
539
|
+
border-radius: 0.375rem;
|
|
540
|
+
background: var(--color-brand);
|
|
541
|
+
font-weight: 600;
|
|
542
|
+
}
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
### Component libraries
|
|
546
|
+
|
|
547
|
+
For ready-made, themeable UI, reach for **flow-ui** — Zerotal's first-party,
|
|
548
|
+
shadcn-style component set built on the same Tailwind tokens, so it follows your
|
|
549
|
+
theme automatically:
|
|
550
|
+
|
|
551
|
+
```bash
|
|
552
|
+
# in your project root
|
|
553
|
+
bun zt flow:add button card dialog
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
Browse the full catalogue in [Components](/docs/components). Any npm component library
|
|
557
|
+
also works with the Bun bundler — common choices:
|
|
558
|
+
|
|
559
|
+
| Library | Install |
|
|
560
|
+
| ----------- | ---------------------------------------- |
|
|
561
|
+
| shadcn/ui | `bunx shadcn@latest init` |
|
|
562
|
+
| Radix UI | `bun add @radix-ui/react-dialog` |
|
|
563
|
+
| Headless UI | `bun add @headlessui/react` |
|
|
564
|
+
| DaisyUI | `bun add -d daisyui` (a Tailwind plugin) |
|
|
565
|
+
|
|
566
|
+
### Web fonts
|
|
567
|
+
|
|
568
|
+
Load fonts from a CDN for a quick start, or self-host them in `public/` for one fewer
|
|
569
|
+
network round-trip (and to keep requests on your own domain). Pair a self-hosted face
|
|
570
|
+
with `--font-sans` in your `@theme` block so Tailwind's `font-sans` utility picks it up:
|
|
571
|
+
|
|
572
|
+
```html
|
|
573
|
+
<!-- in your HTML head — Google Fonts quick start -->
|
|
574
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
575
|
+
<link
|
|
576
|
+
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
|
|
577
|
+
rel="stylesheet"
|
|
578
|
+
/>
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
```css
|
|
582
|
+
/* resources/css/app.css — self-hosted alternative */
|
|
583
|
+
@font-face {
|
|
584
|
+
font-family: "Inter";
|
|
585
|
+
src: url("/assets/fonts/Inter.woff2") format("woff2");
|
|
586
|
+
font-weight: 100 900;
|
|
587
|
+
font-display: swap;
|
|
588
|
+
}
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
## Testing
|
|
592
|
+
|
|
593
|
+
Set your suite up once as described in [Testing](/docs/testing). Assets are built
|
|
594
|
+
artifacts, so the useful tests are about what a request receives — not about the
|
|
595
|
+
bundler.
|
|
596
|
+
|
|
597
|
+
That boundary is worth holding. Tailwind emitting the right rules and Bun bundling
|
|
598
|
+
the right modules are already covered by those tools' own suites, and a test that
|
|
599
|
+
asserts on the _content_ of built CSS breaks every time a class is added. What is
|
|
600
|
+
genuinely yours to verify is the wiring: that the page points at the asset, that
|
|
601
|
+
the asset is served, and that a deploy invalidates the old one.
|
|
602
|
+
|
|
603
|
+
**Assert the page references the asset**, which catches a build config that
|
|
604
|
+
silently stopped emitting it:
|
|
605
|
+
|
|
606
|
+
```typescript
|
|
607
|
+
// tests/http/assets.test.ts
|
|
608
|
+
import { test } from "bun:test";
|
|
609
|
+
import { createApp } from "../helpers.ts";
|
|
610
|
+
|
|
611
|
+
test("the layout links the built stylesheet", async () => {
|
|
612
|
+
const app = await createApp();
|
|
613
|
+
|
|
614
|
+
const res = await app.get("/");
|
|
615
|
+
|
|
616
|
+
res.assertSee("/assets/app.css");
|
|
617
|
+
await app.close();
|
|
618
|
+
});
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
**Assert it is actually served**, because a reference to a missing file is a
|
|
622
|
+
`404` the page will not tell you about:
|
|
623
|
+
|
|
624
|
+
```typescript
|
|
625
|
+
// tests/http/assets.test.ts
|
|
626
|
+
const css = await app.get("/assets/app.css");
|
|
627
|
+
|
|
628
|
+
css.assertOk();
|
|
629
|
+
css.assertHeader("Content-Type", "text/css");
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
These two belong together. Each passes on its own while the pair is broken — a
|
|
633
|
+
page can link a path nothing serves, and a served file can go unreferenced — so
|
|
634
|
+
testing only one leaves the failure that actually reaches users uncovered.
|
|
635
|
+
|
|
636
|
+
**Cache-busting is worth one test** if you rely on it for deploys. In dev the URL
|
|
637
|
+
carries a `?v=` token that changes on rebuild; in production `ASSET_VERSION`
|
|
638
|
+
drives the Inertia version header:
|
|
639
|
+
|
|
640
|
+
```typescript
|
|
641
|
+
// tests/http/assets.test.ts
|
|
642
|
+
test("asset URLs carry a version in dev", async () => {
|
|
643
|
+
const res = await app.get("/");
|
|
644
|
+
|
|
645
|
+
expect(res.text()).toMatch(/\/assets\/app\.css\?v=/);
|
|
646
|
+
});
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
> **Warning** — These tests need a build to have run. A suite in CI that never
|
|
650
|
+
> runs `bun zt css:build` will fail them for a reason unrelated to the code —
|
|
651
|
+
> either build in the pipeline before testing, or scope these to a suite you run
|
|
652
|
+
> after building.
|
|
653
|
+
|
|
654
|
+
The cleanest arrangement is to build once as a CI step before the test job, the way
|
|
655
|
+
you would before starting the server. Failing that, keep asset tests in their own
|
|
656
|
+
file and run it as a separate command after the build, so a missing artifact fails
|
|
657
|
+
one obvious job rather than scattering unrelated failures through the whole suite.
|
|
658
|
+
|
|
659
|
+
### Unit-testing the asset helper
|
|
660
|
+
|
|
661
|
+
`asset()` is a pure function of the configured prefix, so its behaviour can be
|
|
662
|
+
pinned down without a request at all. This is the cheapest place to catch a
|
|
663
|
+
misconfigured CDN prefix:
|
|
664
|
+
|
|
665
|
+
```typescript
|
|
666
|
+
// tests/unit/asset.test.ts
|
|
667
|
+
import { asset } from "zerotal/assets";
|
|
668
|
+
|
|
669
|
+
test("asset() resolves against the configured prefix", () => {
|
|
670
|
+
expect(asset("app.css")).toBe("/css/app.css");
|
|
671
|
+
});
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
### Inertia stale-bundle reloads
|
|
675
|
+
|
|
676
|
+
Inertia apps carry an asset version on every page object, and a request whose
|
|
677
|
+
`X-Inertia-Version` no longer matches the server's is answered with a `409` and an
|
|
678
|
+
`X-Inertia-Location` header, which is what makes a browser holding yesterday's
|
|
679
|
+
bundle do a full reload after a deploy. That flow only fires when the version
|
|
680
|
+
actually changes between builds, so it is worth one test:
|
|
681
|
+
|
|
682
|
+
```typescript
|
|
683
|
+
// tests/http/assets.test.ts
|
|
684
|
+
test("a stale bundle is told to reload", async () => {
|
|
685
|
+
const res = await app.get("/dashboard", {
|
|
686
|
+
"X-Inertia": "true",
|
|
687
|
+
"X-Inertia-Version": "stale-version",
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
res.assertStatus(409);
|
|
691
|
+
res.assertHeader("X-Inertia-Location");
|
|
692
|
+
});
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
## References
|
|
696
|
+
|
|
697
|
+
### Commands
|
|
698
|
+
|
|
699
|
+
| Command | Effect |
|
|
700
|
+
| ----------------------------------- | ----------------------------------------------- |
|
|
701
|
+
| `bun zt serve` | Serve the app; assets are built once at boot |
|
|
702
|
+
| `bun zt serve --dev` | Dev server with hot reload |
|
|
703
|
+
| `bun zt css:build` | Production CSS build (Flow/View) |
|
|
704
|
+
| `bun zt inertia:build` | Development JS+CSS build (Inertia) |
|
|
705
|
+
| `bun zt inertia:build --production` | Production JS+CSS build (Inertia) |
|
|
706
|
+
| `bun zt route:list` | List all routes (including static asset routes) |
|
|
707
|
+
|
|
708
|
+
Which build command belongs to your app follows from the view layer, not from
|
|
709
|
+
preference: `css:build` covers Flow and View, which ship server-rendered HTML
|
|
710
|
+
and need only a stylesheet; `inertia:build` covers Inertia, which additionally
|
|
711
|
+
bundles page components into JavaScript. Running the wrong one leaves the app
|
|
712
|
+
without the output it looks for at boot.
|
|
713
|
+
|
|
714
|
+
### Files
|
|
715
|
+
|
|
716
|
+
| File | Purpose |
|
|
717
|
+
| --------------------------------- | --------------------------------- |
|
|
718
|
+
| `resources/css/app.css` | Tailwind entry (all stacks) |
|
|
719
|
+
| `resources/app.html` | HTML shell (Inertia only) |
|
|
720
|
+
| `resources/js/app.tsx` | JS entry (Inertia only) |
|
|
721
|
+
| `resources/js/pages/` | Inertia page components (default) |
|
|
722
|
+
| `resources/js/pages.generated.ts` | Auto-generated page registry |
|
|
723
|
+
| `public/css/app.css` | Built CSS output (Flow/View) |
|
|
724
|
+
| `public/assets/app.js` | Built JS output (Inertia) |
|
|
725
|
+
| `public/assets/app.css` | Built CSS output (Inertia) |
|
|
726
|
+
|
|
727
|
+
Everything under `resources/` is source you edit; everything under `public/` is
|
|
728
|
+
build output. `public/` is regenerated, so nothing hand-written should live there
|
|
729
|
+
and the whole directory is safe to delete and rebuild.
|
|
730
|
+
|
|
731
|
+
### Configuration
|
|
732
|
+
|
|
733
|
+
| Key | Controls |
|
|
734
|
+
| ------------------- | ------------------------------------------------------ |
|
|
735
|
+
| `app.assets.prefix` | URL prefix `asset()` resolves against — point at a CDN |
|
|
736
|
+
| `ASSET_VERSION` | Inertia asset version; a mismatch forces a full reload |
|
|
737
|
+
|
|
738
|
+
```typescript
|
|
739
|
+
import { asset } from "zerotal/assets";
|
|
740
|
+
|
|
741
|
+
asset("app.css"); // → "/css/app.css"
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
See [the asset helper](#the-asset-helper) for what it does in dev versus
|
|
745
|
+
production, and [what triggers a restart](#what-triggers-a-restart-and-what-only-rebuilds)
|
|
746
|
+
for the watcher's rules.
|
|
747
|
+
|
|
748
|
+
## Next steps
|
|
749
|
+
|
|
750
|
+
- [Flow](/docs/flow) — the zero-config server-rendered SPA stack.
|
|
751
|
+
- [Inertia](/docs/inertia) — controllers, pages, shared props, and SSR.
|
|
752
|
+
- [Middleware & versioning](/docs/inertia/middleware) — the asset-version flow in full.
|
|
753
|
+
- [Components](/docs/components) — themeable flow-ui components for your UI.
|
|
754
|
+
- [View](/docs/view) — classic SSR with JSX controllers.
|
|
755
|
+
- [Deployment](/docs/deployment) — run the production asset build before serving.
|