@aklinker1/aframe 0.1.0 → 0.2.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
@@ -8,14 +8,13 @@ Simple wrapper around Vite for creating pre-rendered, client-side web apps with
8
8
  ```html
9
9
  📂 {rootDir}/
10
10
  📁 app/
11
- 📄 .env
12
11
  📄 index.html
13
12
  📄 main.ts
14
13
  📁 public/
15
14
  📄 favicon.ico
16
15
  📁 server/
17
- 📄 .env
18
16
  📄 main.ts
17
+ 📄 .env
19
18
  📄 aframe.config.ts
20
19
  ```
21
20
 
@@ -72,7 +71,7 @@ export default app;
72
71
  "scripts": {
73
72
  "dev": "aframe",
74
73
  "build": "aframe build",
75
- "preview": "bun --cwd .output server-entry.js"
74
+ "preview": "bun --cwd .output --env-file ../.env server-entry.js"
76
75
  },
77
76
  "dependencies": {
78
77
  "@aklinker1/aframe": "@latest",
@@ -83,3 +82,27 @@ export default app;
83
82
  }
84
83
  }
85
84
  ```
85
+
86
+ ## Usage
87
+
88
+ ### Environment
89
+
90
+ Create a single `.env` file in your project root. `app` secrets must be prefixed with `APP_`, whereas `server` secrets don't need any prefix:
91
+
92
+ ```sh
93
+ # {rootDir}/.env
94
+ APP_DEFAULT_MODEL=gemini-2.0-flash
95
+ OPENAI_API_KEY=...
96
+ ```
97
+
98
+ In this case, you could use `import.meta.env.VITE_DEFAULT_MODEL` in your `app` code and `process.env.OPENAI_API_KEY` in your server code.
99
+
100
+ ### Import files as text
101
+
102
+ When importing a file as text, like an HTML template or a `.gql` schema, you should use `with { type: "text"
103
+ }`:
104
+
105
+ ```ts
106
+ // server/main.ts
107
+ import welcomeEmailTemplate from './assets/email-templates/welcome.html' with { type: "text" };
108
+ ```
package/bin/aframe.ts CHANGED
@@ -1,8 +1,9 @@
1
- import { build, createServer, resolveConfig } from "../src/mod";
1
+ import { build, createServer } from "../src";
2
+ import { resolveConfig } from "../src/config";
2
3
  import { RESET, BOLD, DIM, UNDERLINE, GREEN, CYAN } from "../src/color";
3
4
  import { createTimer } from "../src/timer";
4
5
 
5
- const [_bun, _aframe, ...args] = Bun.argv;
6
+ const [_bun, _aframe, ...args] = process.argv;
6
7
 
7
8
  async function dev(root?: string) {
8
9
  const devServerTimer = createTimer();
@@ -26,8 +27,8 @@ async function dev(root?: string) {
26
27
  "--eval",
27
28
  js,
28
29
  ],
29
- // stdio: ["ignore", "inherit", "inherit"],
30
- cwd: config.serverDir,
30
+ stdio: ["inherit", "inherit", "inherit"],
31
+ cwd: config.rootDir,
31
32
  });
32
33
  });
33
34
 
@@ -44,7 +45,7 @@ async function dev(root?: string) {
44
45
  }
45
46
 
46
47
  async function help() {
47
- console.log("Help");
48
+ console.log("Help: TODO");
48
49
  }
49
50
 
50
51
  if (args[0] === "build") {
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@aklinker1/aframe",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "packageManager": "bun@1.2.2",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "exports": {
8
- ".": "./src/mod.ts",
8
+ ".": "./src/index.ts",
9
9
  "./server": "./src/server.ts",
10
10
  "./env": "./src/env.d.ts"
11
11
  },
@@ -18,9 +18,9 @@
18
18
  ],
19
19
  "scripts": {
20
20
  "check": "check",
21
- "dev": "bun bin/aframe.ts demo",
22
- "build": "bun bin/aframe.ts build demo",
23
- "preview": "bun --cwd demo/.output server-entry.js"
21
+ "dev": "bun node_modules/@aklinker1/aframe/bin/aframe.ts demo",
22
+ "build": "bun node_modules/@aklinker1/aframe/bin/aframe.ts build demo",
23
+ "preview": "bun --cwd demo/.output --env-file ../.env server-entry.js"
24
24
  },
25
25
  "dependencies": {},
26
26
  "devDependencies": {
package/src/config.ts ADDED
@@ -0,0 +1,150 @@
1
+ import * as vite from "vite";
2
+ import type { PrerendererOptions } from "@prerenderer/prerenderer";
3
+ import { resolve, join, relative } from "node:path/posix";
4
+ import { mkdir } from "node:fs/promises";
5
+
6
+ export type UserConfig = {
7
+ vite?: vite.UserConfigExport;
8
+ prerenderedRoutes?: string[];
9
+ prerenderer?: PrerendererOptions;
10
+ };
11
+
12
+ export type ResolvedConfig = {
13
+ rootDir: string;
14
+ appDir: string;
15
+ publicDir: string;
16
+ serverDir: string;
17
+ serverModule: string;
18
+ serverEntry: string;
19
+ prerenderToDir: string;
20
+ outDir: string;
21
+ serverOutDir: string;
22
+ appOutDir: string;
23
+ appPort: number;
24
+ serverPort: number;
25
+ vite: vite.InlineConfig;
26
+ prerenderedRoutes: string[];
27
+ prerenderer: () => Promise<PrerendererOptions>;
28
+ };
29
+
30
+ export function defineConfig(config: UserConfig): UserConfig {
31
+ return config;
32
+ }
33
+
34
+ export async function resolveConfig(
35
+ root: string | undefined,
36
+ command: "build" | "serve",
37
+ mode: string,
38
+ ): Promise<ResolvedConfig> {
39
+ const rootDir = root ? resolve(root) : process.cwd();
40
+ const appDir = join(rootDir, "app");
41
+ const serverDir = join(rootDir, "server");
42
+ const serverModule = join(serverDir, "main.ts");
43
+ const serverEntry = join(import.meta.dir, "server-entry.ts");
44
+ const publicDir = join(rootDir, "public");
45
+ const outDir = join(rootDir, ".output");
46
+ const appOutDir = join(outDir, "public");
47
+ const serverOutDir = outDir;
48
+ const prerenderToDir = appOutDir;
49
+ const appPort = 3000;
50
+ const serverPort = 3001;
51
+
52
+ // Ensure required directories exist
53
+ await mkdir(prerenderToDir, { recursive: true });
54
+
55
+ const configFile = join(rootDir, "aframe.config"); // No file extension to resolve any JS/TS file
56
+ const relativeConfigFile = "./" + relative(import.meta.dir, configFile);
57
+ const { default: userConfig }: { default: UserConfig } = await import(
58
+ relativeConfigFile
59
+ );
60
+
61
+ let viteConfig = await vite.defineConfig((await userConfig.vite) ?? {});
62
+ if (typeof viteConfig === "function") {
63
+ viteConfig = await viteConfig({ command, mode });
64
+ }
65
+ // Apply opinionated config that can be overwritten
66
+
67
+ viteConfig = vite.mergeConfig<vite.InlineConfig, vite.InlineConfig>(
68
+ // Defaults
69
+ {
70
+ envPrefix: "APP_",
71
+ build: {
72
+ emptyOutDir: true,
73
+ },
74
+ },
75
+ // Overrides
76
+ viteConfig,
77
+ );
78
+
79
+ // Override required config
80
+ viteConfig = vite.mergeConfig<vite.InlineConfig, vite.InlineConfig>(
81
+ // Defaults
82
+ viteConfig,
83
+ // Overrides
84
+ {
85
+ logLevel: "warn",
86
+ configFile: false,
87
+ root: appDir,
88
+ publicDir,
89
+ envDir: rootDir,
90
+ build: {
91
+ outDir: appOutDir,
92
+ },
93
+ server: {
94
+ port: appPort,
95
+ strictPort: true,
96
+ proxy: {
97
+ "/api": {
98
+ target: `http://localhost:${serverPort}`,
99
+ changeOrigin: true,
100
+ },
101
+ },
102
+ },
103
+ },
104
+ );
105
+
106
+ const prerenderer = async (): Promise<PrerendererOptions> => {
107
+ const rendererModule =
108
+ tryResolve("@prerenderer/renderer-puppeteer") ??
109
+ tryResolve("@prerenderer/renderer-jsdom");
110
+ if (!rendererModule)
111
+ throw Error(
112
+ `No renderer installed. Did you forget to install @prerenderer/renderer-puppeteer or @prerenderer/renderer-jsdom?`,
113
+ );
114
+
115
+ const { default: Renderer } = await import(rendererModule);
116
+ const renderer = new Renderer();
117
+ return {
118
+ ...userConfig.prerenderer,
119
+ renderer,
120
+ staticDir: appOutDir,
121
+ };
122
+ };
123
+
124
+ return {
125
+ rootDir,
126
+ appDir,
127
+ publicDir,
128
+ serverDir,
129
+ serverModule,
130
+ serverEntry,
131
+ outDir,
132
+ serverOutDir,
133
+ appOutDir,
134
+ prerenderToDir,
135
+ appPort,
136
+ serverPort,
137
+
138
+ prerenderedRoutes: userConfig.prerenderedRoutes ?? ["/"],
139
+ prerenderer,
140
+ vite: viteConfig,
141
+ };
142
+ }
143
+
144
+ function tryResolve(specifier: string): string | undefined {
145
+ try {
146
+ return import.meta.resolve(specifier);
147
+ } catch {
148
+ return undefined;
149
+ }
150
+ }
@@ -7,6 +7,9 @@ import { lstatSync } from "node:fs";
7
7
  import type { BunPlugin } from "bun";
