@aklinker1/aframe 0.4.12 → 1.0.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.
package/README.md CHANGED
@@ -28,8 +28,8 @@ export default defineConfig({
28
28
  },
29
29
  // List of routes to pre-render.
30
30
  prerenderedRoutes: ["/"],
31
- // See https://github.com/Tofandel/prerenderer?tab=readme-ov-file#prerenderer-options
32
- prerenderer: {
31
+ // Configure how prerendering works, or set to `false` to disable
32
+ prerender: {
33
33
  // ...
34
34
  },
35
35
  });
@@ -141,5 +141,5 @@ const visible = !isPrerendering();
141
141
  ## Publish Update to NPM
142
142
 
143
143
  ```sh
144
- bun run release
144
+ bun run release patch
145
145
  ```
package/bin/aframe.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { build, createServer } from "../src";
2
2
  import { resolveConfig } from "../src/config";
3
- import { RESET, BOLD, DIM, UNDERLINE, GREEN, CYAN } from "../src/color";
3
+ import { RESET, BOLD, GREEN, CYAN } from "../src/color";
4
4
  import { createTimer } from "../src/timer";
5
5
 
6
6
  const [_bun, _aframe, ...args] = process.argv;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@aklinker1/aframe",
3
- "version": "0.4.12",
4
- "packageManager": "bun@1.2.5",
3
+ "version": "1.0.0",
4
+ "packageManager": "bun@1.3.2",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "exports": {
@@ -21,11 +21,10 @@
21
21
  "check": "check",
22
22
  "aframe": "bun --silent bin/aframe.ts",
23
23
  "dev": "bun aframe demo",
24
- "build": "bun aframe build demo",
24
+ "build": "bun aframe build demo && bun run demo/post-build.js",
25
25
  "preview": "bun --cwd demo/.output --env-file ../.env server-entry.js",
26
26
  "release": "bun run scripts/release.ts"
27
27
  },
28
- "dependencies": {},
29
28
  "devDependencies": {
30
29
  "@aklinker1/check": "^1.4.5",
31
30
  "@types/bun": "latest",
@@ -39,5 +38,10 @@
39
38
  "peerDependencies": {
40
39
  "vite": "*",
41
40
  "puppeteer": "*"
41
+ },
42
+ "peerDependenciesMeta": {
43
+ "puppeteer": {
44
+ "optional": true
45
+ }
42
46
  }
43
47
  }
@@ -1,15 +1,18 @@
1
1
  import type { BunFile } from "bun";
2
- import { resolve } from "node:path";
3
-
4
- const headers = {
5
- "Cache-Control": "max-age=31536000",
6
- };
2
+ import { readFileSync } from "node:fs";
3
+ import { join, extname, basename } from "node:path";
7
4
 
8
5
  export interface AframeServer {
9
6
  listen(port: number): void | never;
10
7
  }
11
8
 
12
- const publicDir = resolve(import.meta.dir, import.meta.publicDir);
9
+ const staticPathsFile = join(aframe.rootDir, "static.json");
10
+ const publicDir = aframe.publicDir;
11
+
12
+ let staticPaths: Record<string, { cacheable: boolean; path: string }> = {};
13
+ try {
14
+ staticPaths = JSON.parse(readFileSync(staticPathsFile, "utf-8"));
15
+ } catch {}
13
16
 
