Skip to content

Commit 57c49ea

Browse files
bartlomiejuclaude
andcommitted
merge main and update markdown example to modern Fresh 2 API
- Use define.page() in a route file instead of old App/ctx.render API - Use @/utils.ts import consistent with other examples - Fix route path to routes/markdown.tsx - Use inline lint-ignore comments instead of file-level directive - Remove external tutorial link (could go stale) - Fix step numbering (was skipping step 3) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2 parents 202a5c5 + 103427f commit 57c49ea

340 files changed

Lines changed: 7192 additions & 2000 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/CONTRIBUTING.md

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
# Contributing Guidelines
22

3-
## Submitting a pull request
3+
For the full contributing guide, see the
4+
[Fresh documentation](https://fresh.deno.dev/docs/latest/contributing).
45

5-
First, please be sure to ensure `deno task ok` is run and successfully passes.
6+
## PR title format
7+
8+
PR titles must be all lowercase and start with one of the following prefixes:
9+
10+
- `feat:` - new features
11+
- `fix:` - bug fixes
12+
- `test:` - test additions or changes
13+
- `chore:` - maintenance, refactoring, docs
14+
- `ci:` - CI/CD changes
15+
16+
Example: `feat: add middleware compilation`
17+
18+
## Before submitting
19+
20+
Run `deno task ok` and ensure it passes.

.github/workflows/ci.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,19 @@ on:
77
branches: [main]
88

99
jobs:
10+
pr-title:
11+
if: github.event_name == 'pull_request'
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Validate PR title
15+
env:
16+
TITLE: ${{ github.event.pull_request.title }}
17+
run: |
18+
if ! echo "$TITLE" | grep -qP '^(chore|ci|docs|feat|fix|refactor|test):'; then
19+
echo "::error::PR title must start with chore:, ci:, docs:, feat:, fix:, refactor: or test:"
20+
exit 1
21+
fi
22+
1023
test:
1124
runs-on: ${{ matrix.os }}
1225
timeout-minutes: 10
@@ -34,6 +47,9 @@ jobs:
3447
cache: true
3548
deno-version: ${{ matrix.deno }}
3649

50+
- name: Install dependencies
51+
run: deno install
52+
3753
- name: Verify formatting
3854
if: startsWith(matrix.os, 'ubuntu') && matrix.deno == 'v2.x'
3955
run: deno fmt --check
@@ -58,3 +74,7 @@ jobs:
5874
- name: Build fresh.deno.dev
5975
if: startsWith(matrix.os, 'ubuntu') && matrix.deno == 'v2.x'
6076
run: deno task build-www
77+
78+
- name: Check links
79+
if: startsWith(matrix.os, 'ubuntu') && matrix.deno == 'v2.x'
80+
run: deno task check:links

.github/workflows/deploy.yml

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

.github/workflows/publish.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ jobs:
2222
uses: denoland/setup-deno@v2
2323
with:
2424
cache: true
25+
deno-version: canary
26+
27+
- name: Install dependencies
28+
run: deno install
2529

2630
- name: Publish
2731
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 create @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 create @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: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
2-
"nodeModulesDir": "auto",
2+
"vendor": true,
3+
"nodeModulesDir": "manual",
34
"workspace": [
45
"./packages/*",
56
"./www"
@@ -15,6 +16,7 @@
1516
"check:types": "deno check --allow-import",
1617
"check:docs": "deno run -A tools/check_docs.ts",
1718
"ok": "deno fmt --check && deno lint && deno task check:types && deno task test",
19+
"check:links": "deno run -A tools/check_links.ts",
1820
"test:www": "deno test -A www/main_test.*",
1921
"release": "deno run -A tools/release.ts"
2022
},
@@ -47,17 +49,20 @@
4749
"@std/dotenv": "jsr:@std/dotenv@^0.225.5",
4850
"@std/http": "jsr:@std/http@^1.0.15",
4951
"@std/uuid": "jsr:@std/uuid@^1.0.7",
52+
"@supabase/postgrest-js": "npm:@supabase/postgrest-js@^1.21.4",
53+
"@types/mime-db": "npm:@types/mime-db@^1.43.6",
5054
"@types/node": "npm:@types/node@^24.3.0",
51-
"docsearch": "https://esm.sh/@docsearch/js@3.5.2?target=es2020",
55+
"@types/pg": "npm:@types/pg@^8.15.5",
56+
"@types/prismjs": "npm:@types/prismjs@^1.26.5",
57+
"docsearch": "npm:@docsearch/js@^3.5.2",
5258
"esbuild": "npm:esbuild@0.25.7",
5359
"esbuild-wasm": "npm:esbuild-wasm@0.25.7",
5460
"fresh": "jsr:@fresh/core@^2.0.0",
5561
"mime-db": "npm:mime-db@^1.54.0",
56-
"preact": "npm:preact@^10.27.0",
57-
"preact-render-to-string": "npm:preact-render-to-string@^6.5.11",
58-
"$ga4": "https://raw.githubusercontent.com/denoland/ga4/main/mod.ts",
62+
"preact": "npm:preact@^10.28.2",
63+
"preact-render-to-string": "npm:preact-render-to-string@^6.6.5",
5964
"@opentelemetry/api": "npm:@opentelemetry/api@^1.9.0",
60-
"@preact/signals": "npm:@preact/signals@^2.2.1",
65+
"@preact/signals": "npm:@preact/signals@^2.5.1",
6166
"@std/encoding": "jsr:@std/encoding@1",
6267
"@std/fmt": "jsr:@std/fmt@^1.0.7",
6368
"@std/fs": "jsr:@std/fs@1",
@@ -68,27 +73,28 @@
6873
"@std/semver": "jsr:@std/semver@1",
6974
"@std/streams": "jsr:@std/streams@1",
7075

71-
"@astral/astral": "jsr:@astral/astral@^0.5.3",
76+
"@astral/astral": "jsr:@astral/astral@^0.5.5",
7277
"@marvinh-test/fresh-island": "jsr:@marvinh-test/fresh-island@^0.0.3",
7378
"linkedom": "npm:linkedom@^0.18.10",
7479
"@std/async": "jsr:@std/async@^1.0.13",
7580
"@std/expect": "jsr:@std/expect@^1.0.16",
7681
"@std/testing": "jsr:@std/testing@^1.0.12",
7782

7883
"@tailwindcss/postcss": "npm:@tailwindcss/postcss@^4.1.10",
79-
"rollup": "npm:rollup@^4.50.0",
84+
"redis": "npm:redis@^5.8.2",
85+
"rollup": "npm:rollup@^4.55.1",
8086
"tailwindcss": "npm:tailwindcss@^4.1.10",
8187
"postcss": "npm:postcss@8.5.6",
8288

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

8591
"@std/front-matter": "jsr:@std/front-matter@^1.0.5",
8692
"github-slugger": "npm:github-slugger@^2.0.0",
87-
"imagescript": "https://deno.land/x/imagescript@1.3.0/mod.ts",
93+
"imagescript": "jsr:@matmen/imagescript@^1.3.0",
8894
"marked": "npm:marked@^15.0.11",
8995
"marked-mangle": "npm:marked-mangle@^1.1.9",
9096
"prismjs": "npm:prismjs@^1.29.0",
91-
"vite": "npm:vite@^7.1.5"
97+
"vite": "npm:vite@^7.3.1"
9298
},
9399
"compilerOptions": {
94100
"lib": ["dom", "dom.asynciterable", "deno.ns", "deno.unstable"],

0 commit comments

Comments
 (0)