8
8
  import { RESET, DIM, GREEN, BLUE, MAGENTA, CYAN, BOLD } from "./color";
9
9
  import { createTimer } from "./timer";
10
+ import { type ResolvedConfig, resolveConfig } from "./config";
11
+
12
+ export * from "./config";
10
13
 
11
14
  export async function createServer(
12
15
  config: ResolvedConfig,
@@ -145,151 +148,3 @@ function prettyBytes(bytes: number) {
145
148
  const value = bytes / Math.pow(base, exponent);
146
149
  return `${unit === "B" ? value : value.toFixed(2)} ${unit}`;
147
150
  }
148
-
149
- ///
150
- /// CONFIG
151
- ///
152
-
153
- export type UserConfig = {
154
- vite?: vite.UserConfigExport;
155
- prerenderedRoutes?: string[];
156
- prerenderer?: PrerendererOptions;
157
- };
158
-
159
- export type ResolvedConfig = {
160
- rootDir: string;
161
- appDir: string;
162
- publicDir: string;
163
- serverDir: string;
164
- serverModule: string;
165
- serverEntry: string;
166
- prerenderToDir: string;
167
- outDir: string;
168
- serverOutDir: string;
169
- appOutDir: string;
170
- appPort: number;
171
- serverPort: number;
172
- vite: vite.InlineConfig;
173
- prerenderedRoutes: string[];
174
- prerenderer: () => Promise<PrerendererOptions>;
175
- };
176
-
177
- export function defineConfig(config: UserConfig): UserConfig {
178
- return config;
179
- }
180
-
181
- export async function resolveConfig(
182
- root: string | undefined,
183
- command: "build" | "serve",
184
- mode: string,
185
- ): Promise<ResolvedConfig> {
186
- const rootDir = root ? resolve(root) : process.cwd();
187
- const appDir = join(rootDir, "app");
188
- const serverDir = join(rootDir, "server");
189
- const serverModule = join(serverDir, "main.ts");
190
- const serverEntry = join(import.meta.dir, "server-entry.ts");
191
- const publicDir = join(rootDir, "public");
192
- const outDir = join(rootDir, ".output");
193
- const appOutDir = join(outDir, "public");
194
- const serverOutDir = outDir;
195
- const prerenderToDir = appOutDir;
196
- const appPort = 3000;
197
- const serverPort = 3001;
198
-
199
- // Ensure required directories exist
200
- await mkdir(prerenderToDir, { recursive: true });
201
-
202
- const configFile = join(rootDir, "aframe.config"); // No file extension to resolve any JS/TS file
203
- const relativeConfigFile = "./" + relative(import.meta.dir, configFile);
204
- const { default: userConfig }: { default: UserConfig } = await import(
205
- relativeConfigFile
206
- );
207
-
208
- let viteConfig = await vite.defineConfig((await userConfig.vite) ?? {});
209
- if (typeof viteConfig === "function") {
210
- viteConfig = await viteConfig({ command, mode });
211
- }
212
- // Apply opinionated config that can be overwritten
213
-
214
- viteConfig = vite.mergeConfig<vite.InlineConfig, vite.InlineConfig>(
215
- // Defaults
216
- {
217
- build: {
218
- emptyOutDir: true,
219
- },
220
- },
221
- // Overrides
222
- viteConfig,
223
- );
224
-
225
- // Override required config
226
- viteConfig = vite.mergeConfig<vite.InlineConfig, vite.InlineConfig>(
227
- // Defaults
228
- viteConfig,
229
- // Overrides
230
- {
231
- logLevel: "warn",
232
- configFile: false,
233
- root: appDir,
234
- publicDir,
235
- build: {
236
- outDir: appOutDir,
237
- },
238
- server: {
239
- port: appPort,
240
- strictPort: true,
241
- proxy: {
242
- "/api": {
243
- target: `http://localhost:${serverPort}`,
244
- changeOrigin: true,
245
- },
246
- },
247
- },
248
- },
249
- );
250
-
251
- const prerenderer = async (): Promise<PrerendererOptions> => {
252
- const rendererModule =
253
- tryResolve("@prerenderer/renderer-puppeteer") ??
254
- tryResolve("@prerenderer/renderer-jsdom");
255
- if (!rendererModule)
256
- throw Error(
257
- `No renderer installed. Did you forget to install @prerenderer/renderer-puppeteer or @prerenderer/renderer-jsdom?`,
258
- );
259
-
260
- const { default: Renderer } = await import(rendererModule);
261
- const renderer = new Renderer();
262
- return {
263
- ...userConfig.prerenderer,
264
- renderer,
265
- staticDir: appOutDir,
266
- };
267
- };
268
-
269
- return {
270
- rootDir,
271
- appDir,
272
- publicDir,
273
- serverDir,
274
- serverModule,
275
- serverEntry,
276
- outDir,
277
- serverOutDir,
278
- appOutDir,
279
- prerenderToDir,
280
- appPort,
281
- serverPort,
282
-
283
- prerenderedRoutes: userConfig.prerenderedRoutes ?? ["/"],
284
- prerenderer,
285
- vite: viteConfig,
286
- };
287
- }
288
-
289
- function tryResolve(specifier: string): string | undefined {
290
- try {
291
- return import.meta.resolve(specifier);
292
- } catch {
293
- return undefined;
294
- }
295
- }
package/src/server.ts CHANGED
@@ -22,9 +22,19 @@ export async function fetchStatic(request: Request): Promise<Response> {
22
22
 
23
23
  function fetchRootHtml() {
24
24
  if (import.meta.command === "serve") {
25
- return Response.json(
26
- { status: 404, error: "Root index.html not served during development" },
27
- { status: 404 },
25
+ return new Response(
26
+ `<html>
27
+ <body>
28
+ This is a placeholder for your root <code>index.html</code> file during development.
29
+ <br/>
30
+ In production (or via the app's dev server), this path will fallback on the root <code>index.html</code>.
31
+ </body>
32
+ </html>`,
33
+ {
34
+ headers: {
35
+ "Content-Type": "text/html",
36
+ },
37
+ },
28
38
  );
29
39
  }
30
40
 
@@ -33,7 +43,8 @@ function fetchRootHtml() {
33
43
 
34
44
  async function isFile(file: BunFile): Promise<boolean> {
35
45
  try {
36
- return (await file.stat()).isFile();
46
+ const stats = await file.stat();
47
+ return stats.isFile();
37
48
  } catch {
38
49
  return false;
39
50
  }