@kaathewise/ssg 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ import type { Config } from "./config.ts";
2
+ export declare function build(config: Config): Promise<void>;
3
+ //# sourceMappingURL=build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAKzC,wBAAsB,KAAK,CAAC,MAAM,EAAE,MAAM,iBA4BzC"}
package/dist/build.js ADDED
@@ -0,0 +1,31 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ import { Glob, write } from "bun";
4
+ import { renderAll } from "./render.js";
5
+ const glob = new Glob("**/*.{js,jsx,ts,tsx}");
6
+ export async function build(config) {
7
+ const pages = [];
8
+ for await (const relPath of glob.scan(config.pagesDir)) {
9
+ const modulePath = path.join(config.pagesDir, relPath);
10
+ const p = await renderAll(modulePath, config.pagesDir);
11
+ pages.push(...p);
12
+ }
13
+ await fs.rm(config.outputDir, { recursive: true, force: true });
14
+ await fs.mkdir(config.outputDir);
15
+ for (const page of pages) {
16
+ const destPath = path.join(config.outputDir, page.path);
17
+ const dir = path.dirname(destPath);
18
+ if (!(await fs.exists(dir))) {
19
+ await fs.mkdir(path.dirname(destPath), {
20
+ recursive: true,
21
+ });
22
+ }
23
+ const file = Bun.file(destPath);
24
+ await write(file, page.src);
25
+ }
26
+ if (config.assetDir) {
27
+ await fs.cp(config.assetDir, config.outputDir, {
28
+ recursive: true,
29
+ });
30
+ }
31
+ }
@@ -0,0 +1,15 @@
1
+ export type Config = {
2
+ pagesDir: string;
3
+ sourceDir: string;
4
+ assetDir?: string;
5
+ outputDir: string;
6
+ port: number;
7
+ };
8
+ export declare function defineConfig(options: {
9
+ pagesDir: string;
10
+ sourceDir: string;
11
+ assetDir?: string;
12
+ outputDir?: string;
13
+ port?: number;
14
+ }): Config;
15
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,MAAM,GAAG;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;CACZ,CAAA;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE;IACrC,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,CAAA;CACb,UAaA"}
package/dist/config.js ADDED
@@ -0,0 +1,15 @@
1
+ import * as path from "node:path";
2
+ const OUTPUT_DIR = "dist/";
3
+ const DEFAULT_PORT = 3001;
4
+ export function defineConfig(options) {
5
+ const config = {
6
+ pagesDir: path.resolve(options.pagesDir),
7
+ sourceDir: path.resolve(options.sourceDir),
8
+ outputDir: path.resolve(options.outputDir ?? OUTPUT_DIR),
9
+ port: options.port ?? DEFAULT_PORT,
10
+ };
11
+ if (options.assetDir) {
12
+ config.assetDir = path.resolve(options.assetDir);
13
+ }
14
+ return config;
15
+ }
@@ -0,0 +1,4 @@
1
+ export { build } from "./build.ts";
2
+ export { type Config, defineConfig } from "./config.ts";
3
+ export { serve } from "./server.ts";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAClC,OAAO,EAAE,KAAK,MAAM,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AACvD,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { build } from "./build.js";
2
+ export { defineConfig } from "./config.js";
3
+ export { serve } from "./server.js";
@@ -0,0 +1,12 @@
1
+ interface Params {
2
+ [name: string]: string;
3
+ }
4
+ export type Page = {
5
+ path: string;
6
+ src: string;
7
+ contentType: string | null;
8
+ };
9
+ export declare function render(modulePath: string, pagesDir: string, params?: Params): Promise<Page>;
10
+ export declare function renderAll(modulePath: string, pagesDir: string): Promise<Page[]>;
11
+ export {};
12
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAEA,UAAU,MAAM;IACf,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;CACtB;AAED,MAAM,MAAM,IAAI,GAAG;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B,CAAA;AAED,wBAAsB,MAAM,CAC3B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,MAAM,GAAE,MAAW,GACjB,OAAO,CAAC,IAAI,CAAC,CAiCf;AAED,wBAAsB,SAAS,CAC9B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,EAAE,CAAC,CAgBjB"}
package/dist/render.js ADDED
@@ -0,0 +1,63 @@
1
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
+ });
6
+ }
7
+ return path;
8
+ };
9
+ import * as path from "node:path";
10
+ export async function render(modulePath, pagesDir, params = {}) {
11
+ const module = await import(__rewriteRelativeImportExtension(modulePath));
12
+ let contentType = null;
13
+ if ("getContentType" in module) {
14
+ contentType = module.getContentType();
15
+ }
16
+ const def = module.default;
17
+ let src;
18
+ switch (typeof def) {
19
+ case "string": {
20
+ src = def;
21
+ break;
22
+ }
23
+ case "function": {
24
+ src = await def(params);
25
+ break;
26
+ }
27
+ default: {
28
+ throw "Not implemented";
29
+ }
30
+ }
31
+ let pagePath = path.relative(pagesDir, modulePath);
32
+ pagePath = substituteParams(pagePath, params);
33
+ pagePath = pagePath.replace(/\.tsx?$/, "");
34
+ if (pagePath.endsWith("index")) {
35
+ pagePath += ".html";
36
+ }
37
+ return { path: pagePath, src, contentType };
38
+ }
39
+ export async function renderAll(modulePath, pagesDir) {
40
+ const module = await import(__rewriteRelativeImportExtension(modulePath));
41
+ const out = [];
42
+ if ("getStaticParams" in module) {
43
+ const paramsList = await module.getStaticParams();
44
+ for (const params of paramsList) {
45
+ out.push(await render(modulePath, pagesDir, params));
46
+ }
47
+ }
48
+ else {
49
+ out.push(await render(modulePath, pagesDir));
50
+ }
51
+ return out;
52
+ }
53
+ function substituteParams(inputPath, params) {
54
+ let path = inputPath;
55
+ for (const [key, value] of Object.entries(params)) {
56
+ const single = `[${key}]`;
57
+ const multiple = `[[...${key}]]`;
58
+ path = path.replace(single, value);
59
+ path = path.replace(multiple, value);
60
+ }
61
+ path = path.replace(/\.tsx?$/, "");
62
+ return path;
63
+ }
package/dist/run.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bun
2
+ export {};
3
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":""}
package/dist/run.js ADDED
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env bun
2
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
3
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
4
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
5
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
6
+ });
7
+ }
8
+ return path;
9
+ };
10
+ import * as fs from "node:fs/promises";
11
+ import * as path from "node:path";
12
+ import { build, serve } from "./index.js";
13
+ const CONFIG_PATH = "ssg.config.ts";
14
+ const HELP = `Usage: ssg <command>
15
+
16
+ Commands:
17
+ build: build the website
18
+ dev: start a hot-reloading development server
19
+ `;
20
+ const args = process.argv.slice(2);
21
+ const configPath = path.resolve(CONFIG_PATH);
22
+ if (!(await fs.exists(configPath))) {
23
+ console.warn("Configuration file not found");
24
+ process.exit(10);
25
+ }
26
+ const configModule = await import(__rewriteRelativeImportExtension(configPath));
27
+ const config = configModule.default;
28
+ switch (args[0]) {
29
+ case "build": {
30
+ await build(config);
31
+ break;
32
+ }
33
+ case "dev": {
34
+ await serve(config);
35
+ break;
36
+ }
37
+ case undefined: {
38
+ console.log(HELP);
39
+ break;
40
+ }
41
+ default: {
42
+ console.log(`The command must be either 'build' or 'dev', got ${args[0]}`);
43
+ process.exit(11);
44
+ }
45
+ }
@@ -0,0 +1,3 @@
1
+ import type { Config } from "./config.ts";
2
+ export declare function serve(config: Config): Promise<void>;
3
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAKzC,wBAAsB,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA+CzD"}
package/dist/server.js ADDED
@@ -0,0 +1,113 @@
1
+ import * as fs from "node:fs/promises";
2
+ import { watch } from "node:fs/promises";
3
+ import * as path from "node:path";
4
+ import { render } from "./render.js";
5
+ const EVENT_PATH = "/__ssg_dev_sse";
6
+ export async function serve(config) {
7
+ const clients = new Set();
8
+ const router = new Bun.FileSystemRouter({
9
+ style: "nextjs",
10
+ dir: config.pagesDir,
11
+ });
12
+ Bun.serve({
13
+ port: config.port,
14
+ // for SSE
15
+ idleTimeout: 0,
16
+ fetch: async (request) => {
17
+ const url = new URL(request.url);
18
+ if (url.pathname === EVENT_PATH) {
19
+ return createStream(request, clients);
20
+ }
21
+ const route = router.match(url.href);
22
+ if (route) {
23
+ return createHtml(config.pagesDir, route);
24
+ }
25
+ const asset = await fetchStaticFile(url, config.assetDir);
26
+ if (asset) {
27
+ return asset;
28
+ }
29
+ console.warn(`Path '${url.pathname}' not found`);
30
+ return new Response("Page or file not found", {
31
+ status: 404,
32
+ });
33
+ },
34
+ });
35
+ console.log(`Listening on :${config.port}`);
36
+ const watcher = watch(config.sourceDir, { recursive: true });
37
+ for await (const _ of watcher) {
38
+ router.reload();
39
+ clearCache(config.sourceDir);
40
+ for (const client of clients) {
41
+ client.enqueue("data: RELOAD\n\n");
42
+ }
43
+ }
44
+ }
45
+ function createStream(request, clients) {
46
+ const stream = new ReadableStream({
47
+ start(controller) {
48
+ clients.add(controller);
49
+ // workaround because @ts-expect-error doesn't fail for
50
+ // aspartik/website for some reason, breaking the check.
51
+ // biome-ignore lint/suspicious/noExplicitAny: above
52
+ const signal = request.signal;
53
+ signal.addEventListener("abort", () => {
54
+ controller.close();
55
+ clients.delete(controller);
56
+ });
57
+ },
58
+ });
59
+ return new Response(stream, {
60
+ headers: {
61
+ "Content-Type": "text/event-stream",
62
+ "Cache-Control": "no-cache",
63
+ },
64
+ });
65
+ }
66
+ async function fetchStaticFile(url, assetDir) {
67
+ if (!assetDir) {
68
+ return null;
69
+ }
70
+ let assetPath = path.join(assetDir, url.pathname.slice(1));
71
+ assetPath = path.resolve(assetPath);
72
+ if (!assetPath.startsWith(assetDir)) {
73
+ return new Response("Tried to get a file outside of the asset directory", { status: 403 });
74
+ }
75
+ const exists = await fs.exists(assetPath);
76
+ if (!exists) {
77
+ return null;
78
+ }
79
+ return new Response(Bun.file(assetPath));
80
+ }
81
+ const RELOAD_SCRIPT = `
82
+ <script type="module">
83
+ const sse = new EventSource("${EVENT_PATH}");
84
+ sse.onmessage = function(msg) {
85
+ if (msg.data === "RELOAD") {
86
+ location.reload()
87
+ }
88
+ }
89
+ window.addEventListener("beforeunload", () => sse.close())
90
+ </script>
91
+ `;
92
+ async function createHtml(pagesDir, route) {
93
+ const page = await render(route.filePath, pagesDir, route.params);
94
+ const response = new Response(page.src, {
95
+ headers: {
96
+ "Content-Type": page.contentType ?? "text/html",
97
+ },
98
+ });
99
+ return new HTMLRewriter()
100
+ .onDocument({
101
+ end: (el) => {
102
+ el.append(RELOAD_SCRIPT, { html: true });
103
+ },
104
+ })
105
+ .transform(response);
106
+ }
107
+ function clearCache(prefix) {
108
+ for (const path in import.meta.require.cache) {
109
+ if (path.startsWith(prefix)) {
110
+ delete import.meta.require.cache[path];
111
+ }
112
+ }
113
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kaathewise/ssg",
3
- "description": "Bun & JSX static site generator",
4
- "version": "0.5.2",
3
+ "description": "JSX static site generator",
4
+ "version": "0.6.0",
5
5
  "license": "MPL-2.0",
6
6
  "author": "Andrej Kolčin",
7
7
  "type": "module",
@@ -10,14 +10,15 @@
10
10
  "url": "git+https://github.com/kaathewisegit/ssg.git"
11
11
  },
