@aklinker1/aframe 1.0.4 → 1.1.1
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/package.json +4 -4
- package/src/client/server.ts +20 -23
- package/src/config.ts +15 -2
- package/src/env.d.ts +5 -1
- package/src/index.ts +124 -64
- package/src/prerender.ts +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aklinker1/aframe",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"packageManager": "bun@1.3.
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"packageManager": "bun@1.3.5",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"exports": {
|
|
@@ -21,13 +21,13 @@
|
|
|
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",
|
|
25
25
|
"preview": "bun --cwd demo/.output --env-file ../.env server-entry.js",
|
|
26
26
|
"release": "bun run scripts/release.ts"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@aklinker1/check": "^1.4.5",
|
|
30
|
-
"@types/bun": "
|
|
30
|
+
"@types/bun": "^1.3.5",
|
|
31
31
|
"oxlint": "^0.15.11",
|
|
32
32
|
"prettier": "^3.5.2",
|
|
33
33
|
"publint": "^0.3.6",
|
package/src/client/server.ts
CHANGED
|
@@ -1,19 +1,10 @@
|
|
|
1
1
|
import type { BunFile } from "bun";
|
|
2
|
-
import {
|
|
3
|
-
import { join, extname, basename } from "node:path";
|
|
2
|
+
import { basename, extname, join } from "node:path";
|
|
4
3
|
|
|
5
4
|
export interface AframeServer {
|
|
6
5
|
listen(port: number): void | never;
|
|
7
6
|
}
|
|
8
7
|
|
|
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 {}
|
|
16
|
-
|
|
17
8
|
/**
|
|
18
9
|
* Fetches a file from the `public` directory.
|
|
19
10
|
*/
|
|
@@ -28,11 +19,8 @@ export function fetchStatic(options?: {
|
|
|
28
19
|
const path = new URL(request.url).pathname.replace(/\/+$/, "") || "/";
|
|
29
20
|
|
|
30
21
|
// Fetch file on disk
|
|
31
|
-
if (
|
|
32
|
-
const
|
|
33
|
-
const file = Bun.file(filePath);
|
|
34
|
-
const gzFile = Bun.file(filePath + ".gz");
|
|
35
|
-
|
|
22
|
+
if (aframe.static?.[path]) {
|
|
23
|
+
const { file, gzFile } = aframe.static[path];
|
|
36
24
|
const customResponse = await options?.onFetch?.(path, file);
|
|
37
25
|
if (customResponse) return customResponse;
|
|
38
26
|
|
|
@@ -72,13 +60,22 @@ export function fetchStatic(options?: {
|
|
|
72
60
|
}
|
|
73
61
|
|
|
74
62
|
// Fallback to public/index.html file
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
63
|
+
if (aframe.static?.["fallback"]) {
|
|
64
|
+
const { file, gzFile } = aframe.static["fallback"];
|
|
65
|
+
return createGzipResponse(file, gzFile);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const file = Bun.file(join(aframe.publicDir, "index.html"));
|
|
69
|
+
const gzFile = Bun.file(join(aframe.publicDir, "index.html.gz"));
|
|
70
|
+
return createGzipResponse(file, gzFile);
|
|
83
71
|
};
|
|
84
72
|
}
|
|
73
|
+
|
|
74
|
+
function createGzipResponse(file: BunFile, gzFile: BunFile): Response {
|
|
75
|
+
return new Response(gzFile.stream(), {
|
|
76
|
+
headers: {
|
|
77
|
+
"Content-Type": file.type,
|
|
78
|
+
"Content-Encoding": "gzip",
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { resolve, join, relative } from "node:path/posix";
|
|
1
|
+
import { join, relative, resolve } from "node:path/posix";
|
|
3
2
|
import type { LaunchOptions } from "puppeteer";
|
|
3
|
+
import * as vite from "vite";
|
|
4
4
|
|
|
5
5
|
export type AframeHooks = {
|
|
6
6
|
afterServerBuild?: (config: ResolvedConfig) => Promise<void> | void;
|
|
@@ -18,6 +18,11 @@ export type UserConfig = {
|
|
|
18
18
|
appPort?: number;
|
|
19
19
|
serverPort?: number;
|
|
20
20
|
hooks?: AframeHooks;
|
|
21
|
+
/**
|
|
22
|
+
* Compile the app into a single binary using Bun.
|
|
23
|
+
* @default true
|
|
24
|
+
*/
|
|
25
|
+
compile?: boolean;
|
|
21
26
|
};
|
|
22
27
|
|
|
23
28
|
export type PrerenderConfig = {
|
|
@@ -54,6 +59,9 @@ export type ResolvedConfig = {
|
|
|
54
59
|
prerenderedRoutes: string[];
|
|
55
60
|
prerender: PrerenderConfig | false;
|
|
56
61
|
hooks: AframeHooks | undefined;
|
|
62
|
+
serverEntryPath: string;
|
|
63
|
+
compileOutputPath: string;
|
|
64
|
+
compile: boolean;
|
|
57
65
|
};
|
|
58
66
|
|
|
59
67
|
export function defineConfig(config: UserConfig): UserConfig {
|
|
@@ -75,6 +83,8 @@ export async function resolveConfig(
|
|
|
75
83
|
const appOutDir = join(outDir, "public");
|
|
76
84
|
const serverOutDir = join(outDir, "server");
|
|
77
85
|
const prerenderedDir = join(outDir, "prerendered");
|
|
86
|
+
const serverEntryPath = join(outDir, "server-entry.ts");
|
|
87
|
+
const compileOutputPath = join(outDir, "server-entry");
|
|
78
88
|
|
|
79
89
|
const configFile = join(rootDir, "aframe.config"); // No file extension to resolve any JS/TS file
|
|
80
90
|
const relativeConfigFile = "./" + relative(import.meta.dir, configFile);
|
|
@@ -147,10 +157,13 @@ export async function resolveConfig(
|
|
|
147
157
|
appPort,
|
|
148
158
|
serverPort,
|
|
149
159
|
proxyPaths,
|
|
160
|
+
serverEntryPath,
|
|
161
|
+
compileOutputPath,
|
|
150
162
|
|
|
151
163
|
prerenderedRoutes: userConfig.prerenderedRoutes ?? ["/"],
|
|
152
164
|
vite: viteConfig,
|
|
153
165
|
prerender: userConfig.prerender ?? {},
|
|
154
166
|
hooks: userConfig.hooks,
|
|
167
|
+
compile: userConfig.compile ?? true,
|
|
155
168
|
};
|
|
156
169
|
}
|
package/src/env.d.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
import type { BunFile } from "bun";
|
|
1
2
|
import "vite/client";
|
|
2
3
|
|
|
3
4
|
declare global {
|
|
4
5
|
declare var aframe: {
|
|
5
6
|
command: "build" | "serve";
|
|
6
|
-
rootDir: string;
|
|
7
7
|
publicDir: string;
|
|
8
|
+
static?: Record<
|
|
9
|
+
string,
|
|
10
|
+
{ file: BunFile; gzFile: BunFile; cacheable: boolean }
|
|
11
|
+
>;
|
|
8
12
|
};
|
|
9
13
|
}
|
|
10
14
|
|
package/src/index.ts
CHANGED
|
@@ -6,13 +6,13 @@ import {
|
|
|
6
6
|
} from "node:fs";
|
|
7
7
|
import { cp, mkdir, readdir, rm, writeFile } from "node:fs/promises";
|
|
8
8
|
import { join, relative } from "node:path/posix";
|
|
9
|
+
import { pipeline } from "node:stream/promises";
|
|
10
|
+
import { createGzip } from "node:zlib";
|
|
9
11
|
import * as vite from "vite";
|
|
10
12
|
import { BLUE, BOLD, CYAN, DIM, GREEN, MAGENTA, RESET, YELLOW } from "./color";
|
|
11
13
|
import type { ResolvedConfig } from "./config";
|
|
12
|
-
import { createTimer } from "./timer";
|
|
13
14
|
import { prerenderPages, type PrerenderedRoute } from "./prerender";
|
|
14
|
-
import {
|
|
15
|
-
import { pipeline } from "node:stream/promises";
|
|
15
|
+
import { createTimer } from "./timer";
|
|
16
16
|
|
|
17
17
|
export * from "./config";
|
|
18
18
|
export * from "./dev-server";
|
|
@@ -46,31 +46,33 @@ export async function build(config: ResolvedConfig) {
|
|
|
46
46
|
(file) => !bundledAppFileSet.has(file),
|
|
47
47
|
);
|
|
48
48
|
|
|
49
|
-
await gzipFiles(
|
|
49
|
+
await gzipFiles(allAbsoluteAppFiles);
|
|
50
50
|
|
|
51
|
-
const
|
|
52
|
-
let staticRoutes = [
|
|
51
|
+
const staticRoutes: StaticRouteArray = [
|
|
53
52
|
...Array.from(bundledAppFiles)
|
|
54
53
|
.filter((path) => path !== "index.html")
|
|
55
|
-
.map((path) =>
|
|
54
|
+
.map((path) => ({
|
|
55
|
+
route: `/${path}`,
|
|
56
|
+
cacheable: true,
|
|
57
|
+
filePath: `public/${path}`,
|
|
58
|
+
gzPath: `public/${path}.gz`,
|
|
59
|
+
})),
|
|
56
60
|
...publicAppFiles
|
|
57
61
|
.filter((path) => path !== "index.html")
|
|
58
|
-
.
|
|
59
|
-
`/${path}`,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
.flatMap((path) => ({
|
|
63
|
+
route: `/${path}`,
|
|
64
|
+
cacheable: false,
|
|
65
|
+
filePath: `public/${path}`,
|
|
66
|
+
gzPath: `public/${path}.gz`,
|
|
67
|
+
})),
|
|
62
68
|
];
|
|
63
|
-
await writeFile(
|
|
64
|
-
staticRoutesFile,
|
|
65
|
-
JSON.stringify(Object.fromEntries(staticRoutes)),
|
|
66
|
-
);
|
|
67
69
|
|
|
68
70
|
console.log();
|
|
69
71
|
|
|
70
72
|
const serverTimer = createTimer();
|
|
71
|
-
console.log(`${BOLD}${CYAN}ℹ${RESET}
|
|
72
|
-
await buildServer(config);
|
|
73
|
-
console.log(`${GREEN}✔${RESET}
|
|
73
|
+
console.log(`${BOLD}${CYAN}ℹ${RESET} Preparing ${CYAN}./server${RESET}`);
|
|
74
|
+
await buildServer(config, staticRoutes);
|
|
75
|
+
console.log(`${GREEN}✔${RESET} Done in ${serverTimer()}`);
|
|
74
76
|
|
|
75
77
|
console.log();
|
|
76
78
|
|
|
@@ -89,25 +91,27 @@ export async function build(config: ResolvedConfig) {
|
|
|
89
91
|
console.log(`${DIM}${BOLD}→${RESET} Pre-render disabled`);
|
|
90
92
|
}
|
|
91
93
|
|
|
92
|
-
await gzipFiles(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
94
|
+
await gzipFiles(prerendered.map((entry) => entry.absolutePath));
|
|
95
|
+
|
|
96
|
+
staticRoutes.push({
|
|
97
|
+
route: "fallback",
|
|
98
|
+
cacheable: false,
|
|
99
|
+
filePath: "public/index.html",
|
|
100
|
+
gzPath: "public/index.html.gz",
|
|
101
|
+
});
|
|
102
|
+
for (const entry of prerendered) {
|
|
103
|
+
staticRoutes.push({
|
|
104
|
+
route: entry.route,
|
|
105
|
+
cacheable: false,
|
|
106
|
+
filePath: `prerendered/${entry.relativePath}`,
|
|
107
|
+
gzPath: `prerendered/${entry.relativePath}.gz`,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
await writeServerEntry(config, staticRoutes);
|
|
107
111
|
|
|
108
112
|
console.log();
|
|
109
113
|
|
|
110
|
-
console.log(`${GREEN}✔${RESET}
|
|
114
|
+
console.log(`${GREEN}✔${RESET} Bundled in ${buildTimer()}`);
|
|
111
115
|
const relativeOutDir = `${relative(config.rootDir, config.outDir)}/`;
|
|
112
116
|
const files = (
|
|
113
117
|
await listDirFiles(config.outDir, (path) => !path.includes("node_modules"))
|
|
@@ -126,22 +130,50 @@ export async function build(config: ResolvedConfig) {
|
|
|
126
130
|
files
|
|
127
131
|
.map(([file, size], i, array) => {
|
|
128
132
|
const boxChar = i === array.length - 1 ? "└" : "├";
|
|
129
|
-
return `
|
|
133
|
+
return ` ${DIM}${boxChar}─ ${relativeOutDir}${RESET}${getColor(file)}${file.padEnd(fileColumnCount)}${RESET} ${DIM}${prettyBytes(size)}${RESET}`;
|
|
130
134
|
})
|
|
131
135
|
.join("\n"),
|
|
132
136
|
);
|
|
133
137
|
console.log(`${CYAN}Σ Total size:${RESET} ${prettyBytes(totalSize)}`);
|
|
134
138
|
console.log();
|
|
135
139
|
|
|
136
|
-
|
|
137
|
-
|
|
140
|
+
if (config.compile) {
|
|
141
|
+
const compileTimer = createTimer();
|
|
142
|
+
console.log(`${BOLD}${CYAN}ℹ${RESET} Compiling single binary`);
|
|
138
143
|
|
|
139
|
-
|
|
144
|
+
await writeServerEntry(config, staticRoutes);
|
|
145
|
+
await Bun.build({
|
|
146
|
+
compile: {
|
|
147
|
+
outfile: config.compileOutputPath,
|
|
148
|
+
},
|
|
149
|
+
entrypoints: [config.serverEntryPath],
|
|
150
|
+
});
|
|
151
|
+
console.log(`${GREEN}✔${RESET} Compiled in ${compileTimer()}`);
|
|
152
|
+
console.log(
|
|
153
|
+
` ${DIM}└─${RESET} ${BLUE}${relative(process.cwd(), config.compileOutputPath)}${RESET} ${DIM}${prettyBytes(lstatSync(config.compileOutputPath).size)}${RESET}`,
|
|
154
|
+
);
|
|
155
|
+
console.log();
|
|
156
|
+
|
|
157
|
+
console.log(
|
|
158
|
+
`To preview production build, run:
|
|
159
|
+
|
|
160
|
+
${GREEN}./${relative(process.cwd(), config.compileOutputPath)}${RESET}
|
|
140
161
|
`,
|
|
141
|
-
|
|
162
|
+
);
|
|
163
|
+
} else {
|
|
164
|
+
console.log(
|
|
165
|
+
`To preview production build, run:
|
|
166
|
+
|
|
167
|
+
${GREEN}bun run ${relative(process.cwd(), config.serverEntryPath)}${RESET}
|
|
168
|
+
`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
142
171
|
}
|
|
143
172
|
|
|
144
|
-
async function buildServer(
|
|
173
|
+
async function buildServer(
|
|
174
|
+
config: ResolvedConfig,
|
|
175
|
+
staticRoutes: StaticRouteArray,
|
|
176
|
+
): Promise<void> {
|
|
145
177
|
const cpDirOptions: CopyOptions = {
|
|
146
178
|
recursive: true,
|
|
147
179
|
filter: (src) =>
|
|
@@ -172,7 +204,7 @@ async function buildServer(config: ResolvedConfig): Promise<void> {
|
|
|
172
204
|
const packageJson = await Bun.file(config.packageJsonPath)
|
|
173
205
|
.json()
|
|
174
206
|
.catch(() => ({}));
|
|
175
|
-
await
|
|
207
|
+
await writeFile(
|
|
176
208
|
join(config.outDir, "package.json"),
|
|
177
209
|
JSON.stringify(
|
|
178
210
|
{
|
|
@@ -185,23 +217,7 @@ async function buildServer(config: ResolvedConfig): Promise<void> {
|
|
|
185
217
|
),
|
|
186
218
|
);
|
|
187
219
|
|
|
188
|
-
await
|
|
189
|
-
join(config.outDir, "server-entry.ts"),
|
|
190
|
-
`import { resolve } from 'node:path';
|
|
191
|
-
|
|
192
|
-
globalThis.aframe = {
|
|
193
|
-
command: "build",
|
|
194
|
-
rootDir: import.meta.dir,
|
|
195
|
-
publicDir: resolve(import.meta.dir, "public"),
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
const { default: server } = await import("./server/main");
|
|
199
|
-
|
|
200
|
-
const port = Number(process.env.PORT) || 3000;
|
|
201
|
-
console.log(\`Server running @ http://localhost:\${port}\`);
|
|
202
|
-
server.listen(port);
|
|
203
|
-
`,
|
|
204
|
-
);
|
|
220
|
+
await writeServerEntry(config, staticRoutes);
|
|
205
221
|
|
|
206
222
|
const installProc = Bun.spawn(
|
|
207
223
|
["bun", "i", "--production", "--frozen-lockfile"],
|
|
@@ -217,6 +233,45 @@ server.listen(port);
|
|
|
217
233
|
await config.hooks?.afterServerBuild?.(config);
|
|
218
234
|
}
|
|
219
235
|
|
|
236
|
+
async function writeServerEntry(
|
|
237
|
+
config: ResolvedConfig,
|
|
238
|
+
staticRoutes: StaticRouteArray,
|
|
239
|
+
): Promise<void> {
|
|
240
|
+
const staticDef: string[] = [];
|
|
241
|
+
if (staticRoutes.length > 0) {
|
|
242
|
+
staticDef.push(" static: {");
|
|
243
|
+
staticRoutes.forEach((entry, i) => {
|
|
244
|
+
staticDef.push(
|
|
245
|
+
` "${entry.route}": { file: Bun.file(file${i}), cacheable: ${entry.cacheable}, gzFile: Bun.file(gzFile${i}) },`,
|
|
246
|
+
);
|
|
247
|
+
});
|
|
248
|
+
staticDef.push(" },");
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
await writeFile(
|
|
252
|
+
join(config.outDir, "server-entry.ts"),
|
|
253
|
+
`import { resolve } from 'node:path';
|
|
254
|
+
${staticRoutes
|
|
255
|
+
.flatMap(({ filePath }, i) => [
|
|
256
|
+
`import file${i} from "./${filePath}" with { type: "file" };`,
|
|
257
|
+
`import gzFile${i} from "./${filePath}.gz" with { type: "file" };`,
|
|
258
|
+
])
|
|
259
|
+
.join("\n")}
|
|
260
|
+
import server from "./server/main";
|
|
261
|
+
|
|
262
|
+
globalThis.aframe = {
|
|
263
|
+
command: "build",
|
|
264
|
+
publicDir: resolve(import.meta.dir, "public"),
|
|
265
|
+
${staticDef.join("\n")}
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const port = Number(process.env.PORT) || 3000;
|
|
269
|
+
console.log(\`Server running @ http://localhost:\${port}\`);
|
|
270
|
+
server.listen(port);
|
|
271
|
+
`,
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
220
275
|
function getColor(file: string) {
|
|
221
276
|
if (file.endsWith(".js")) return CYAN;
|
|
222
277
|
if (file.endsWith(".ts")) return CYAN;
|
|
@@ -238,14 +293,11 @@ function prettyBytes(bytes: number) {
|
|
|
238
293
|
return `${unit === "B" ? value : value.toFixed(2)} ${unit}`;
|
|
239
294
|
}
|
|
240
295
|
|
|
241
|
-
async function gzipFiles(
|
|
242
|
-
|
|
243
|
-
files: string[],
|
|
244
|
-
): Promise<void> {
|
|
245
|
-
for (const file of files) await gzipFile(config, file);
|
|
296
|
+
async function gzipFiles(files: string[]): Promise<void> {
|
|
297
|
+
for (const file of files) await gzipFile(file);
|
|
246
298
|
}
|
|
247
299
|
|
|
248
|
-
async function gzipFile(
|
|
300
|
+
async function gzipFile(file: string): Promise<void> {
|
|
249
301
|
await writeFile(`${file}.gz`, "");
|
|
250
302
|
await pipeline(
|
|
251
303
|
createReadStream(file),
|
|
@@ -276,3 +328,11 @@ async function listDirFiles(
|
|
|
276
328
|
|
|
277
329
|
return files;
|
|
278
330
|
}
|
|
331
|
+
|
|
332
|
+
type StaticRouteEntry = {
|
|
333
|
+
route: string;
|
|
334
|
+
filePath: string;
|
|
335
|
+
gzPath: string;
|
|
336
|
+
cacheable: boolean;
|
|
337
|
+
};
|
|
338
|
+
type StaticRouteArray = StaticRouteEntry[];
|
package/src/prerender.ts
CHANGED