14
17
  /**
15
18
  * Fetches a file from the `public` directory.
@@ -22,62 +25,60 @@ export function fetchStatic(options?: {
22
25
  ) => Promise<Response | undefined> | Response | undefined;
23
26
  }): (request: Request) => Promise<Response> {
24
27
  return async (request) => {
25
- const path = new URL(request.url).pathname.replace(/\/+$/, "");
28
+ const path = new URL(request.url).pathname.replace(/\/+$/, "") || "/";
26
29
 
27
- const paths = [`${publicDir}${path}`, `${publicDir}${path}/index.html`];
30
+ // Fetch file on disk
31
+ if (staticPaths[path]) {
32
+ const filePath = join(aframe.rootDir, staticPaths[path].path);
33
+ const file = Bun.file(filePath);
34
+ const gzFile = Bun.file(filePath + ".gz");
28
35
 
29
- // Only fallback on the root HTML file when building application
30
- if (import.meta.command === "build") {
31
- paths.push(`${publicDir}/index.html`);
36
+ const customResponse = await options?.onFetch?.(path, file);
37
+ if (customResponse) return customResponse;
38
+
39
+ return new Response(gzFile.stream(), {
40
+ headers: {
41
+ "Content-Type": file.type,
42
+ "Content-Encoding": "gzip",
43
+ "Cache-Control": "max-age=31536000",
44
+ },
45
+ });
32
46
  }
33
47
 
34
- for (const path of paths) {
35
- const isHtml = path.includes(".html");
36
- const gzFile = Bun.file(path + ".gz");
37
- const file = Bun.file(path);
48
+ // If the path is asking for a file (e.g., it has an extension), return a
49
+ // 404 if it wasn't in the static list
50
+ const ext = extname(basename(path));
51
+ if (ext) {
52
+ return new Response(undefined, { status: 404 });
53
+ }
38
54
 
39
- if (await isFile(gzFile)) {
40
- const customResponse = await options?.onFetch?.(path, file);
41
- if (customResponse) return customResponse;
42
- return new Response(gzFile.stream(), {
55
+ // During development, render a fallback HTML page since Vite should handle
56
+ // all these routes before proxying the request to the server.
57
+ if (aframe.command === "serve") {
58
+ return new Response(
59
+ `<html>
60
+ <body>
61
+ This is a placeholder for your root <code>index.html</code> file during development.
62
+ <br/>
63
+ In production (or via the app's dev server), this path will fallback on the root <code>index.html</code>.
64
+ </body>
65
+ </html>`,
66
+ {
43
67
  headers: {
44
- ...(isHtml ? {} : headers),
45
- "content-type": file.type,
46
- "content-encoding": "gzip",
68
+ "Content-Type": "text/html",
47
69
  },
48
- });
49
- }
50
-
51
- if (await isFile(file)) {
52
- const customResponse = await options?.onFetch?.(path, file);
53
- if (customResponse) return customResponse;
54
-
55
- return new Response(file.stream(), { headers });
56
- }
70
+ },
71
+ );
57
72
  }
58
73
 
59
- return new Response(
60
- `<html>
61
- <body>
62
- This is a placeholder for your root <code>index.html</code> file during development.
63
- <br/>
64
- In production (or via the app's dev server), this path will fallback on the root <code>index.html</code>.
65
- </body>
66
- </html>`,
67
- {
68
- headers: {
69
- "Content-Type": "text/html",
70
- },
74
+ // Fallback to public/index.html file
75
+ const file = Bun.file(join(publicDir, "index.html"));
76
+ const gzFile = Bun.file(join(publicDir, "index.html.gz"));
77
+ return new Response(gzFile.stream(), {
78
+ headers: {
79
+ "Content-Type": file.type,
80
+ "Content-Encoding": "gzip",
71
81
  },
72
- );
82
+ });
73
83
  };
74
84
  }
75
-
76
- async function isFile(file: BunFile): Promise<boolean> {
77
- try {
78
- const stats = await file.stat();
79
- return stats.isFile();
80
- } catch {
81
- return false;
82
- }
83
- }
package/src/config.ts CHANGED
@@ -1,8 +1,11 @@
1
1
  import * as vite from "vite";
2
2
  import { resolve, join, relative } from "node:path/posix";
3
- import { mkdir } from "node:fs/promises";
4
3
  import type { LaunchOptions } from "puppeteer";
5
4
 
5
+ export type AframeHooks = {
6
+ afterServerBuild?: (config: ResolvedConfig) => Promise<void> | void;
7
+ };
8
+
6
9
  export type UserConfig = {
7
10
  vite?: vite.UserConfigExport;
8
11
  /**
@@ -11,12 +14,13 @@ export type UserConfig = {
11
14
  */
12
15
  proxyPaths?: string[];
13
16
  prerenderedRoutes?: string[];
