Skip to content

Commit e716def

Browse files
bartlomiejuclaude
andcommitted
merge main, resolve conflicts
Keep main's new tests and append PR's vite-plugin-pwa tests. Take main's deno.lock (will be regenerated). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2 parents 76ec6cb + dc23248 commit e716def

359 files changed

Lines changed: 8357 additions & 4161 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: 17 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|perf|refactor|test):'; then
19+
echo "::error::PR title must start with chore:, ci:, docs:, feat:, fix:, perf:, refactor: or test:"
20+
exit 1
21+
fi
22+
1023
test:
1124
runs-on: ${{ matrix.os }}
1225
timeout-minutes: 10
@@ -61,3 +74,7 @@ jobs:
6174
- name: Build fresh.deno.dev
6275
if: startsWith(matrix.os, 'ubuntu') && matrix.deno == 'v2.x'
6376
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 & 40 deletions
This file was deleted.

.github/workflows/publish.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ jobs:
2222
uses: denoland/setup-deno@v2
2323
with:
2424
cache: true
25+
deno-version: canary
2526

2627
- name: Install dependencies
2728
run: deno install

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: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ You can scaffold a new project by running the Fresh init script. To scaffold a
2929
project run the following:
3030

3131
```sh
32-
deno run -Ar jsr:@fresh/init
32+
deno create @fresh/init
3333
```
3434

3535
Then navigate to the newly created project folder:
@@ -52,11 +52,10 @@ To deploy the project to the live internet, you can use
5252
[Deno Deploy](https://deno.com/deploy):
5353

5454
1. Push your project to GitHub.
55-
2. [Create a Deno Deploy project](https://dash.deno.com/new).
56-
3. [Link](https://docs.deno.com/deploy/manual/#deploy-your-project) the Deno
57-
Deploy project to the **`main.ts`** file in the root of the created
58-
repository.
59-
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.
6059

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

deno.json

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2+
"vendor": true,
23
"nodeModulesDir": "manual",
34
"workspace": [
45
"./packages/*",
@@ -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
},
@@ -46,22 +48,22 @@
4648
"@std/collections": "jsr:@std/collections@^1.1.2",
4749
"@std/dotenv": "jsr:@std/dotenv@^0.225.5",
4850
"@std/http": "jsr:@std/http@^1.0.15",
51+
"@std/net": "jsr:@std/net@^1.0.6",
4952
"@std/uuid": "jsr:@std/uuid@^1.0.7",
5053
"@supabase/postgrest-js": "npm:@supabase/postgrest-js@^1.21.4",
5154
"@types/mime-db": "npm:@types/mime-db@^1.43.6",
5255
"@types/node": "npm:@types/node@^24.3.0",
5356
"@types/pg": "npm:@types/pg@^8.15.5",
5457
"@types/prismjs": "npm:@types/prismjs@^1.26.5",
55-
"docsearch": "https://esm.sh/@docsearch/js@3.5.2?target=es2020",
58+
"docsearch": "npm:@docsearch/js@^3.5.2",
5659
"esbuild": "npm:esbuild@0.25.7",
5760
"esbuild-wasm": "npm:esbuild-wasm@0.25.7",
5861
"fresh": "jsr:@fresh/core@^2.0.0",
5962
"mime-db": "npm:mime-db@^1.54.0",
60-
"preact": "npm:preact@^10.27.0",
61-
"preact-render-to-string": "npm:preact-render-to-string@^6.5.11",
62-
"$ga4": "https://raw.githubusercontent.com/denoland/ga4/main/mod.ts",
63+
"preact": "npm:preact@^10.28.2",
64+
"preact-render-to-string": "npm:preact-render-to-string@^6.6.5",
6365
"@opentelemetry/api": "npm:@opentelemetry/api@^1.9.0",
64-
"@preact/signals": "npm:@preact/signals@^2.2.1",
66+
"@preact/signals": "npm:@preact/signals@^2.5.1",
6567
"@std/encoding": "jsr:@std/encoding@1",
6668
"@std/fmt": "jsr:@std/fmt@^1.0.7",
6769
"@std/fs": "jsr:@std/fs@1",
@@ -72,7 +74,7 @@
7274
"@std/semver": "jsr:@std/semver@1",
7375
"@std/streams": "jsr:@std/streams@1",
7476

75-
"@astral/astral": "jsr:@astral/astral@^0.5.3",
77+
"@astral/astral": "jsr:@astral/astral@^0.5.5",
7678
"@marvinh-test/fresh-island": "jsr:@marvinh-test/fresh-island@^0.0.3",
7779
"linkedom": "npm:linkedom@^0.18.10",
7880
"@std/async": "jsr:@std/async@^1.0.13",
@@ -81,19 +83,19 @@
8183

8284
"@tailwindcss/postcss": "npm:@tailwindcss/postcss@^4.1.10",
8385
"redis": "npm:redis@^5.8.2",
84-
"rollup": "npm:rollup@^4.50.0",
86+
"rollup": "npm:rollup@^4.55.1",
8587
"tailwindcss": "npm:tailwindcss@^4.1.10",
8688
"postcss": "npm:postcss@8.5.6",
8789

88-
"ts-morph": "npm:ts-morph@^26.0.0",
90+
"ts-morph": "npm:ts-morph@^27.0.2",
8991

9092
"@std/front-matter": "jsr:@std/front-matter@^1.0.5",
9193
"github-slugger": "npm:github-slugger@^2.0.0",
92-
"imagescript": "https://deno.land/x/imagescript@1.3.0/mod.ts",
94+
"imagescript": "jsr:@matmen/imagescript@^1.3.0",
9395
"marked": "npm:marked@^15.0.11",
9496
"marked-mangle": "npm:marked-mangle@^1.1.9",
9597
"prismjs": "npm:prismjs@^1.29.0",
96-
"vite": "npm:vite@^7.1.5"
98+
"vite": "npm:vite@^7.3.1"
9799
},
98100
"compilerOptions": {
99101
"lib": ["dom", "dom.asynciterable", "deno.ns", "deno.unstable"],

0 commit comments

Comments
 (0)