Skip to content

Commit ba8d1a3

Browse files
authored
Merge branch 'main' into remove-finish-setup
2 parents fd8f4bd + 9605cd8 commit ba8d1a3

14 files changed

Lines changed: 343 additions & 129 deletions

File tree

docs/latest/getting-started/form-submissions.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,10 @@ export default function Page({ data }: PageProps<Data>) {
5353
return (
5454
<div>
5555
<form>
56-
<input type="text" name="q" value={query} />
57-
<button type="submit">Search</button>
56+
<input type="text" name="q" value={query} class="border p-1" />
57+
<button type="submit" class="ml-1 px-2 py-1 bg-gray-100 border">
58+
Search
59+
</button>
5860
</form>
5961
<ul>
6062
{results.map((name) => <li key={name}>{name}</li>)}

init/src/init.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,7 @@ ${GRADIENT_CSS}`;
403403
import { define, type State } from "./utils.ts";
404404
405405
export const app = new App<State>();
406+
406407
app.use(staticFiles());
407408
408409
// this is the same as the /api/:name route defined via a file. feel free to delete this!
@@ -421,7 +422,6 @@ const exampleLoggerMiddleware = define.middleware((ctx) => {
421422
app.use(exampleLoggerMiddleware);
422423
423424
await fsRoutes(app, {
424-
dir: "./",
425425
loadIsland: (path) => import(\`./islands/\${path}\`),
426426
loadRoute: (path) => import(\`./routes/\${path}\`),
427427
});

src/app.ts

Lines changed: 79 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,7 @@ import { DENO_DEPLOYMENT_ID } from "./runtime/build_id.ts";
66
import * as colors from "@std/fmt/colors";
77
import { type MiddlewareFn, runMiddlewares } from "./middlewares/mod.ts";
88
import { FreshReqContext } from "./context.ts";
9-
import {
10-
mergePaths,
11-
type Method,
12-
type Router,
13-
UrlPatternRouter,
14-
} from "./router.ts";
9+
import { type Method, type Router, UrlPatternRouter } from "./router.ts";
1510
import {
1611
type FreshConfig,
1712
normalizeConfig,
@@ -20,6 +15,7 @@ import {
2015
import { type BuildCache, ProdBuildCache } from "./build_cache.ts";
2116
import type { ServerIslandRegistry } from "./context.ts";
2217
import { HttpError, SetupError } from "./error.ts";
18+
import { mergePaths } from "./utils.ts";
2319

2420
// TODO: Completed type clashes in older Deno versions
2521
// deno-lint-ignore no-explicit-any
@@ -42,8 +38,80 @@ export type ListenOptions =
4238
& {
4339
remoteAddress?: string;
4440
};
41+
function createOnListen(
42+
basePath: string,
43+
options: ListenOptions,
44+
): (localAddr: Deno.NetAddr) => void {
45+
return (params) => {
46+
// Don't spam logs with this on live deployments
47+
if (DENO_DEPLOYMENT_ID) return;
48+
49+
const pathname = basePath + "/";
50+
const protocol = "key" in options && options.key && options.cert
51+
? "https:"
52+
: "http:";
53+
54+
let hostname = params.hostname;
55+
// Windows being windows...
56+
if (
57+
Deno.build.os === "windows" &&
58+
(hostname === "0.0.0.0" || hostname === "::")
59+
) {
60+
hostname = "localhost";
61+
}
62+
// Work around https://github.com/denoland/deno/issues/23650
63+
hostname = hostname.startsWith("::") ? `[${hostname}]` : hostname;
64+
65+
// deno-lint-ignore no-console
66+
console.log();
67+
// deno-lint-ignore no-console
68+
console.log(
69+
colors.bgRgb8(colors.rgb8(" 🍋 Fresh ready ", 0), 121),
70+
);
71+
const sep = options.remoteAddress ? "" : "\n";
72+
const space = options.remoteAddress ? " " : "";
73+
74+
const localLabel = colors.bold("Local:");
75+
const address = colors.cyan(
76+
`${protocol}//${hostname}:${params.port}${pathname}`,
77+
);
78+
// deno-lint-ignore no-console
79+
console.log(` ${localLabel} ${space}${address}${sep}`);
80+
if (options.remoteAddress) {
81+
const remoteLabel = colors.bold("Remote:");
82+
const remoteAddress = colors.cyan(options.remoteAddress);
83+
// deno-lint-ignore no-console
84+
console.log(` ${remoteLabel} ${remoteAddress}\n`);
85+
}
86+
};
87+
}
4588

