Skip to content

Commit c1f90bb

Browse files
committed
Merge remote-tracking branch 'origin/main' into fix-layout-thrashign
2 parents 1906e23 + 9509b08 commit c1f90bb

21 files changed

Lines changed: 416 additions & 64 deletions

File tree

AGENTS.md

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
# Agents
2+
3+
## Repository Overview
4+
5+
Fresh is a web framework for Deno built on Preact. This is a **Deno monorepo**
6+
with workspace members in `packages/*` and `www/`.
7+
8+
### Packages
9+
10+
- **`packages/fresh/`** (`@fresh/core`): Core framework — routing, rendering,
11+
islands, build cache, middlewares, and client/server runtime.
12+
- **`packages/plugin-vite/`** (`@fresh/plugin-vite`): Vite integration plugin
13+
with dev server, SSR/client builds, and HMR.
14+
- **`packages/init/`** (`@fresh/init`): Project scaffolding
15+
(`deno run -A jsr:@fresh/init`).
16+
- **`packages/update/`** (`@fresh/update`): Automated Fresh 1.x to 2.x migration
17+
tool using ts-morph for AST transforms.
18+
- **`packages/build-id/`** (`@fresh/build-id`): Build/deployment ID generation.
19+
- **`packages/plugin-tailwindcss/`** (`@fresh/plugin-tailwind`): TailwindCSS v4
20+
plugin.
21+
- **`packages/plugin-tailwindcss-v3/`** (`@fresh/plugin-tailwind-v3`): Legacy
22+
TailwindCSS v3 plugin.
23+
- **`packages/examples/`** (`@fresh/examples`): Example components for tests.
24+
25+
### Other directories
26+
27+
- **`www/`**: Documentation website (fresh.deno.dev), built with Fresh + Vite +
28+
Tailwind. Has its own routes, islands, and vite.config.ts.
29+
- **`docs/`**: Markdown documentation organized by version (`latest/`, `1.x/`,
30+
`canary/`).
31+
- **`tools/`**: `release.ts` (version bumping), `check_docs.ts` (doc
32+
validation), `check_links.ts` (link checker).
33+
- **`vendor/`**: Vendored dependencies (`"vendor": true` in deno.json).
34+
35+
## Git Workflow
36+
37+
- To check out a PR branch, use `gh pr checkout <pr-number>`. Do not set up
38+
remotes manually.
39+
- Always run `deno fmt` before pushing.
40+
- Do not commit `deno.lock` changes unless the PR is specifically about updating
41+
dependencies. Lockfile diffs tend to be noisy and environment-specific.
42+
- **Never amend commits or force push.** Always create new commits.
43+
44+
## Development
45+
46+
- Run `deno task ok` before pushing — it runs the full local CI check (fmt,
47+
lint, type check, tests).
48+
- Run `deno install` if you get missing dependency errors.
49+
- Tests: `deno task test` (all tests, parallel). Tests use `@std/expect` for
50+
assertions and `linkedom` for DOM testing.
51+
- JSX is configured in "precompile" mode with Preact as the import source.
52+
53+
### Lockfile quirks
54+
55+
The lockfile contains remote specifiers pointing to `refs/heads/main` (e.g.
56+
`raw.githubusercontent.com/.../refs/heads/main/...`). These hashes go stale when
57+
upstream pushes. When that happens, manually update the hash in `deno.lock`
58+
since `deno cache --reload` cannot fix it (see
59+
https://github.com/denoland/deno/issues/32991).
60+
61+
## Architecture
62+
63+
### Request lifecycle
64+
65+
1. `App.handler()` receives an HTTP request (`app.ts`)
66+
2. URL is parsed and normalized (double slashes removed)
67+
3. `UrlPatternRouter.match()` finds the matching route — static routes are
68+
checked first via direct `Map` lookup, then dynamic routes via `URLPattern`
69+
4. A `Context` is created with request, params, and build cache
70+
5. The middleware chain executes (built backwards as nested closures)
71+
6. `ctx.render()` composes layouts and app wrapper around the page component
72+
7. Preact's `renderToString()` generates HTML, with option hooks detecting
73+
islands along the way
74+
8. `FreshScripts` component emits the inline boot script with island imports and
75+
serialized props
76+
9. Response is returned with HTML and `Link` modulepreload headers
77+
78+
### Island architecture
79+
80+
Islands are interactive Preact components that hydrate on the client while the
81+
rest of the page stays static HTML.
82+
83+
**Server side** (`runtime/server/preact_hooks.ts`):
84+
85+
- Preact's diff hook intercepts every VNode during SSR
86+
- When a component exists in `buildCache.islandRegistry`, it's wrapped in HTML
87+
comment markers: `<!--frsh:island:NAME:PROPSIDX:KEY-->...<!--/frsh:island-->`
88+
- Island props are collected into a `RenderState.islandProps[]` array
89+
- JSX element props become **slots** — stored in `<template>` elements and
90+
replaced with symbolic references
91+
92+
**Client side** (`runtime/client/reviver.ts`):
93+
94+
- The `boot()` function is called from an inline `<script type="module">`
95+
- DOM is walked to find `<!--frsh:island:...-->` comment markers
96+
- Props are deserialized with custom handlers: signals become reactive, slots
97+
become VNode references
98+
- Each island is hydrated via `render(h(component, props), container)` using
99+
`scheduler.postTask()` for non-blocking hydration
100+
101+
### Routing
102+
103+
Filesystem paths are converted to URL patterns (`router.ts`, `fs_routes.ts`):
104+
105+
- `/routes/blog/[id].tsx` becomes `/blog/:id`
106+
- `/routes/blog/[...rest].tsx` becomes `/blog/:rest*`
107+
- `/routes/(group)/page.tsx` becomes `/page` (groups are transparent)
108+
- `/routes/[[id]].tsx` becomes `/:id?` (optional segment)
109+
110+
Routes form a **segment tree** (`segments.ts`) where each level accumulates
111+
middlewares, layouts, and error handlers. When a route matches, the tree is
112+
walked from root to leaf to build the full middleware chain.
113+
114+
### Build system
115+
116+
Two build paths exist:
117+
118+
- **esbuild-based** (`dev/builder.ts`, `dev/esbuild.ts`): The native Fresh
119+
builder. Discovers islands, creates entry points per island + a
120+
`fresh-runtime` entry, bundles with esbuild-wasm (splitting, tree-shaking, ESM
121+
output). Output goes to `/_fresh/js/{BUILD_ID}/`.
122+
- **Vite-based** (`plugin-vite/`): Uses Vite's environment API for dual
123+
client/SSR builds. Provides the same island discovery and bundling through
124+
Vite's plugin system with HMR in dev.
125+
126+
Both produce: separate island bundles, a client runtime entry, static assets
127+
with content hashing, and a BUILD_ID for cache busting.
128+
129+
Build caches come in two flavors:
130+
131+
- `MemoryBuildCache` / `DiskBuildCache` for development (live rebuilds)
132+
- `ProdBuildCache` for production (snapshot-based, read from `_fresh/`)
133+
134+
### Partials
135+
136+
`<Partial>` components enable incremental page updates without full reloads.
137+
They are wrapped with markers (`<!--frsh:partial:{name}:{mode}:{key}-->`) and
138+
support `replace`, `append`, and `prepend` modes. Elements with `f-client-nav`
139+
enable client-side navigation that fetches and swaps partials instead of full
140+
page loads.
141+
142+
## CI
143+
144+
CI runs on every PR against `main` across a matrix of Deno v2.x + canary on
145+
macOS, Windows, and Ubuntu. Steps:
146+
147+
1. `deno install`
148+
2. `deno fmt --check` (Ubuntu + v2.x only)
149+
3. `deno lint` (Ubuntu + v2.x only)
150+
4. Spell-check via `typos` (Ubuntu + v2.x only)
151+
5. `deno task check:types` (all platforms)
152+
6. `deno task test` (all platforms)
153+
7. `deno task check:docs` (all platforms)
154+
8. `deno task build-www` (Ubuntu + v2.x only)
155+
156+
Publishing to JSR happens automatically on push to `main` via `deno publish`.

deno.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686
"tailwindcss": "npm:tailwindcss@^4.1.10",
8787
"postcss": "npm:postcss@8.5.6",
8888

89-
"ts-morph": "npm:ts-morph@^26.0.0",
89+
"ts-morph": "npm:ts-morph@^27.0.2",
9090

9191
"@std/front-matter": "jsr:@std/front-matter@^1.0.5",
9292
"github-slugger": "npm:github-slugger@^2.0.0",

deno.lock

Lines changed: 23 additions & 23 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/latest/concepts/static-files.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,32 @@ const app = new App()
1616
.use(staticFiles());
1717
```
1818

19+
## Imported assets vs static files
20+
21+
When using Fresh with Vite (now the default), **files that you import in your
22+
JavaScript/TypeScript code should not be placed in the `static/` folder**. This
23+
prevents file duplication during the build process.
24+
25+
```tsx
26+
// Don't import from static/
27+
import "./static/styles.css";
28+
29+
// Import from outside static/ (e.g., assets/)
30+
import "./assets/styles.css";
31+
```
32+
33+
**Rule of thumb:**
34+
35+
- Files **imported in code** (CSS, icons, etc.): place outside `static/` (e.g.,
36+
in an `assets/` folder)
37+
- Files **referenced by URL path** (favicon.ico, fonts, robots.txt, PDFs, etc.):
38+
place in `static/`
39+
40+
When you import a file in your code, Vite processes it through its build
41+
pipeline, optimizes it, and adds a content hash to the filename for cache
42+
busting. Keeping these files outside `static/` ensures they're only included
43+
once in your build output.
44+
1945
## Caching headers
2046

2147
By default, Fresh adds caching headers for the `src` and `srcset` attributes on

0 commit comments

Comments
 (0)