Skip to content

Commit 0f6b979

Browse files
authored
Merge branch 'main' into rename-FreshContext-req-to-request
2 parents b1bd6ec + 9e58af7 commit 0f6b979

68 files changed

Lines changed: 1295 additions & 521 deletions

Some content is hidden

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

deno.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
"www/static/fresh-badge-dark.svg",
3434
"*.todo"
3535
],
36-
"exclude": ["**/*_test.*", "src/__OLD/**", "*.todo"]
36+
"exclude": ["**/*_test.*", "src/__OLD/**", "*.todo", "**/tests/**"]
3737
},
3838
"imports": {
3939
"@deno/doc": "jsr:@deno/doc@^0.172.0",
@@ -43,6 +43,7 @@
4343
"@std/collections": "jsr:@std/collections@^1.1.2",
4444
"@std/http": "jsr:@std/http@^1.0.15",
4545
"@std/uuid": "jsr:@std/uuid@^1.0.7",
46+
"@types/node": "npm:@types/node@^24.3.0",
4647
"docsearch": "https://esm.sh/@docsearch/js@3.5.2?target=es2020",
4748
"esbuild": "npm:esbuild@0.25.7",
4849
"esbuild-wasm": "npm:esbuild-wasm@0.25.7",
@@ -64,13 +65,14 @@
6465
"@std/streams": "jsr:@std/streams@1",
6566

6667
"@astral/astral": "jsr:@astral/astral@^0.5.3",
67-
"@marvinh-test/fresh-island": "jsr:@marvinh-test/fresh-island@^0.0.2",
68+
"@marvinh-test/fresh-island": "jsr:@marvinh-test/fresh-island@^0.0.3",
6869
"linkedom": "npm:linkedom@^0.18.10",
6970
"@std/async": "jsr:@std/async@^1.0.13",
7071
"@std/expect": "jsr:@std/expect@^1.0.16",
7172
"@std/testing": "jsr:@std/testing@^1.0.12",
7273

7374
"@tailwindcss/postcss": "npm:@tailwindcss/postcss@^4.1.10",
75+
"rollup": "npm:rollup@^4.50.0",
7476
"tailwindcss": "npm:tailwindcss@^4.1.10",
7577
"postcss": "npm:postcss@8.5.6",
7678

deno.lock

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

docs/canary/advanced/builder.md

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
---
2+
description: |
3+
The Builder class is used to generate optimized assets for production.
4+
---
5+
6+
> [warn]: The `Builder` class was used during the alpha phase of Fresh 2 before
7+
> the Fresh vite plugin was released. You can skip this page if you're using
8+
> vite.
9+
10+
The `Builder` class is used to generate production assets of your app. You'll
11+
typically find it being created inside your project's `dev.ts` file.
12+
13+
```ts dev.ts
14+
import { Builder } from "fresh/dev";
15+
16+
const builder = new Builder({ target: "safari12" });
17+
18+
if (Deno.args.includes("build")) {
19+
// This creates a production build
20+
await builder.build();
21+
} else {
22+
// This starts a development server with live reload
23+
await builder.listen(() => import("./main.ts"));
24+
}
25+
```
26+
27+
## Options
28+
29+
You can customize the builder by passing options.
30+
31+
```ts dev.ts
32+
const builder = new Builder({
33+
// Browser target for generated code. Maps to https://esbuild.github.io/api/#target
34+
target?: string | string[];
35+
// The root directory of the project. All other paths will be resolved
36+
// against this if they're relative. (Default: `Deno.cwd()`)
37+
root?: string;
38+
// The path to your server entry point. (Default: `<root>/main.ts`)
39+
serverEntry?: string;
40+
// Where to write generated files when doing a production build.
41+
// (default: `<root>/_fresh/`)
42+
outDir?: string;
43+
// Path to static file directory. (Default: `<root>/static/`)
44+
staticDir?: string;
45+
// Path to island directory. (Default: `<root>/islands`)
46+
islandDir?: string;
47+
// Path to routes directory. (Default: `<root>/routes`)
48+
routeDir?: string;
49+
// File paths which should be ignored
50+
ignore?: RegExp[];
51+
// Optionally generate production source maps
52+
// See https://esbuild.github.io/api/#source-maps
53+
sourceMap?: {
54+
kind?: boolean | 'linked' | 'inline' | 'external' | 'both';
55+
sourceRoot?: string;
56+
sourcesContent?: boolean;
57+
};
58+
})
59+
```
60+
61+
## Registering islands
62+
63+
The builder is where you'll register files that contain islands. This is the
64+
same API that Fresh uses internally.
65+
66+
```ts dev.ts
67+
const builder = new Builder();
68+
69+
// Path to local island
70+
builder.registerIsland("path/to/my/Island.tsx");
71+
// File urls work too
72+
builder.registerIsland("file:///path/to/my/Island.tsx");
73+
// Also islands from jsr
74+
builder.registerIsland("jsr:@marvinh-test/fresh-island");
75+
```
76+
77+
## Adding build plugins
78+
79+
The `Builder` has a very simple processing mechanism for static files.
80+
81+
```ts dev.ts
82+
builder.onTransformStaticFile({
83+
pluginName: "My cool plugin",
84+
filter: /\.css$/,
85+
}, (args) => {
86+
// Prepend `body { background: red }` to every `.css` file
87+
const code = `body { background: red } ${args.text}`;
88+
89+
return {
90+
content: code,
91+
map: undefined, // Optional: source maps
92+
};
93+
});
94+
```
95+
96+
> [info]: Only static files in `static/` or the value you set `staticDir` to
97+
> will be processed. The builder won't process anything else.
98+
99+
## Testing
100+
101+
Testing applications with the `Builder` class involves creating a build snapshot
102+
and assigning that to each app instance.
103+
104+
```ts my-app.test.ts
105+
// Best to do this once instead of for every test case for
106+
// performance reasons.
107+
const builder = new Builder();
108+
const applySnapshot = await builder.build({ snapshot: "memory" });
109+
110+
function testApp() {
111+
const app = new App()
112+
.get("/", () => new Response("hello"))
113+
.fsRoutes();
114+
115+
// Applies build snapshot to this app instance.
116+
applySnapshot(app);
117+
return app;
118+
}
119+
120+
Deno.test("My Test", async () => {
121+
const handler = testApp().handler();
122+
123+
const response = await handler(new Request("http://localhost"));
124+
const text = await response.text();
125+
126+
if (text !== "hello") {
127+
throw new Error("fail");
128+
}
129+
});
130+
```
131+
132+
## Tailwindcss
133+
134+
[Tailwindcss](https://tailwindcss.com/) is a utility-first CSS framework that
135+
generates CSS out of the class names that are used in JSX. Since we use
136+
Tailwindcss ourselves here at Deno, Fresh ships with an official plugin for
137+
that.
138+
139+
### Usage
140+
141+
1. Set `nodeModulesDir` in `deno.json` to `"auto"` or `"manual"`
142+
143+
```diff deno.json
144+
{
145+
"name": "@example/my-cool-project"
146+
+ "nodeModulesDir": "auto",
147+
"imports": {
148+
...
149+
}
150+
}
151+
```
152+
153+
2. Run `deno install jsr:@fresh/plugin-tailwind`
154+
3. Update `dev.ts`:
155+
156+
```diff dev.ts
157+
import { Builder } from "fresh/dev";
158+
+ import { tailwind } from "@fresh/plugin-tailwind";
159+
160+
const builder = new Builder();
161+
+ tailwind(builder);
162+
```
163+
164+
4. Add `@import "tailwindcss";` at the top of your main stylesheet.
165+
166+
For more information on how to use tailwindcss, check out
167+
[their documentation](https://tailwindcss.com/docs/styling-with-utility-classes).
168+
169+
### Options
170+
171+
You can customize the tailwind plugin via the following options:
172+
173+
```ts dev.ts
174+
tailwind(builder, app, {
175+
// Exclude certain files from processing
176+
exclude: ["/admin/**", "*.temp.css"],
177+
// Force optimization (defaults to production mode)
178+
optimize: true,
179+
// Exclude base styles
180+
base: null,
181+
});
182+
```
183+
184+
### Tailwindcss v3
185+
186+
If can't update to the current version of tailwindcss we have a dedicated
187+
`@fresh/plugin-tailwindcss-v3` plugin that uses tailwindcss v3. That way you can
188+
decided on your own when it's best to update to v4.
189+
190+
```ts dev.ts
191+
import { Builder } from "fresh/dev";
192+
import { tailwind } from "@fresh/plugin-tailwind-v3";
193+
194+
tailwind(builder, {});
195+
```

docs/canary/advanced/forms.md

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ This example demonstrates how to handle `application/x-www-form-urlencoded`
2020
`<form>` submissions:
2121

2222
```tsx routes/subscribe.tsx
23-
import { Handlers } from "$fresh/server.ts";
23+
import { define } from "../utils.ts";
2424

25-
export const handler: Handlers = {
25+
export const handlers = define.handlers({
2626
async GET(req, ctx) {
2727
return await ctx.render();
2828
},
@@ -40,9 +40,9 @@ export const handler: Handlers = {
4040
headers,
4141
});
4242
},
43-
};
43+
});
4444

45-
export default function Subscribe() {
45+
export default define.page<typeof handlers>(function Subscribe() {
4646
return (
4747
<>
4848
<form method="post">
@@ -51,7 +51,7 @@ export default function Subscribe() {
5151
</form>
5252
</>
5353
);
54-
}
54+
});
5555
```
5656

5757
When the user submits the form, Deno will retrieve the `email` value using the
@@ -65,40 +65,30 @@ that this time, we have to explicitly declare the form's encoding to be
6565
`multipart/form-data`.
6666

6767
```tsx routes/subscribe.tsx
68-
import { Handlers, type PageProps } from "$fresh/server.ts";
68+
import { define } from "../utils.ts";
6969

70-
interface Props {
71-
message: string | null;
72-
}
73-
74-
export const handler: Handlers<Props> = {
70+
export const handler = define.handlers({
7571
async GET(req, ctx) {
76-
return await ctx.render({
77-
message: null,
78-
});
72+
return { data: { message: null } };
7973
},
8074
async POST(req, ctx) {
8175
const form = await req.formData();
8276
const file = form.get("my-file") as File;
8377

8478
if (!file) {
85-
return ctx.render({
86-
message: `Please try again`,
87-
});
79+
return { data: { message: "Please try again" } };
8880
}
8981

9082
const name = file.name;
9183
const contents = await file.text();
9284

9385
console.log(contents);
9486

95-
return ctx.render({
96-
message: `${name} uploaded!`,
97-
});
87+
return { data: { message: `${name} uploaded!` } };
9888
},
99-
};
89+
});
10090

101-
export default function Upload(props: PageProps<Props>) {
91+
export default define.page<typeof handlers>(function Upload(props) {
10292
const { message } = props.data;
10393
return (
10494
<>
@@ -109,7 +99,7 @@ export default function Upload(props: PageProps<Props>) {
10999
{message ? <p>{message}</p> : null}
110100
</>
111101
);
112-
}
102+
});
113103
```
114104

115105
## A note of caution

docs/canary/advanced/index.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
description: |
3+
This chapter goes over some advanced concepts of Fresh.
4+
---
5+
6+
This section of the documentation describes advanced functionality of Fresh.

docs/canary/advanced/partials.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ The quickest way to get started is to enable partials for every page in
1818
`routes/_app.tsx` by making the following changes.
1919

2020
```diff routes/_app.tsx
21-
import { PageProps } from "$fresh/server.ts";
22-
+ import { Partial } from "$fresh/runtime.ts";
21+
import { define } from "../utils.ts";
22+
+ import { Partial } from "fresh/runtime";
2323

24-
export default function App({ Component }: PageProps) {
24+
export default define.page(function App({ Component }) {
2525
return (
2626
<html>
2727
<head>
@@ -37,7 +37,7 @@ The quickest way to get started is to enable partials for every page in
3737
</body>
3838
</html>
3939
);
40-
}
40+
});
4141
```
4242

4343
By adding the `f-client-nav` attribute, we enable partials for every element
@@ -81,7 +81,9 @@ documentation (marked green here).
8181
The code for such a page (excluding styling) might look like this:
8282

8383
```tsx routes/docs/[id].tsx
84-
export default defineRoute(async (req, ctx) => {
84+
import { define } from "../../utils.ts";
85+
86+
export default define.page(async (ctx) => {
8587
const content = await loadContent(ctx.params.id);
8688

8789
return (
@@ -102,8 +104,8 @@ An optimal route that only renders the content instead of the outer layout with
102104
the sidebar might look like this respectively.
103105

104106
```tsx routes/partials/docs/[id].tsx
105-
import { defineRoute, RouteConfig } from "$fresh/server.ts";
106-
import { Partial } from "$fresh/runtime.ts";
107+
import { define } from "../utils.ts";
108+
import { Partial } from "fresh/runtime";
107109

108110
// We only want to render the content, so disable
109111
// the `_app.tsx` template as well as any potentially
@@ -113,7 +115,7 @@ export const config: RouteConfig = {
113115
skipInheritedLayouts: true,
114116
};
115117

116-
export default defineRoute(async (req, ctx) => {
118+
export default define.page(async (ctx) => {
117119
const content = await loadContent(ctx.params.id);
118120

119121
// Only render the new content

0 commit comments

Comments
 (0)