46-
Deno.serve;
89+
async function listenOnFreePort(
90+
options: ListenOptions,
91+
handler: (
92+
request: Request,
93+
info?: Deno.ServeHandlerInfo,
94+
) => Promise<Response>,
95+
) {
96+
// No port specified, check for a free port. Instead of picking just
97+
// any port we'll check if the next one is free for UX reasons.
98+
// That way the user only needs to increment a number when running
99+
// multiple apps vs having to remember completely different ports.
100+
let firstError = null;
101+
for (let port = 8000; port < 8020; port++) {
102+
try {
103+
return await Deno.serve({ ...options, port }, handler);
104+
} catch (err) {
105+
if (err instanceof Deno.errors.AddrInUse) {
106+
// Throw first EADDRINUSE error if no port is free
107+
if (!firstError) firstError = err;
108+
continue;
109+
}
110+
throw err;
111+
}
112+
}
113+
throw firstError;
114+
}
47115

48116
export let getRouter: <State>(app: App<State>) => Router<MiddlewareFn<State>>;
49117
// deno-lint-ignore no-explicit-any
@@ -249,80 +317,15 @@ export class App<State> {
249317

250318
async listen(options: ListenOptions = {}): Promise<void> {
251319
if (!options.onListen) {
252-
options.onListen = (params) => {
253-
const pathname = (this.config.basePath) + "/";
254-
const protocol = "key" in options && options.key && options.cert
255-
? "https:"
256-
: "http:";
257-
258-
let hostname = params.hostname;
259-
// Windows being windows...
260-
if (
261-
Deno.build.os === "windows" &&
262-
(hostname === "0.0.0.0" || hostname === "::")
263-
) {
264-
hostname = "localhost";
265-
}
266-
// Work around https://github.com/denoland/deno/issues/23650
267-
hostname = hostname.startsWith("::") ? `[${hostname}]` : hostname;
268-
const address = colors.cyan(
269-
`${protocol}//${hostname}:${params.port}${pathname}`,
270-
);
271-
const localLabel = colors.bold("Local:");
272-
273-
// Don't spam logs with this on live deployments
274-
if (!DENO_DEPLOYMENT_ID) {
275-
// deno-lint-ignore no-console
276-
console.log();
277-
// deno-lint-ignore no-console
278-
console.log(
279-
colors.bgRgb8(colors.rgb8(" 🍋 Fresh ready ", 0), 121),
280-
);
281-
const sep = options.remoteAddress ? "" : "\n";
282-
const space = options.remoteAddress ? " " : "";
283-
// deno-lint-ignore no-console
284-
console.log(` ${localLabel} ${space}${address}${sep}`);
285-
if (options.remoteAddress) {
286-
const remoteLabel = colors.bold("Remote:");
287-
const remoteAddress = colors.cyan(options.remoteAddress);
288-
// deno-lint-ignore no-console
289-
console.log(` ${remoteLabel} ${remoteAddress}\n`);
290-
}
291-
}
292-
};
320+
options.onListen = createOnListen(this.config.basePath, options);
293321
}
294322

295323
const handler = await this.handler();
296324
if (options.port) {
297325
await Deno.serve(options, handler);
298-
} else {
299-
// No port specified, check for a free port. Instead of picking just
300-
// any port we'll check if the next one is free for UX reasons.
301-
// That way the user only needs to increment a number when running
302-
// multiple apps vs having to remember completely different ports.
303-
let firstError;
304-
for (let port = 8000; port < 8020; port++) {
305-
try {
306-
await Deno.serve({ ...options, port }, handler);
307-
firstError = undefined;
308-
break;
309-
} catch (err) {
310-
if (err instanceof Deno.errors.AddrInUse) {
311-
// Throw first EADDRINUSE error
312-
// if no port is free
313-
if (!firstError) {
314-
firstError = err;
315-
}
316-
continue;
317-
}
318-
319-
throw err;
320-
}
321-
}
322-
323-
if (firstError) {
324-
throw firstError;
325-
}
326+
return;
326327
}
328+
329+
await listenOnFreePort(options, handler);
327330
}
328331
}

