Skip to content

Commit 172a856

Browse files
committed
Merge remote-tracking branch 'origin/main' into actually-no-js-by-default
2 parents cc1387a + dc23248 commit 172a856

104 files changed

Lines changed: 4267 additions & 549 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: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ jobs:
1515
env:
1616
TITLE: ${{ github.event.pull_request.title }}
1717
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:"
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:"
2020
exit 1
2121
fi
2222

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ with workspace members in `packages/*` and `www/`.
1212
- **`packages/plugin-vite/`** (`@fresh/plugin-vite`): Vite integration plugin
1313
with dev server, SSR/client builds, and HMR.
1414
- **`packages/init/`** (`@fresh/init`): Project scaffolding
15-
(`deno run -A jsr:@fresh/init`).
15+
(`deno create @fresh/init`).
1616
- **`packages/update/`** (`@fresh/update`): Automated Fresh 1.x to 2.x migration
1717
tool using ts-morph for AST transforms.
1818
- **`packages/build-id/`** (`@fresh/build-id`): Build/deployment ID generation.

README.md

Lines changed: 1 addition & 1 deletion
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:

deno.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"@std/collections": "jsr:@std/collections@^1.1.2",
4949
"@std/dotenv": "jsr:@std/dotenv@^0.225.5",
5050
"@std/http": "jsr:@std/http@^1.0.15",
51+
"@std/net": "jsr:@std/net@^1.0.6",
5152
"@std/uuid": "jsr:@std/uuid@^1.0.7",
5253
"@supabase/postgrest-js": "npm:@supabase/postgrest-js@^1.21.4",
5354
"@types/mime-db": "npm:@types/mime-db@^1.43.6",

deno.lock

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

docs/1.x/concepts/partials.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ Let's use a typical documentation page layout as an example. It often features a
7676
main content area and a sidebar of links to switch between pages of the
7777
documentation (marked green here).
7878

79-
![A sketched layout of a typical documentation page with the sidebar on the left composed of green links and a main content area on the right. The main content area is labeled as Partial docs-content](/docs/1.x/fresh-partial-docs.png)
79+
![A sketched layout of a typical documentation page with the sidebar on the left composed of green links and a main content area on the right. The main content area is labeled as Partial docs-content](/docs/fresh-partial-docs.png)
8080

8181
The code for such a page (excluding styling) might look like this:
8282

