Skip to content

Commit a2e8081

Browse files
authored
Merge branch 'denoland:main' into fix-basepath-root-index
2 parents 2daa513 + 8a2b659 commit a2e8081

511 files changed

Lines changed: 13558 additions & 5223 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ jobs:
3434
cache: true
3535
deno-version: ${{ matrix.deno }}
3636

37+
- name: Install dependencies
38+
run: deno install
39+
3740
- name: Verify formatting
3841
if: startsWith(matrix.os, 'ubuntu') && matrix.deno == 'v2.x'
3942
run: deno fmt --check

.github/workflows/deploy.yml

Lines changed: 0 additions & 37 deletions
This file was deleted.

.github/workflows/publish.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,8 @@ jobs:
2323
with:
2424
cache: true
2525

26+
- name: Install dependencies
27+
run: deno install
28+
2629
- name: Publish
2730
run: deno publish

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`.

README.md

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ simplicity.
1010

1111
Some stand-out features:
1212

13-
- Just-in-time rendering on the edge.
1413
- Island based client hydration for maximum interactivity.
1514
- Zero runtime overhead: no JS is shipped to the client by default.
1615
- No configuration necessary.
@@ -30,34 +29,33 @@ You can scaffold a new project by running the Fresh init script. To scaffold a
3029
project run the following:
3130

3231
```sh
33-
deno run -A -r https://fresh.deno.dev
32+
deno run -Ar jsr:@fresh/init
3433
```
3534

3635
Then navigate to the newly created project folder:
3736

3837
```
39-
cd deno-fresh-demo
38+
cd fresh-project
4039
```
4140

4241
From within your project folder, start the development server using the
4342
`deno task` command:
4443

4544
```
46-
deno task start
45+
deno task dev
4746
```
4847

49-
Now open http://localhost:8000 in your browser to view the page. You make
48+
Now open http://localhost:5173 in your browser to view the page. You make
5049
changes to the project source code and see them reflected in your browser.
5150

5251
To deploy the project to the live internet, you can use
5352
[Deno Deploy](https://deno.com/deploy):
5453

5554
1. Push your project to GitHub.
56-
2. [Create a Deno Deploy project](https://dash.deno.com/new).
57-
3. [Link](https://docs.deno.com/deploy/manual/#deploy-your-project) the Deno
58-
Deploy project to the **`main.ts`** file in the root of the created
59-
repository.
60-
4. The project will be deployed to a public $project.deno.dev subdomain.
55+
2. [Create a Deno Deploy project.](https://console.deno.com/new)
56+
3. Select your GitHub repository.
57+
4. The project will be deployed to a public $project.$username.deno.net
58+
subdomain with no configuration necessary.
6159

6260
For a more in-depth getting started guide, visit the
6361
[Getting Started](https://fresh.deno.dev/docs/getting-started) page in the Fresh

deno.json

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
{
2-
"nodeModulesDir": "auto",
2+
"vendor": true,
3+
"nodeModulesDir": "manual",
34
"workspace": [
45
"./packages/*",
56
"./www"
67
],
78
"tasks": {
9+
"demo": "deno task --cwd=packages/plugin-vite demo",
10+
"demo:build": "deno task --cwd=packages/plugin-vite demo:build",
11+
"demo:start": "deno task --cwd=packages/plugin-vite demo:start",
812
"test": "deno test -A --parallel",
9-
"www": "deno task --cwd=www start",
13+
"www": "deno task --cwd=www dev",
1014
"build-www": "deno task --cwd=www build",
1115
"screenshot": "deno run -A www/utils/screenshot.ts",
1216
"check:types": "deno check --allow-import",
@@ -33,26 +37,32 @@
3337
"www/static/fresh-badge-dark.svg",
3438
"*.todo"
3539
],
36-
"exclude": ["**/*_test.*", "src/__OLD/**", "*.todo"]
40+
"exclude": ["**/*_test.*", "src/__OLD/**", "*.todo", "**/tests/**"]
3741
},
3842
"imports": {
3943
"@deno/doc": "jsr:@deno/doc@^0.172.0",
4044
"@deno/esbuild-plugin": "jsr:@deno/esbuild-plugin@^1.2.0",
4145
"@fresh/build-id": "jsr:@fresh/build-id@^1.0.0",
4246
"@std/cli": "jsr:@std/cli@^1.0.19",
4347
"@std/collections": "jsr:@std/collections@^1.1.2",
48+
"@std/dotenv": "jsr:@std/dotenv@^0.225.5",
4449
"@std/http": "jsr:@std/http@^1.0.15",
4550
"@std/uuid": "jsr:@std/uuid@^1.0.7",
51+
"@supabase/postgrest-js": "npm:@supabase/postgrest-js@^1.21.4",
52+
"@types/mime-db": "npm:@types/mime-db@^1.43.6",
53+
"@types/node": "npm:@types/node@^24.3.0",
54+
"@types/pg": "npm:@types/pg@^8.15.5",
55+
"@types/prismjs": "npm:@types/prismjs@^1.26.5",
4656
"docsearch": "https://esm.sh/@docsearch/js@3.5.2?target=es2020",
4757
"esbuild": "npm:esbuild@0.25.7",
4858
"esbuild-wasm": "npm:esbuild-wasm@0.25.7",
49-
"fresh": "jsr:@fresh/core@^2.0.0-alpha.37",
59+
"fresh": "jsr:@fresh/core@^2.0.0",
5060
"mime-db": "npm:mime-db@^1.54.0",
51-
"preact": "npm:preact@^10.27.0",
52-
"preact-render-to-string": "npm:preact-render-to-string@^6.5.11",
61+
"preact": "npm:preact@^10.28.2",
62+
"preact-render-to-string": "npm:preact-render-to-string@^6.6.5",
5363
"$ga4": "https://raw.githubusercontent.com/denoland/ga4/main/mod.ts",
5464
"@opentelemetry/api": "npm:@opentelemetry/api@^1.9.0",
55-
"@preact/signals": "npm:@preact/signals@^2.2.1",
65+
"@preact/signals": "npm:@preact/signals@^2.5.1",
5666
"@std/encoding": "jsr:@std/encoding@1",
5767
"@std/fmt": "jsr:@std/fmt@^1.0.7",
5868
"@std/fs": "jsr:@std/fs@1",
@@ -63,31 +73,50 @@
6373
"@std/semver": "jsr:@std/semver@1",
6474
"@std/streams": "jsr:@std/streams@1",
6575

66-
"@astral/astral": "jsr:@astral/astral@^0.5.3",
67-
"@marvinh-test/fresh-island": "jsr:@marvinh-test/fresh-island@^0.0.2",
76+
"@astral/astral": "jsr:@astral/astral@^0.5.5",
77+
"@marvinh-test/fresh-island": "jsr:@marvinh-test/fresh-island@^0.0.3",
6878
"linkedom": "npm:linkedom@^0.18.10",
6979
"@std/async": "jsr:@std/async@^1.0.13",
7080
"@std/expect": "jsr:@std/expect@^1.0.16",
7181
"@std/testing": "jsr:@std/testing@^1.0.12",
7282

7383
"@tailwindcss/postcss": "npm:@tailwindcss/postcss@^4.1.10",
84+
"redis": "npm:redis@^5.8.2",
85+
"rollup": "npm:rollup@^4.55.1",
7486
"tailwindcss": "npm:tailwindcss@^4.1.10",
7587
"postcss": "npm:postcss@8.5.6",
7688

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

7991
"@std/front-matter": "jsr:@std/front-matter@^1.0.5",
8092
"github-slugger": "npm:github-slugger@^2.0.0",
8193
"imagescript": "https://deno.land/x/imagescript@1.3.0/mod.ts",
8294
"marked": "npm:marked@^15.0.11",
8395
"marked-mangle": "npm:marked-mangle@^1.1.9",
84-
"prismjs": "npm:prismjs@^1.29.0"
96+
"prismjs": "npm:prismjs@^1.29.0",
97+
"vite": "npm:vite@^7.3.1"
8598
},
8699
"compilerOptions": {
87100
"lib": ["dom", "dom.asynciterable", "deno.ns", "deno.unstable"],
88101
"jsx": "precompile",
89102
"jsxImportSource": "preact",
90-
"jsxPrecompileSkipElements": ["a", "img", "source", "body", "html", "head"]
103+
"jsxPrecompileSkipElements": [
104+
"a",
105+
"img",
106+
"source",
107+
"body",
108+
"html",
109+
"head",
110+
"title",
111+
"meta",
112+
"script",
113+
"link",
114+
"style",
115+
"base",
116+
"noscript",
117+
"template"
118+
],
119+
"types": ["vite/client"]
91120
},
92121
"lint": {
93122
"rules": {

0 commit comments

Comments
 (0)