@aklinker1/aframe 1.0.4 → 1.1.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/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 +125 -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.0
|
|
4
|
-
"packageManager": "bun@1.3.
|
|
3
|
+
"version": "1.1.0",
|
|
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,51 @@ 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
|
+
bytecode: true,
|
|
151
|
+
});
|
|
152
|
+
console.log(`${GREEN}✔${RESET} Compiled in ${compileTimer()}`);
|
|
153
|
+
console.log(
|
|
154
|
+
` ${DIM}└─${RESET} ${BLUE}${relative(process.cwd(), config.compileOutputPath)}${RESET} ${DIM}${prettyBytes(lstatSync(config.compileOutputPath).size)}${RESET}`,
|
|
155
|
+
);
|
|
156
|
+
console.log();
|
|
157
|
+
|
|
158
|
+
console.log(
|
|
159
|
+
`To preview production build, run:
|
|
160
|
+
|
|
161
|
+
${GREEN}./${relative(process.cwd(), config.compileOutputPath)}${RESET}
|
|
140
162
|
`,
|
|
141
|
-
|
|
163
|
+
);
|
|
164
|
+
} else {
|
|
165
|
+
console.log(
|
|
166
|
+
`To preview production build, run:
|
|
167
|
+
|
|
168
|
+
${GREEN}bun run ${relative(process.cwd(), config.serverEntryPath)}${RESET}
|
|
169
|
+
`,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
142
172
|
}
|
|
143
173
|
|
|
144
|
-
async function buildServer(
|
|
174
|
+
async function buildServer(
|
|
175
|
+
config: ResolvedConfig,
|
|
176
|
+
staticRoutes: StaticRouteArray,
|
|
177
|
+
): Promise<void> {
|
|
145
178
|
const cpDirOptions: CopyOptions = {
|
|
146
179
|
recursive: true,
|
|
147
180
|
filter: (src) =>
|
|
@@ -172,7 +205,7 @@ async function buildServer(config: ResolvedConfig): Promise<void> {
|
|
|
172
205
|
const packageJson = await Bun.file(config.packageJsonPath)
|
|
173
206
|
.json()
|
|
174
207
|
.catch(() => ({}));
|
|
175
|
-
await
|
|
208
|
+
await writeFile(
|
|
176
209
|
join(config.outDir, "package.json"),
|
|
177
210
|
JSON.stringify(
|
|
178
211
|
{
|
|
@@ -185,23 +218,7 @@ async function buildServer(config: ResolvedConfig): Promise<void> {
|
|
|
185
218
|
),
|
|
186
219
|
);
|
|
187
220
|
|
|
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
|
-
);
|
|
221
|
+
await writeServerEntry(config, staticRoutes);
|
|
205
222
|
|
|
206
223
|
const installProc = Bun.spawn(
|
|
207
224
|
["bun", "i", "--production", "--frozen-lockfile"],
|
|
@@ -217,6 +234,45 @@ server.listen(port);
|
|
|
217
234
|
await config.hooks?.afterServerBuild?.(config);
|
|
218
235
|
}
|
|
219
236
|
|
|
237
|
+
async function writeServerEntry(
|
|
238
|
+
config: ResolvedConfig,
|
|
239
|
+
staticRoutes: StaticRouteArray,
|
|
240
|
+
): Promise<void> {
|
|
241
|
+
const staticDef: string[] = [];
|
|
242
|
+
if (staticRoutes.length > 0) {
|
|
243
|
+
staticDef.push(" static: {");
|
|
244
|
+
staticRoutes.forEach((entry, i) => {
|
|
245
|
+
staticDef.push(
|
|
246
|
+
` "${entry.route}": { file: Bun.file(file${i}), cacheable: ${entry.cacheable}, gzFile: Bun.file(gzFile${i}) },`,
|
|
247
|
+
);
|
|
248
|
+
});
|
|
249
|
+
staticDef.push(" },");
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
await writeFile(
|
|
253
|
+
join(config.outDir, "server-entry.ts"),
|
|
254
|
+
`import { resolve } from 'node:path';
|
|
255
|
+
${staticRoutes
|
|
256
|
+
.flatMap(({ filePath }, i) => [
|
|
257
|
+
`import file${i} from "./${filePath}" with { type: "file" };`,
|
|
258
|
+
`import gzFile${i} from "./${filePath}.gz" with { type: "file" };`,
|
|
259
|
+
])
|
|
260
|
+
.join("\n")}
|
|
261
|
+
import server from "./server/main";
|
|
262
|
+
|
|
263
|
+
globalThis.aframe = {
|
|
264
|
+
command: "build",
|
|
265
|
+
publicDir: resolve(import.meta.dir, "public"),
|
|
266
|
+
${staticDef.join("\n")}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const port = Number(process.env.PORT) || 3000;
|
|
270
|
+
console.log(\`Server running @ http://localhost:\${port}\`);
|
|
271
|
+
server.listen(port);
|
|
272
|
+
`,
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
220
276
|
function getColor(file: string) {
|
|
221
277
|
if (file.endsWith(".js")) return CYAN;
|
|
222
278
|
if (file.endsWith(".ts")) return CYAN;
|
|
@@ -238,14 +294,11 @@ function prettyBytes(bytes: number) {
|
|
|
238
294
|
return `${unit === "B" ? value : value.toFixed(2)} ${unit}`;
|
|
239
295
|
}
|
|
240
296
|
|
|
241
|
-
async function gzipFiles(
|
|
242
|
-
|
|
243
|
-
files: string[],
|
|
244
|
-
): Promise<void> {
|
|
245
|
-
for (const file of files) await gzipFile(config, file);
|
|
297
|
+
async function gzipFiles(files: string[]): Promise<void> {
|
|
298
|
+
for (const file of files) await gzipFile(file);
|
|
246
299
|
}
|
|
247
300
|
|
|
248
|
-
async function gzipFile(
|
|
301
|
+
async function gzipFile(file: string): Promise<void> {
|
|
249
302
|
await writeFile(`${file}.gz`, "");
|
|
250
303
|
await pipeline(
|
|
251
304
|
createReadStream(file),
|
|
@@ -276,3 +329,11 @@ async function listDirFiles(
|
|
|
276
329
|
|
|
277
330
|
return files;
|
|
278
331
|
}
|
|
332
|
+
|
|
333
|
+
type StaticRouteEntry = {
|
|
334
|
+
route: string;
|
|
335
|
+
filePath: string;
|
|
336
|
+
gzPath: string;
|
|
337
|
+
cacheable: boolean;
|
|
338
|
+
};
|
|
339
|
+
type StaticRouteArray = StaticRouteEntry[];
|
package/src/prerender.ts
CHANGED