12
12
  "scripts": {
13
- "check": "biome check && tsc --noEmit"
13
+ "check": "biome check && tsc --noEmit",
14
+ "build": "tsc --project tsconfig.build.json"
14
15
  },
15
- "main": "src/index.ts",
16
+ "main": "dist/index.ts",
16
17
  "bin": {
17
18
  "ssg": "src/run.ts"
18
19
  },
19
20
  "files": [
20
- "src"
21
+ "dist"
21
22
  ],
22
23
  "devDependencies": {
23
24
  "@biomejs/biome": "^2.2.3",
package/src/run.ts CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  import * as fs from "node:fs/promises"
4
4
  import * as path from "node:path"
5
- import { build, type Config, serve } from "./index"
5
+ import { build, type Config, serve } from "./index.ts"
6
6
 
7
7
  const CONFIG_PATH = "ssg.config.ts"
8
8
  const HELP = `Usage: ssg <command>
package/src/build.ts DELETED
@@ -1,37 +0,0 @@
1
- import * as fs from "node:fs/promises"
2
- import * as path from "node:path"
3
- import { Glob, write } from "bun"
4
- import type { Config } from "./config"
5
- import { type Page, renderAll } from "./render"
6
-
7
- const glob = new Glob("**/*.{js,jsx,ts,tsx}")
8
-
9
- export async function build(config: Config) {
10
- const pages: Page[] = []
11
- for await (const relPath of glob.scan(config.pagesDir)) {
12
- const modulePath = path.join(config.pagesDir, relPath)
13
- const p = await renderAll(modulePath, config.pagesDir)
14
- pages.push(...p)
15
- }
16
-
17
- await fs.rm(config.outputDir, { recursive: true, force: true })
18
- await fs.mkdir(config.outputDir)
19
-
20
- for (const page of pages) {
21
- const destPath = path.join(config.outputDir, page.path)
22
- const dir = path.dirname(destPath)
23
- if (!(await fs.exists(dir))) {
24
- await fs.mkdir(path.dirname(destPath), {
25
- recursive: true,
26
- })
27
- }
28
- const file = Bun.file(destPath)
29
- await write(file, page.src)
30
- }
31
-
32
- if (config.assetDir) {
33
- await fs.cp(config.assetDir, config.outputDir, {
34
- recursive: true,
35
- })
36
- }
37
- }
package/src/config.ts DELETED
@@ -1,33 +0,0 @@
1
- import * as path from "node:path"
2
-
3
- const OUTPUT_DIR = "dist/"
4
- const DEFAULT_PORT = 3001
5
-
6
- export type Config = {
7
- pagesDir: string
8
- sourceDir: string
9
- assetDir?: string
10
- outputDir: string
11
- port: number
12
- }
13
-
14
- export function defineConfig(options: {
15
- pagesDir: string
16
- sourceDir: string
17
- assetDir?: string
18
- outputDir?: string
19
- port?: number
20
- }) {
21
- const config = {
22
- pagesDir: path.resolve(options.pagesDir),
23
- sourceDir: path.resolve(options.sourceDir),
24
- outputDir: path.resolve(options.outputDir ?? OUTPUT_DIR),
25
- port: options.port ?? DEFAULT_PORT,
26
- } as Config
27
-
28
- if (options.assetDir) {
29
- config.assetDir = path.resolve(options.assetDir)
30
- }
31
-
32
- return config
33
- }
package/src/index.ts DELETED
@@ -1,3 +0,0 @@
1
- export { build } from "./build"
2
- export { type Config, defineConfig } from "./config"
3
- export { serve } from "./server"
package/src/render.ts DELETED
@@ -1,86 +0,0 @@
1
- import * as path from "node:path"
2
-
3
- interface Params {
4
- [name: string]: string
5
- }
6
-
7
- export type Page = {
8
- path: string
9
- src: string
10
- contentType: string | null
11
- }
12
-
13
- export async function render(
14
- modulePath: string,
15
- pagesDir: string,
16
- params: Params = {},
17
- ): Promise<Page> {
18
- const module = await import(modulePath)
19
-
20
- let contentType = null
21
- if ("getContentType" in module) {
22
- contentType = module.getContentType()
23
- }
24
-
25
- const def = module.default
26
-
27
- let src: string
28
- switch (typeof def) {
29
- case "string": {
30
- src = def
31
- break
32
- }
33
- case "function": {
34
- src = await def(params)
35
- break
36
- }
37
- default: {
38
- throw "Not implemented"
39
- }
40
- }
41
-
42
- let pagePath = path.relative(pagesDir, modulePath)
43
- pagePath = substituteParams(pagePath, params)
44
- pagePath = pagePath.replace(/\.tsx?$/, "")
45
- if (pagePath.endsWith("index")) {
46
- pagePath += ".html"
47
- }
48
-
49
- return { path: pagePath, src, contentType }
50
- }
51
-
52
- export async function renderAll(
53
- modulePath: string,
54
- pagesDir: string,
55
- ): Promise<Page[]> {
56
- const module = await import(modulePath)
57
-
58
- const out: Page[] = []
59
-
60
- if ("getStaticParams" in module) {
61
- const paramsList: Params[] = await module.getStaticParams()
62
-
63
- for (const params of paramsList) {
64
- out.push(await render(modulePath, pagesDir, params))
65
- }
66
- } else {
67
- out.push(await render(modulePath, pagesDir))
68
- }
69
-
70
- return out
71
- }
72
-
73
- function substituteParams(inputPath: string, params: Params): string {
74
- let path = inputPath
75
- for (const [key, value] of Object.entries(params)) {
76
- const single = `[${key}]`
77
- const multiple = `[[...${key}]]`
78
-
79
- path = path.replace(single, value)
80
- path = path.replace(multiple, value)
81
- }
82
-
83
- path = path.replace(/\.tsx?$/, "")
84
-
85
- return path
86
- }
package/src/server.ts DELETED
@@ -1,151 +0,0 @@
1
- import * as fs from "node:fs/promises"
2
- import { watch } from "node:fs/promises"
3
- import * as path from "node:path"
4
- import type { ReadableStreamDefaultController } from "node:stream/web"
5
- import type { MatchedRoute } from "bun"
6
- import type { Config } from "./config"
7
- import { render } from "./render"
8
-
9
- const EVENT_PATH = "/__ssg_dev_sse"
10
-
11
- export async function serve(config: Config): Promise<void> {
12
- const clients = new Set<ReadableStreamDefaultController>()
13
- const router = new Bun.FileSystemRouter({
14
- style: "nextjs",
15
- dir: config.pagesDir,
16
- })
17
-
18
- Bun.serve({
19
- port: config.port,
20
- // for SSE
21
- idleTimeout: 0,
22
-
23
- fetch: async request => {
24
- const url = new URL(request.url)
25
- if (url.pathname === EVENT_PATH) {
26
- return createStream(request, clients)
27
- }
28
-
29
- const route = router.match(url.href)
30
- if (route) {
31
- return createHtml(config.pagesDir, route)
32
- }
33
-
34
- const asset = await fetchStaticFile(
35
- url,
36
- config.assetDir,
37
- )
38
- if (asset) {
39
- return asset
40
- }
41
-
42
- console.warn(`Path '${url.pathname}' not found`)
43
- return new Response("Page or file not found", {
44
- status: 404,
45
- })
46
- },
47
- })
48
- console.log(`Listening on :${config.port}`)
49
-
50
- const watcher = watch(config.sourceDir, { recursive: true })
51
- for await (const _ of watcher) {
52
- router.reload()
53
- clearCache(config.sourceDir)
54
- for (const client of clients) {
55
- client.enqueue("data: RELOAD\n\n")
56
- }
57
- }
58
- }
59
-
60
- function createStream(
61
- request: Request,
62
- clients: Set<ReadableStreamDefaultController>,
63
- ) {
64
- const stream = new ReadableStream({
65
- start(controller): void {
66
- clients.add(controller)
67
-
68
- // workaround because @ts-expect-error doesn't fail for
69
- // aspartik/website for some reason, breaking the check.
70
- // biome-ignore lint/suspicious/noExplicitAny: above
71
- const signal = request.signal as any
72
- signal.addEventListener("abort", () => {
73
- controller.close()
74
- clients.delete(controller)
75
- })
76
- },
77
- })
78
-
79
- return new Response(stream, {
80
- headers: {
81
- "Content-Type": "text/event-stream",
82
- "Cache-Control": "no-cache",
83
- },
84
- })
85
- }
86
-
87
- async function fetchStaticFile(
88
- url: URL,
89
- assetDir?: string,
90
- ): Promise<Response | null> {
91
- if (!assetDir) {
92
- return null
93
- }
94
-
95
- let assetPath = path.join(assetDir, url.pathname.slice(1))
96
- assetPath = path.resolve(assetPath)
97
- if (!assetPath.startsWith(assetDir)) {
98
- return new Response(
99
- "Tried to get a file outside of the asset directory",
100
- { status: 403 },
101
- )
102
- }
103
-
104
- const exists = await fs.exists(assetPath)
105
- if (!exists) {
106
- return null
107
- }
108
-
109
- return new Response(Bun.file(assetPath))
110
- }
111
-
112
- const RELOAD_SCRIPT = `
113
- <script type="module">
114
- const sse = new EventSource("${EVENT_PATH}");
115
- sse.onmessage = function(msg) {
116
- if (msg.data === "RELOAD") {
117
- location.reload()
118
- }
119
- }
120
- window.addEventListener("beforeunload", () => sse.close())
121
- </script>
122
- `
123
-
124
- async function createHtml(
125
- pagesDir: string,
126
- route: MatchedRoute,
127
- ): Promise<Response> {
128
- const page = await render(route.filePath, pagesDir, route.params)
129
-
130
- const response = new Response(page.src, {
131
- headers: {
132
- "Content-Type": page.contentType ?? "text/html",
133
- },
134
- })
135
-
136
- return new HTMLRewriter()
137
- .onDocument({
138
- end: (el): void => {
139
- el.append(RELOAD_SCRIPT, { html: true })
140
- },
141
- })
142
- .transform(response)
143
- }
144
-
145
- function clearCache(prefix: string) {
146
- for (const path in import.meta.require.cache) {
147
- if (path.startsWith(prefix)) {
148
- delete import.meta.require.cache[path]
149
- }
150
- }
151
- }