docs/check_images_test.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/**
2+
* Verifies that all image references in documentation markdown files
3+
* point to existing files in www/static/.
4+
*
5+
* Run: deno test -A docs/check_images_test.ts
6+
*/
7+
import { walk } from "jsr:@std/fs@1/walk";
8+
import { join, resolve } from "jsr:@std/path@1";
9+
10+
const ROOT = resolve(import.meta.dirname!);
11+
const PROJECT_ROOT = resolve(ROOT, "..");
12+
const DOCS_DIR = ROOT;
13+
const STATIC_DIR = join(PROJECT_ROOT, "www", "static");
14+
15+
const IMAGE_RE = /!\[.*?\]\(([^)]+)\)/g;
16+
17+
Deno.test("all doc image references point to existing files", async () => {
18+
const errors: string[] = [];
19+
20+
for await (
21+
const entry of walk(DOCS_DIR, {
22+
exts: [".md", ".mdx"],
23+
includeDirs: false,
24+
})
25+
) {
26+
const content = await Deno.readTextFile(entry.path);
27+
const relativePath = entry.path.slice(PROJECT_ROOT.length);
28+
29+
for (const match of content.matchAll(IMAGE_RE)) {
30+
const imgPath = match[1];
31+
32+
// Skip external URLs
33+
if (imgPath.startsWith("http://") || imgPath.startsWith("https://")) {
34+
continue;
35+
}
36+
37+
// Doc image paths like "/docs/foo.png" map to "www/static/docs/foo.png"
38+
const fsPath = join(STATIC_DIR, imgPath);
39+
40+
try {
41+
await Deno.stat(fsPath);
42+
} catch {
43+
errors.push(`${relativePath}: image not found: ${imgPath}`);
44+
}
45+
}
46+
}
47+
48+
if (errors.length > 0) {
49+
throw new Error(
50+
`Found ${errors.length} broken image reference(s):\n\n${
51+
errors.join("\n")
52+
}`,
53+
);
54+
}
55+
});
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
---
2+
description: |
3+
Quick reference for all public exports from Fresh's entry points: fresh, fresh/runtime, and fresh/dev.
4+
---
5+
6+
This page lists all public exports from Fresh's entry points.
7+
8+
> [info]: You can also explore Fresh's full API documentation on JSR:
9+
> [`@fresh/core`](https://jsr.io/@fresh/core/doc)
10+
11+
## `fresh`
12+
13+
The main entry point for server-side code.
14+
15+
```ts
16+
import { App, createDefine, HttpError, page, staticFiles } from "fresh";
17+
```
18+
19+
| Export | Kind | Description |
20+
| --------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------- |
21+
| [`App`](https://jsr.io/@fresh/core/doc/~/App) | Class | The main application class. See [App](/docs/concepts/app). |
22+
| [`staticFiles`](https://jsr.io/@fresh/core/doc/~/staticFiles) | Function | Middleware for serving static files. See [Static Files](/docs/concepts/static-files). |
23+
| [`createDefine`](https://jsr.io/@fresh/core/doc/~/createDefine) | Function | Create type-safe `define.*` helpers. See [Define Helpers](/docs/advanced/define). |
24+
| [`page`](https://jsr.io/@fresh/core/doc/~/page) | Function | Return data from a handler to a page component. See [Data Fetching](/docs/concepts/data-fetching). |
25+
| [`HttpError`](https://jsr.io/@fresh/core/doc/~/HttpError) | Class | Throw HTTP errors with status codes. See [Error Handling](/docs/advanced/error-handling). |
26+
| [`cors`](https://jsr.io/@fresh/core/doc/~/cors) | Function | CORS middleware. See [cors](/docs/plugins/cors). |
27+
| [`csrf`](https://jsr.io/@fresh/core/doc/~/csrf) | Function | CSRF protection middleware. See [csrf](/docs/plugins/csrf). |
28+
| [`csp`](https://jsr.io/@fresh/core/doc/~/csp) | Function | Content Security Policy middleware. See [csp](/docs/plugins/csp). |
29+
| [`trailingSlashes`](https://jsr.io/@fresh/core/doc/~/trailingSlashes) | Function | Trailing slash enforcement middleware. See [trailingSlashes](/docs/plugins/trailing-slashes). |
30+
31+
**Types:**
32+
33+
| Export | Kind | Description |
34+
| --------------------------------------------------------------------------------------------------------------------------------------------- | --------- | --------------------------------------------------------------------------- |
35+
| [`Context`](https://jsr.io/@fresh/core/doc/~/Context) / [`FreshContext`](https://jsr.io/@fresh/core/doc/~/FreshContext) | Interface | The request context passed to all middlewares and handlers. |
36+
| [`PageProps`](https://jsr.io/@fresh/core/doc/~/PageProps) | Type | Props received by page components (`data`, `url`, `params`, `state`, etc.). |
37+
| [`Middleware`](https://jsr.io/@fresh/core/doc/~/Middleware) / [`MiddlewareFn`](https://jsr.io/@fresh/core/doc/~/MiddlewareFn) | Type | Middleware function type. |
38+
| [`HandlerFn`](https://jsr.io/@fresh/core/doc/~/HandlerFn) | Type | Single handler function type. |
39+
| [`HandlerByMethod`](https://jsr.io/@fresh/core/doc/~/HandlerByMethod) | Type | Object with per-method handler functions. |
40+
| [`RouteHandler`](https://jsr.io/@fresh/core/doc/~/RouteHandler) | Type | Union of `HandlerFn` and `HandlerByMethod`. |
41+
| [`PageResponse`](https://jsr.io/@fresh/core/doc/~/PageResponse) | Type | Return type of `page()`. |
42+
| [`RouteConfig`](https://jsr.io/@fresh/core/doc/~/RouteConfig) | Interface | Route configuration (`routeOverride`, `skipInheritedLayouts`, etc.). |
43+
| [`LayoutConfig`](https://jsr.io/@fresh/core/doc/~/LayoutConfig) | Interface | Layout configuration (`skipInheritedLayouts`, `skipAppWrapper`). |
44+
| [`Define`](https://jsr.io/@fresh/core/doc/~/Define) | Interface | Type of the object returned by `createDefine()`. |
45+
| [`FreshConfig`](https://jsr.io/@fresh/core/doc/~/FreshConfig) / [`ResolvedFreshConfig`](https://jsr.io/@fresh/core/doc/~/ResolvedFreshConfig) | Interface | App configuration types. |
46+
| [`ListenOptions`](https://jsr.io/@fresh/core/doc/~/ListenOptions) | Interface | Options for `app.listen()`. |
47+
| [`Island`](https://jsr.io/@fresh/core/doc/~/Island) | Type | Island component type. |
48+
| [`Method`](https://jsr.io/@fresh/core/doc/~/Method) | Type | HTTP method union type. |
49+
| [`RouteData`](https://jsr.io/@fresh/core/doc/~/RouteData) | Type | Data type returned by route handlers via `page()`. |
50+
| [`Lazy`](https://jsr.io/@fresh/core/doc/~/Lazy) / [`MaybeLazy`](https://jsr.io/@fresh/core/doc/~/MaybeLazy) | Type | Utility types for lazily-loaded routes and middleware. |
51+
| [`CORSOptions`](https://jsr.io/@fresh/core/doc/~/CORSOptions) | Interface | Options for `cors()`. |
52+
| [`CsrfOptions`](https://jsr.io/@fresh/core/doc/~/CsrfOptions) | Interface | Options for `csrf()`. |
53+
| [`CSPOptions`](https://jsr.io/@fresh/core/doc/~/CSPOptions) | Interface | Options for `csp()`. |
54+
55+
## `fresh/runtime`
56+
57+
Shared runtime utilities for both server and client code. Safe to import in
58+
[islands](/docs/concepts/islands).
59+
60+
```ts
61+
import {
62+
asset,
63+
assetSrcSet,
64+
Head,
65+
HttpError,
66+
IS_BROWSER,
67+
Partial,
68+
} from "fresh/runtime";
69+
```
70+
71+
| Export | Kind | Description |
72+
| --------------------------------------------------------------------- | --------- | ---------------------------------------------------------------------------------------------- |
73+
| [`IS_BROWSER`](https://jsr.io/@fresh/core/doc/runtime/~/IS_BROWSER) | Constant | `true` in the browser, `false` on the server. Use to guard browser-only code. |
74+
| [`asset`](https://jsr.io/@fresh/core/doc/runtime/~/asset) | Function | Add cache-busting query params to asset URLs. See [Static Files](/docs/concepts/static-files). |
75+
| [`assetSrcSet`](https://jsr.io/@fresh/core/doc/runtime/~/assetSrcSet) | Function | Apply `asset()` to all URLs in a `srcset` string. |
76+
| [`Partial`](https://jsr.io/@fresh/core/doc/runtime/~/Partial) | Component | Mark a region for partial updates. See [Partials](/docs/advanced/partials). |
77+
| [`Head`](https://jsr.io/@fresh/core/doc/runtime/~/Head) | Component | Add elements to the document `<head>`. See [<head> element](/docs/advanced/head). |
78+
| [`HttpError`](https://jsr.io/@fresh/core/doc/runtime/~/HttpError) | Class | HTTP error class (re-exported from `fresh`). |
79+
80+
## `fresh/dev`
81+
82+
Development and build tools. Only used in `dev.ts` (legacy) or build scripts.
83+
84+
```ts
85+
import { Builder } from "fresh/dev";
86+
```
87+
88+
| Export | Kind | Description |
89+
| --------------------------------------------------------- | ----- | ---------------------------------------------------------------------- |
90+
| [`Builder`](https://jsr.io/@fresh/core/doc/dev/~/Builder) | Class | Pre-Vite build system (legacy). See [Builder](/docs/advanced/builder). |
91+
92+
**Types:**
93+
94+
| Export | Kind | Description |
95+
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ----------------------------- |
96+
| [`BuildOptions`](https://jsr.io/@fresh/core/doc/dev/~/BuildOptions) | Interface | Options for `new Builder()`. |
97+
| [`ResolvedBuildConfig`](https://jsr.io/@fresh/core/doc/dev/~/ResolvedBuildConfig) | Interface | Resolved build configuration. |
98+
| [`OnTransformArgs`](https://jsr.io/@fresh/core/doc/dev/~/OnTransformArgs) / [`OnTransformOptions`](https://jsr.io/@fresh/core/doc/dev/~/OnTransformOptions) / [`TransformFn`](https://jsr.io/@fresh/core/doc/dev/~/TransformFn) | Type | Build plugin hook types. |
Lines changed: 116 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,53 @@
11
---
22
description: |
3-
Add a global app wrapper to provide common meta tags or context for application routes.
3+
The app wrapper defines the outermost HTML shell shared by all pages - the <html>, <head>, and <body> tags.
44
---
55

6-
The app wrapper component is a Preact component that represents the outer
7-
structure of the HTML document, typically up until the `<body>`-tag. It is only
8-
rendered on the server and never on the client. The passed `Component` value
9-
represents the children of this component.
6+
The app wrapper is the outermost component in Fresh's rendering hierarchy. It
7+
defines the `<html>`, `<head>`, and `<body>` tags that every page shares. It is
8+
only rendered on the server.
109

11-
```tsx main.tsx
10+
## When to use an app wrapper
11+
12+
Use an app wrapper when you need to:
13+
14+
- Set the document language (`<html lang="en">`)
15+
- Include global `<meta>` tags, fonts, or stylesheets
16+
- Add analytics scripts or structured data to every page
17+
- Set a global `<body>` class or data attribute
18+
- Provide a consistent HTML skeleton without repeating it in every layout
19+
20+
If you're using [file-based routing](/docs/concepts/file-routing), create a
21+
`routes/_app.tsx` file. Otherwise, register it programmatically with
22+
`app.appWrapper()`.
23+
24+
## Basic example
25+
26+
```tsx routes/_app.tsx
27+
import { define } from "../utils.ts";
28+
29+
export default define.page(({ Component, url }) => {
30+
return (
31+
<html lang="en">
32+
<head>
33+
<meta charset="utf-8" />
34+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
35+
<title>My App</title>
36+
<link rel="stylesheet" href="/styles.css" />
37+
</head>
38+
<body>
39+
<Component />
40+
</body>
41+
</html>
42+
);
43+
});
44+
```
45+
46+
## Programmatic registration
47+
48+
When building your app with `new App()` instead of file-based routing:
49+
50+
```tsx
1251
function AppWrapper({ Component }) {
1352
return (
1453
<html lang="en">
@@ -26,8 +65,75 @@ function AppWrapper({ Component }) {
2665
app.appWrapper(AppWrapper);
2766
```
2867

29-
Every [`ctx.render()`](/docs/concepts/context#render-1) call will include the
30-
app wrapper component by default, unless opted out.
68+
Only one app wrapper is supported per [`App`](/docs/concepts/app) instance.
69+
70+
## How it fits in the render hierarchy
71+
72+
When Fresh renders a page, the components nest like this:
73+
74+
1. **App wrapper** (`_app.tsx`) - outermost, provides `<html>`/`<head>`/`<body>`
75+
2. **[Layouts](/docs/concepts/layouts)** (`_layout.tsx`) - shared page chrome
76+
(nav, sidebar, footer)
77+
3. **Page component** - the route itself
78+
79+
The app wrapper wraps everything. Layouts sit inside it and wrap the page.
80+
81+
## Accessing request data
82+
83+
The app wrapper receives the same props as page components - `url`, `state`,
84+
`params`, and more. This is useful for conditional logic:
85+
86+
```tsx routes/_app.tsx
87+
import { define } from "../utils.ts";
3188

32-
Note that only one app wrapper component is supported per
33-
[`App`](/docs/concepts/app) instance.
89+
export default define.page(({ Component, url, state }) => {
90+
return (
91+
<html lang="en" data-theme={state.theme ?? "light"}>
92+
<head>
93+
<meta charset="utf-8" />
94+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
95+
<title>My App</title>
96+
<meta property="og:url" content={url.href} />
97+
<link rel="canonical" href={url.href} />
98+
</head>
99+
<body>
100+
<Component />
101+
</body>
102+
</html>
103+
);
104+
});
105+
```
106+
107+
## Skipping the app wrapper
108+
109+
Some routes may need to bypass the app wrapper entirely - for example, API
110+
routes that return JSON, or pages that need a completely different HTML
111+
structure. Use `skipAppWrapper` in the route config:
112+
113+
```tsx routes/embed.tsx
114+
import { type RouteConfig } from "fresh";
115+
import { define } from "../utils.ts";
116+
117+
export const config: RouteConfig = {
118+
skipAppWrapper: true,
119+
};
120+
121+
export default define.page(() => {
122+
return (
123+
<html>
124+
<head>
125+
<title>Embed</title>
126+
</head>
127+
<body>
128+
<div id="widget">Embeddable widget</div>
129+
</body>
130+
</html>
131+
);
132+
});
133+
```
134+
135+
When using programmatic layouts, pass `skipAppWrapper` as an option:
136+
137+
```ts main.ts
138+
app.layout("/embed", EmbedLayout, { skipAppWrapper: true });
139+
```

0 commit comments

Comments
 (0)