14
- prerenderer?: PrerendererConfig | false;
17
+ prerender?: PrerenderConfig | false;
15
18
  appPort?: number;
16
19
  serverPort?: number;
20
+ hooks?: AframeHooks;
17
21
  };
18
22
 
19
- export type PrerendererConfig = {
23
+ export type PrerenderConfig = {
20
24
  /** Wait for an selector`document.querySelector` to be in the DOM before grabbing the HTML. */
21
25
  waitForSelector?: string;
22
26
  /** When `waitForSelector` is set, also wait for the element to be visible before grabbing the HTML. */
@@ -34,12 +38,12 @@ export type PrerendererConfig = {
34
38
 
35
39
  export type ResolvedConfig = {
36
40
  rootDir: string;
41
+ packageJsonPath: string;
37
42
  appDir: string;
38
43
  publicDir: string;
39
44
  serverDir: string;
40
45
  serverModule: string;
41
- serverEntry: string;
42
- prerenderToDir: string;
46
+ prerenderedDir: string;
43
47
  proxyPaths: string[];
44
48
  outDir: string;
45
49
  serverOutDir: string;
@@ -48,7 +52,8 @@ export type ResolvedConfig = {
48
52
  serverPort: number;
49
53
  vite: vite.InlineConfig;
50
54
  prerenderedRoutes: string[];
51
- prerenderer: PrerendererConfig | false;
55
+ prerender: PrerenderConfig | false;
56
+ hooks: AframeHooks | undefined;
52
57
  };
53
58
 
54
59
  export function defineConfig(config: UserConfig): UserConfig {
@@ -61,18 +66,15 @@ export async function resolveConfig(
61
66
  mode: string,
62
67
  ): Promise<ResolvedConfig> {
63
68
  const rootDir = root ? resolve(root) : process.cwd();
69
+ const packageJsonPath = join(rootDir, "package.json");
64
70
  const appDir = join(rootDir, "app");
65
71
  const serverDir = join(rootDir, "server");
66
72
  const serverModule = join(serverDir, "main.ts");
67
- const serverEntry = join(import.meta.dir, "server-entry.ts");
68
73
  const publicDir = join(rootDir, "public");
69
74
  const outDir = join(rootDir, ".output");
70
75
  const appOutDir = join(outDir, "public");
71
- const serverOutDir = outDir;
72
- const prerenderToDir = appOutDir;
73
-
74
- // Ensure required directories exist
75
- await mkdir(prerenderToDir, { recursive: true });
76
+ const serverOutDir = join(outDir, "server");
77
+ const prerenderedDir = join(outDir, "prerendered");
76
78
 
77
79
  const configFile = join(rootDir, "aframe.config"); // No file extension to resolve any JS/TS file
78
80
  const relativeConfigFile = "./" + relative(import.meta.dir, configFile);
@@ -120,6 +122,7 @@ export async function resolveConfig(
120
122
  publicDir,
121
123
  envDir: rootDir,
122
124
  build: {
125
+ emptyOutDir: false,
123
126
  outDir: appOutDir,
124
127
  },
125
128
  server: {
@@ -132,21 +135,22 @@ export async function resolveConfig(
132
135
 
133
136
  return {
134
137
  rootDir,
138
+ packageJsonPath,
135
139
  appDir,
136
140
  publicDir,
137
141
  serverDir,
138
142
  serverModule,
139
- serverEntry,
140
143
  outDir,
141
144
  serverOutDir,
142
145
  appOutDir,
143
- prerenderToDir,
146
+ prerenderedDir,
144
147
  appPort,
145
148
  serverPort,
146
149
  proxyPaths,
147
150
 
148
151
  prerenderedRoutes: userConfig.prerenderedRoutes ?? ["/"],
149
152
  vite: viteConfig,
150
- prerenderer: userConfig.prerenderer ?? {},
153
+ prerender: userConfig.prerender ?? {},
154
+ hooks: userConfig.hooks,
151
155
  };
152
156
  }
package/src/dev-server.ts CHANGED
@@ -11,21 +11,18 @@ export async function createServer(
11
11
 
12
12
  let serverProcess: Subprocess | undefined;
13
13
  const startServer = () => {
14
- const js = [
15
- `import server from '${config.serverModule}';`,
16
- `server.listen(${config.serverPort});`,
17
- ].join("\n");
14
+ const js = `globalThis.aframe = {
15
+ command: "serve",
16
+ rootDir: "${config.rootDir}",
17
+ publicDir: "${config.publicDir}",
18
+ };
19
+
20
+ const { default: server } = await import('${config.serverModule}');
21
+
22
+ server.listen(${config.serverPort});
23
+ `;
18
24
  return Bun.spawn({
19
- cmd: [
20
- "bun",
21
- "--watch",
22
- "--define",
23
- `import.meta.publicDir:"${config.publicDir}"`,
24
- "--define",
25
- `import.meta.command:"serve"`,
26
- "--eval",
27
- js,
28
- ],
25
+ cmd: ["bun", "--watch", "--eval", js],
29
26
  stdio: ["inherit", "inherit", "inherit"],
30
27
  cwd: config.rootDir,
31
28
  });
package/src/env.d.ts CHANGED
@@ -1,16 +1,11 @@
1
1
  import "vite/client";
2
2
 
3
3
  declare global {
4
- interface ImportMeta {
5
- /**
6
- * Absolute path or relative path (relative to main server file, not CWD).
7
- * This ensures the public directory path is constant regardless of the CWD.
8
- * It allows dev mode, production builds, and preview mode to all run from
9
- * any working directory.
10
- */
4
+ declare var aframe: {
5
+ command: "build" | "serve";
6
+ rootDir: string;
11
7
  publicDir: string;
12
- command: string;
13
- }
8
+ };
14
9
  }
15
10
 
16
11
  export {};
package/src/index.ts CHANGED
@@ -1,12 +1,13 @@
1
- import type { BunPlugin } from "bun";
2
- import { lstatSync } from "node:fs";
3
- import { mkdir, rm } from "node:fs/promises";
1
+ import { createReadStream, createWriteStream, lstatSync } from "node:fs";
2
+ import { cp, mkdir, readdir, rm, writeFile } from "node:fs/promises";
4
3
  import { join, relative } from "node:path/posix";
5
4
  import * as vite from "vite";
6
- import { BLUE, BOLD, CYAN, DIM, GREEN, MAGENTA, RESET } from "./color";
5
+ import { BLUE, BOLD, CYAN, DIM, GREEN, MAGENTA, RESET, YELLOW } from "./color";
7
6
  import type { ResolvedConfig } from "./config";
8
7
  import { createTimer } from "./timer";
9
- import { prerenderPages, type PrerenderedRoute } from "./prerenderer";
8
+ import { prerenderPages, type PrerenderedRoute } from "./prerender";
9
+ import { createGzip } from "node:zlib";
10
+ import { pipeline } from "node:stream/promises";
10
11
 
11
12
  export * from "./config";
12
13
  export * from "./dev-server";
@@ -22,36 +23,54 @@ export async function build(config: ResolvedConfig) {
22
23
  console.log(
23
24
  `${BOLD}${CYAN}ℹ${RESET} Building ${CYAN}./app${RESET} with ${GREEN}Vite ${vite.version}${RESET}`,
24
25
  );
25
- const { output: app } = (await vite.build(
26
- config.vite,
27
- )) as vite.Rollup.RollupOutput;
26
+ const appOutput = (await vite.build(config.vite)) as vite.Rollup.RollupOutput;
28
27
  console.log(`${GREEN}✔${RESET} Built in ${appTimer()}`);
29
28
 
29
+ const allAbsoluteAppFiles = (
30
+ await readdir(config.appOutDir, { recursive: true, withFileTypes: true })
31
+ )
32
+ .filter((entry) => entry.isFile())
33
+ .map((entry) => join(entry.parentPath, entry.name));
34
+ const allAppFiles = allAbsoluteAppFiles.map((path) =>
35
+ relative(config.appOutDir, path),
36
+ );
37
+
38
+ const bundledAppFiles = appOutput.output.map((entry) => entry.fileName);
39
+ const bundledAppFileSet = new Set(bundledAppFiles);
40
+ const publicAppFiles = allAppFiles.filter(
41
+ (file) => !bundledAppFileSet.has(file),
42
+ );
43
+
44
+ await gzipFiles(config, allAbsoluteAppFiles);
45
+
46
+ const staticRoutesFile = join(config.outDir, "static.json");
47
+ let staticRoutes = [
48
+ ...Array.from(bundledAppFiles)
49
+ .filter((path) => path !== "index.html")
50
+ .map((path) => [`/${path}`, { cacheable: true, path: `public/${path}` }]),
51
+ ...publicAppFiles
52
+ .filter((path) => path !== "index.html")
53
+ .map((path) => [
54
+ `/${path}`,
55
+ { cacheable: false, path: `public/${path}` },
56
+ ]),
57
+ ];
58
+ await writeFile(
59
+ staticRoutesFile,
60
+ JSON.stringify(Object.fromEntries(staticRoutes)),
61
+ );
62
+
30
63
  console.log();
31
64
 
32
65
  const serverTimer = createTimer();
33
- console.log(
34
- `${BOLD}${CYAN}ℹ${RESET} Building ${CYAN}./server${RESET} with ${MAGENTA}Bun ${Bun.version}${RESET}`,
35
- );
36
- const server = await Bun.build({
37
- outdir: config.serverOutDir,
38
- sourcemap: "external",
39
- entrypoints: [config.serverEntry],
40
- target: "bun",
41
- define: {
42
- // In production, the public directory is inside the CWD
43
- "import.meta.publicDir": `"public"`,
44
- "import.meta.command": `"build"`,
45
- },
46
- plugins: [aframeServerMainBunPlugin(config)],
47
- throw: true,
48
- });
66
+ console.log(`${BOLD}${CYAN}ℹ${RESET} Building ${CYAN}./server${RESET}`);
67
+ await buildServer(config);
49
68
  console.log(`${GREEN}✔${RESET} Built in ${serverTimer()}`);
50
69
 
51
70
  console.log();
52
71
 
53
72
  let prerendered: PrerenderedRoute[] = [];
54
- if (config.prerenderer !== false) {
73
+ if (config.prerender !== false) {
55
74
  const prerenderTimer = createTimer();
56
75
  console.log(
57
76
  `${BOLD}${CYAN}ℹ${RESET} Prerendering...\n` +
@@ -62,23 +81,37 @@ export async function build(config: ResolvedConfig) {
62
81
  prerendered = await prerenderPages(config);
63
82
  console.log(`${GREEN}✔${RESET} Prerendered in ${prerenderTimer()}`);
64
83
  } else {
65
- console.log(`${DIM}${BOLD}→${RESET} Pre-rendering disabled`);
84
+ console.log(`${DIM}${BOLD}→${RESET} Pre-render disabled`);
66
85
  }
67
86
 
87
+ await gzipFiles(
88
+ config,
89
+ prerendered.map((entry) => entry.absolutePath),
90
+ );
91
+
92
+ staticRoutes = staticRoutes.concat(
93
+ prerendered.map((entry) => [
94
+ entry.route,
95
+ { cacheable: false, path: `prerendered/${entry.relativePath}` },
96
+ ]),
97
+ );
98
+ await writeFile(
99
+ staticRoutesFile,
100
+ JSON.stringify(Object.fromEntries(staticRoutes)),
101
+ );
102
+
68
103
  console.log();
69
104
 
70
105
  console.log(`${GREEN}✔${RESET} Application built in ${buildTimer()}`);
71
106
  const relativeOutDir = `${relative(config.rootDir, config.outDir)}/`;
72
- const files = [
73
- ...server.outputs.map((output) => output.path),
74
- ...prerendered.map((output) => output.file),
75
- ...app
76
- .filter((output) => output.fileName !== "index.html")
77
- .map((output) => join(config.appOutDir, output.fileName)),
78
- ].map((file): [file: string, size: number] => [
79
- relative(config.outDir, file),
80
- lstatSync(file).size,
81
- ]);
107
+ const files = (
108
+ await listDirFiles(config.outDir, (path) => !path.includes("node_modules"))
109
+ )
110
+ .toSorted()
111
+ .map((file): [file: string, size: number] => [
112
+ relative(config.outDir, file),
113
+ lstatSync(file).size,
114
+ ]);
82
115
  const fileColumnCount = files.reduce(
83
116
  (max, [file]) => Math.max(file.length, max),
84
117
  0,
@@ -94,21 +127,79 @@ export async function build(config: ResolvedConfig) {
94
127
  );
95
128
  console.log(`${CYAN}Σ Total size:${RESET} ${prettyBytes(totalSize)}`);
96
129
  console.log();
130
+
131
+ console.log(
132
+ `To preview production build, run:
133
+
134
+ ${GREEN}bun run ${relative(process.cwd(), config.outDir)}/server-entry.ts${RESET}
135
+ `,
136
+ );
97
137
  }
98
138
 
99
- function aframeServerMainBunPlugin(config: ResolvedConfig): BunPlugin {
100
- return {
101
- name: "aframe:resolve-server-main",
102
- setup(bun) {
103
- bun.onResolve({ filter: /^aframe:server-main$/ }, () => ({
104
- path: config.serverModule,
105
- }));
139
+ async function buildServer(config: ResolvedConfig): Promise<void> {
140
+ await cp(config.serverDir, config.serverOutDir, {
141
+ recursive: true,
142
+ filter: (src) =>
143
+ !src.includes("__tests__") &&
144
+ !src.includes(".test.") &&
145
+ !src.includes(".spec."),
146
+ });
147
+ await Promise.all(
148
+ ["bun.lock", "bun.lockb", "tsconfig.json"].map((file) =>
149
+ cp(join(config.rootDir, file), join(config.outDir, file)).catch(() => {
150
+ // Ignore errors
151
+ }),
152
+ ),
153
+ );
154
+ const packageJson = await Bun.file(config.packageJsonPath)
155
+ .json()
156
+ .catch(() => ({}));
157
+ await Bun.write(
158
+ join(config.outDir, "package.json"),
159
+ JSON.stringify(
160
+ {
161
+ dependencies: packageJson.dependencies,
162
+ devDependencies: packageJson.devDependencies,
163
+ },
164
+ null,
165
+ 2,
166
+ ),
167
+ );
168
+ await Bun.write(
169
+ join(config.outDir, "server-entry.ts"),
170
+ `import { resolve } from 'node:path';
171
+
172
+ globalThis.aframe = {
173
+ command: "build",
174
+ rootDir: import.meta.dir,
175
+ publicDir: resolve(import.meta.dir, "public"),
176
+ };
177
+
178
+ const { default: server } = await import("./server/main");
179
+
180
+ const port = Number(process.env.PORT) || 3000;
181
+ console.log(\`Server running @ http://localhost:\${port}\`);
182
+ server.listen(port);
183
+ `,
184
+ );
185
+ const installProc = Bun.spawn(
186
+ ["bun", "i", "--production", "--frozen-lockfile"],
187
+ {
188
+ cwd: config.outDir,
106
189
  },
107
- };
190
+ );
191
+ const installStatus = await installProc.exited;
192
+ if (installStatus !== 0) {
193
+ throw new Error(`Failed to run "bun i --production" in ${config.outDir}`);
194
+ }
195
+
196
+ await config.hooks?.afterServerBuild?.(config);
108
197
  }
109
198
 
110
199
  function getColor(file: string) {
111
200
  if (file.endsWith(".js")) return CYAN;
201
+ if (file.endsWith(".ts")) return CYAN;
202
+ if (file.endsWith(".json")) return YELLOW;
112
203
  if (file.endsWith(".html")) return GREEN;
113
204
  if (file.endsWith(".css")) return MAGENTA;
114
205
  if (file.endsWith(".map")) return DIM;
@@ -125,3 +216,42 @@ function prettyBytes(bytes: number) {
125
216
  const value = bytes / Math.pow(base, exponent);
126
217
  return `${unit === "B" ? value : value.toFixed(2)} ${unit}`;
127
218
  }
219
+
220
+ async function gzipFiles(
221
+ config: ResolvedConfig,
222
+ files: string[],
223
+ ): Promise<void> {
224
+ for (const file of files) await gzipFile(config, file);
225
+ }
226
+
227
+ async function gzipFile(config: ResolvedConfig, file: string): Promise<void> {
228
+ await writeFile(`${file}.gz`, "");
229
+ await pipeline(
230
+ createReadStream(file),
231
+ createGzip(),
232
+ createWriteStream(`${file}.gz`),
233
+ );
234
+ }
235
+
236
+ async function listDirFiles(
237
+ dir: string,
238
+ filter: (path: string) => boolean,
239
+ ): Promise<string[]> {
240
+ const entries = await readdir(dir, { withFileTypes: true });
241
+ const files: string[] = [];
242
+
243
+ for (const entry of entries) {
244
+ const fullPath = join(entry.parentPath, entry.name);
245
+
246
+ if (!filter(fullPath)) continue;
247
+
248
+ if (entry.isFile()) {
249
+ files.push(fullPath);
250
+ } else if (entry.isDirectory()) {
251
+ const subFiles = await listDirFiles(fullPath, filter);
252
+ files.push(...subFiles);
253
+ }
254
+ }
255
+
256
+ return files;
257
+ }
@@ -1,18 +1,18 @@
1
- import {} from "node:url";
2
- import { join } from "node:path";
1
+ import { dirname, join } from "node:path";
3
2
  import { mkdir, writeFile } from "node:fs/promises";
4
3
  import type { Browser } from "puppeteer";
5
4
  import type { ResolvedConfig } from "./config";
6
5
 
7
6
  export type PrerenderedRoute = {
8
7
  route: string;
9
- file: string;
8
+ absolutePath: string;
9
+ relativePath: string;
10
10
  };
11
11
 
12
12
  export async function prerenderPages(
13
13
  config: ResolvedConfig,
14
14
  ): Promise<PrerenderedRoute[]> {
15
- if (config.prerenderer === false) return [];
15
+ if (config.prerender === false) return [];
16
16
 
17
17
  const puppeteer = await import("puppeteer");
18
18
  const {
@@ -21,10 +21,10 @@ export async function prerenderPages(
21
21
  waitForSelector,
22
22
  waitForSelectorVisible,
23
23
  waitForTimeout,
24
- } = config.prerenderer ?? {};
24
+ } = config.prerender ?? {};
25
25
 
26
26
  const server = Bun.spawn({
27
- cmd: ["bun", join(config.serverOutDir, "server-entry.js")],
27
+ cmd: ["bun", join(config.outDir, "server-entry.ts")],
28
28
  cwd: config.rootDir,
29
29
  stdio: ["inherit", "inherit", "inherit"],
30
30
  });
@@ -67,13 +67,15 @@ export async function prerenderPages(
67
67
  throw Error("Vite error prevented page from being rendered.");
68
68
  }
69
69
 
70
- const dir = join(config.appOutDir, route.substring(1));
71
- const file = join(dir, "index.html");
70
+ const relativePath = join(route.substring(1), "index.html");
71
+ const absolutePath = join(config.prerenderedDir, relativePath);
72
+ const dir = dirname(absolutePath);
72
73
  await mkdir(dir, { recursive: true });
73
- await writeFile(file, html);
74
+ await writeFile(absolutePath, html);
74
75
  results.push({
75
- file,
76
76
  route,
77
+ relativePath,
78
+ absolutePath,
77
79
  });
78
80
  }
79
81
  } finally {
@@ -1,6 +0,0 @@
1
- // This import is resolved by a Bun plugin pointing to your project's `server/main.ts` file.
2
- import server from "aframe:server-main";
3
-
4
- const port = Number(process.env.PORT) || 3000;
5
- console.log(`Server running @ http://localhost:${port}`);
6
- server.listen(port);
@@ -1,4 +0,0 @@
1
- declare module "aframe:server-main" {
2
- const server: import("./server").AframeServer;
3
- export default server;
4
- }