src/config.ts

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,37 @@
11
import * as path from "@std/path";
22

33
export interface FreshConfig {
4+
/**
5+
* The root directory of the Fresh project.
6+
*
7+
* Other paths, such as `build.outDir`, `staticDir`, and `fsRoutes()`
8+
* are resolved relative to this directory.
9+
* @default Deno.cwd()
10+
*/
411
root?: string;
512
build?: {
613
/**
714
* The directory to write generated files to when `dev.ts build` is run.
15+
*
816
* This can be an absolute path, a file URL or a relative path.
17+
* Relative paths are resolved against the `root` option.
18+
* @default "_fresh"
919
*/
1020
outDir?: string;
1121
};
1222
/**
1323
* Serve fresh from a base path instead of from the root.
1424
* "/foo/bar" -> http://localhost:8000/foo/bar
15-
* @default {undefined}
25+
* @default undefined
1626
*/
1727
basePath?: string;
28+
/**
29+
* The directory to serve static files from.
30+
*
31+
* This can be an absolute path, a file URL or a relative path.
32+
* Relative paths are resolved against the `root` option.
33+
* @default "static"
34+
*/
1835
staticDir?: string;
1936
}
2037

@@ -35,35 +52,47 @@ export interface ResolvedFreshConfig {
3552
}
3653

3754
export function parseRootPath(root: string, cwd: string): string {
38-
if (root.startsWith("file://")) {
39-
root = path.fromFileUrl(root);
40-
} else if (!path.isAbsolute(root)) {
41-
root = path.join(cwd, root);
55+
return parseDirPath(root, cwd, true);
56+
}
57+
58+
function parseDirPath(
59+
dirPath: string,
60+
root: string,
61+
fileToDir = false,
62+
): string {
63+
if (dirPath.startsWith("file://")) {
64+
dirPath = path.fromFileUrl(dirPath);
65+
} else if (!path.isAbsolute(dirPath)) {
66+
dirPath = path.join(root, dirPath);
67+
}
68+
69+
if (fileToDir) {
70+
const ext = path.extname(dirPath);
71+
if (
72+
ext === ".ts" || ext === ".tsx" || ext === ".js" || ext === ".jsx" ||
73+
ext === ".mjs"
74+
) {
75+
dirPath = path.dirname(dirPath);
76+
}
4277
}
4378

44-
const ext = path.extname(root);
45-
if (
46-
ext === ".ts" || ext === ".tsx" || ext === ".js" || ext === ".jsx" ||
47-
ext === ".mjs"
48-
) {
49-
root = path.dirname(root);
79+
if (Deno.build.os === "windows") {
80+
dirPath = dirPath.replaceAll("\\", "/");
5081
}
5182

52-
return root;
83+
return dirPath;
5384
}
5485

5586
export function normalizeConfig(options: FreshConfig): ResolvedFreshConfig {
56-
const root = options.root
57-
? parseRootPath(options.root, Deno.cwd())
58-
: Deno.cwd();
87+
const root = parseRootPath(options.root ?? ".", Deno.cwd());
5988

6089
return {
6190
root,
6291
build: {
63-
outDir: options.build?.outDir ?? path.join(root, "_fresh"),
92+
outDir: parseDirPath(options.build?.outDir ?? "_fresh", root),
6493
},
6594
basePath: options.basePath ?? "",
66-
staticDir: options.staticDir ?? path.join(root, "static"),
95+
staticDir: parseDirPath(options.staticDir ?? "static", root),
6796
mode: "production",
6897
};
6998
}

0 commit comments

Comments
 (0)