Skip to content
This repository was archived by the owner on Jul 6, 2025. It is now read-only.

Commit 5182707

Browse files
committed
Improve server boostrap
1 parent 9b8b4f7 commit 5182707

File tree

7 files changed

+123
-50
lines changed

7 files changed

+123
-50
lines changed

cli.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,13 @@ async function main() {
9191
runOptions.importMapFile = resolve("./import_map.json");
9292
Deno.env.set("ALEPH_DEV_ROOT", Deno.cwd());
9393
Deno.env.set("ALEPH_DEV_PORT", "2020");
94-
serveDir({ cwd: Deno.cwd(), port: 2020 });
95-
console.debug(dim("DEBUG"), `Proxy https://deno.land/x/aleph on http://localhost:2020`);
94+
serveDir({
95+
port: 2020,
96+
workingDir: Deno.cwd(),
97+
onListenSuccess: (port) => {
98+
console.debug(dim("DEBUG"), `Proxy https://deno.land/x/aleph on http://localhost:${port}`);
99+
},
100+
});
96101
} else {
97102
runOptions.denoConfigFile = await findFile(["deno.jsonc", "deno.json", "tsconfig.json"]);
98103
runOptions.importMapFile = await findFile(

commands/build.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ if (import.meta.main) {
2020
// serve app modules
2121
const importMap = await loadImportMap();
2222
const moduleLoaders = await initModuleLoaders(importMap);
23-
proxyModules(6060, { importMap, moduleLoaders });
23+
await proxyModules(6060, { importMap, moduleLoaders });
2424

2525
let serverEntry = await findFile(builtinModuleExts.map((ext) => `server.${ext}`));
2626
if (serverEntry) {

commands/dev.ts

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { basename, relative } from "https://deno.land/[email protected]/path/mod.ts";
2-
import { serve as stdServe, serveTls } from "https://deno.land/[email protected]/http/server.ts";
32
import mitt, { Emitter } from "https://esm.sh/[email protected]";
43
import { findFile, watchFs } from "../lib/fs.ts";
54
import { builtinModuleExts } from "../lib/helpers.ts";
65
import log, { blue } from "../lib/log.ts";
76
import util from "../lib/util.ts";
7+
import { serve as httpServe } from "../lib/serve.ts";
88
import { initModuleLoaders, loadImportMap } from "../server/config.ts";
99
import { serve } from "../server/mod.ts";
1010
import { initRoutes, toRouteRegExp } from "../server/routing.ts";
@@ -86,7 +86,7 @@ if (import.meta.main) {
8686
const cwd = Deno.cwd();
8787
const importMap = await loadImportMap();
8888
const moduleLoaders = await initModuleLoaders(importMap);
89-
proxyModules(6060, { importMap, moduleLoaders });
89+
await proxyModules(6060, { importMap, moduleLoaders });
9090

9191
log.info(`Watching files for changes...`);
9292
watchFs(cwd, (kind, path) => {
@@ -113,6 +113,12 @@ if (import.meta.main) {
113113
return false;
114114
}
115115
});
116+
serverDependencyGraph?.lookup(specifier, (specifier) => {
117+
if (e.all.has(`hotUpdate:${specifier}`)) {
118+
e.emit(`hotUpdate:${specifier}`, { specifier });
119+
return false;
120+
}
121+
});
116122
}
117123
});
118124
} else {
@@ -131,7 +137,10 @@ if (import.meta.main) {
131137

132138
let ac: AbortController | null = null;
133139
const bs = async () => {
134-
ac?.abort();
140+
if (ac) {
141+
ac.abort();
142+
log.info(`Restart server...`);
143+
}
135144
ac = new AbortController();
136145
await bootstrap(ac.signal, serverEntry);
137146
};
@@ -166,46 +175,61 @@ if (import.meta.main) {
166175
await bs();
167176
}
168177

169-
async function bootstrap(signal: AbortSignal, entry?: string): Promise<void> {
178+
async function bootstrap(signal: AbortSignal, entry: string | undefined, forcePort?: number): Promise<void> {
170179
// delete previous server handler
171180
if (Reflect.has(globalThis, "__ALEPH_SERVER")) {
172181
Reflect.deleteProperty(globalThis, "__ALEPH_SERVER");
173182
}
174183

175184
if (entry) {
185+
const entryName = basename(entry);
176186
await import(
177-
`http://localhost:${Deno.env.get("ALEPH_MODULES_PROXY_PORT")}/${basename(entry)}?t=${Date.now().toString(16)}`
187+
`http://localhost:${Deno.env.get("ALEPH_MODULES_PROXY_PORT")}/${entryName}?t=${Date.now().toString(16)}`
178188
);
179-
Deno.env.set("ALEPH_SERVER_ENTRY", basename(entry));
180-
log.info(`Bootstrap server from ${blue(basename(entry))}...`);
189+
if (Deno.env.get("ALEPH_SERVER_ENTRY") !== entryName) {
190+
Deno.env.set("ALEPH_SERVER_ENTRY", entryName);
191+
log.info(`Bootstrap server from ${blue(entryName)}...`);
192+
}
181193
}
182194
// make the default handler
183195
if (!Reflect.has(globalThis, "__ALEPH_SERVER")) {
184196
serve();
185197
}
186198

187199
const {
188-
port = 8080,
200+
port: userPort,
189201
hostname,
190202
certFile,
191203
keyFile,
192204
handler,
193205
} = Reflect.get(globalThis, "__ALEPH_SERVER") || {};
194-
const finalHandler = (req: Request) => {
195-
const { pathname } = new URL(req.url);
206+
const port = forcePort || userPort || 8080;
207+
208+
try {
209+
await httpServe({
210+
port,
211+
hostname,
212+
certFile,
213+
keyFile,
214+
signal,
215+
onListenSuccess: (port) => log.info(`Server ready on http://localhost:${port}`),
216+
handler: (req: Request) => {
217+
const { pathname } = new URL(req.url);
218+
219+
// handle HMR sockets
220+
if (pathname === "/-/hmr") {
221+
return handleHMRSocket(req);
222+
}
196223

197-
// handle HMR sockets
198-
if (pathname === "/-/hmr") {
199-
return handleHMRSocket(req);
224+
return handler?.(req);
225+
},
226+
});
227+
} catch (error) {
228+
if (error instanceof Deno.errors.AddrInUse) {
229+
log.warn(`Port ${port} is in use, try ${port + 1}...`);
230+
await bootstrap(signal, entry, port + 1);
231+
} else {
232+
throw error;
200233
}
201-
202-
return handler?.(req);
203-
};
204-
205-
log.info(`Server ready on http://localhost:${port}`);
206-
if (certFile && keyFile) {
207-
await serveTls(finalHandler, { port, hostname, certFile, keyFile, signal });
208-
} else {
209-
await stdServe(finalHandler, { port, hostname, signal });
210234
}
211235
}

commands/start.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ if (import.meta.main) {
2323
const ac = new AbortController();
2424
const importMap = await loadImportMap();
2525
const moduleLoaders = await initModuleLoaders(importMap);
26-
proxyModules(6060, { importMap, moduleLoaders, signal: ac.signal });
26+
await proxyModules(6060, { importMap, moduleLoaders, signal: ac.signal });
2727

2828
let serverEntry = await findFile(builtinModuleExts.map((ext) => `server.${ext}`));
2929
if (serverEntry) {

lib/serve.ts

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,64 @@
11
import { readableStreamFromReader } from "https://deno.land/[email protected]/streams/conversion.ts";
22
import { basename, join } from "https://deno.land/[email protected]/path/mod.ts";
3-
import { serve } from "https://deno.land/[email protected]/http/server.ts";
3+
import { Server, type ServerInit } from "https://deno.land/[email protected]/http/server.ts";
44
import { getContentType } from "./mime.ts";
55

6-
export type ServeDirOptions = {
7-
port: number;
8-
cwd?: string;
6+
export type ServeInit = ServerInit & {
7+
certFile?: string;
8+
keyFile?: string;
99
signal?: AbortSignal;
10+
onListenSuccess?: (port: number) => void;
11+
};
12+
13+
export async function serve(options: ServeInit) {
14+
const server = new Server(options);
15+
16+
let port: number;
17+
let listener: Deno.Listener;
18+
19+
if (options.certFile && options.keyFile) {
20+
port = options.port ?? 443;
21+
listener = Deno.listenTls({
22+
port,
23+
hostname: options.hostname ?? "0.0.0.0",
24+
certFile: options.certFile,
25+
keyFile: options.keyFile,
26+
transport: "tcp",
27+
// ALPN protocol support not yet stable.
28+
// alpnProtocols: ["h2", "http/1.1"],
29+
});
30+
} else {
31+
port = options.port ?? 80;
32+
listener = Deno.listen({
33+
port,
34+
hostname: options.hostname ?? "0.0.0.0",
35+
transport: "tcp",
36+
});
37+
}
38+
39+
options?.signal?.addEventListener("abort", () => server.close(), {
40+
once: true,
41+
});
42+
43+
options.onListenSuccess?.(port);
44+
45+
await server.serve(listener);
46+
}
47+
48+
export type ServeDirOptions = Omit<ServeInit, "handler"> & {
49+
workingDir?: string;
1050
loader?: (req: Request) => Promise<{ content: string | Uint8Array; contentType?: string } | null | undefined>;
1151
};
1252

1353
export async function serveDir(options: ServeDirOptions) {
14-
const cwd = options.cwd || Deno.cwd();
54+
const workingDir = options.workingDir || Deno.cwd();
1555
const handler = async (req: Request): Promise<Response> => {
1656
const url = new URL(req.url);
17-
const filepath = join(cwd, url.pathname);
57+
const filepath = join(workingDir, url.pathname);
1858
try {
1959
const stat = await Deno.lstat(filepath);
2060
if (stat.isDirectory) {
21-
const title = basename(cwd) + url.pathname;
61+
const title = basename(workingDir) + url.pathname;
2262
const items: string[] = [];
2363
for await (const item of Deno.readDir(filepath)) {
2464
if (!item.name.startsWith(".")) {
@@ -67,5 +107,6 @@ export async function serveDir(options: ServeDirOptions) {
67107
return new Response(err.message, { status: 500 });
68108
}
69109
};
70-
await serve(handler, { port: options.port, signal: options.signal });
110+
111+
await serve({ ...options, handler });
71112
}

server/proxy_modules.ts

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -120,27 +120,30 @@ type ProxyModulesOptions = {
120120
};
121121

122122
/** serve app modules to support module loader that allows you import Non-JavaScript modules like `.css/.vue/.svelet/...` */
123-
export async function proxyModules(port: number, options: ProxyModulesOptions) {
124-
if (!Reflect.has(globalThis, "serverDependencyGraph")) {
125-
Reflect.set(globalThis, "serverDependencyGraph", new DependencyGraph());
126-
}
127-
Deno.env.set("ALEPH_MODULES_PROXY_PORT", port.toString());
128-
log.info(`Proxy modules on http://localhost:${port}`);
129-
try {
130-
await serveDir({
123+
export function proxyModules(port: number, options: ProxyModulesOptions) {
124+
return new Promise<void>((resolve, reject) => {
125+
if (!Reflect.has(globalThis, "serverDependencyGraph")) {
126+
Reflect.set(globalThis, "serverDependencyGraph", new DependencyGraph());
127+
}
128+
serveDir({
131129
port,
132130
signal: options.signal,
133131
loader: buildLoader(options.moduleLoaders, {
134132
importMap: options.importMap,
135133
isDev: Deno.env.get("ALEPH_ENV") === "development",
136134
ssr: true,
137135
}),
136+
onListenSuccess: (port) => {
137+
Deno.env.set("ALEPH_MODULES_PROXY_PORT", port.toString());
138+
log.info(`Proxy modules on http://localhost:${port}`);
139+
resolve();
140+
},
141+
}).catch((err) => {
142+
if (err instanceof Deno.errors.AddrInUse) {
143+
proxyModules(port + 1, options).then(resolve).catch(reject);
144+
} else {
145+
reject(err);
146+
}
138147
});
139-
} catch (error) {
140-
if (error instanceof Deno.errors.AddrInUse) {
141-
proxyModules(port + 1, options);
142-
} else {
143-
throw error;
144-
}
145-
}
148+
});
146149
}

server/routing.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ async function generate(routes: Route[]) {
135135

136136
if (imports.length) {
137137
const code = [
138-
"/*! Generated by Aleph.js, do **NOT** change it and ensure it's **NOT** in the `.gitignore`. */",
138+
"/*! Generated by Aleph.js, do **NOT** change and ensure the file is **NOT** in the `.gitignore`. */",
139139
"",
140140
`import { revive } from "aleph/server";`,
141141
...imports,

0 commit comments

Comments
 (0)