@aklinker1/aframe 0.1.0 → 0.2.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/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,12 +1,13 @@
1
1
  {
2
2
  "name": "@aklinker1/aframe",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "packageManager": "bun@1.2.2",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "exports": {
8
- ".": "./src/mod.ts",
9
- "./server": "./src/server.ts",
8
+ ".": "./src/index.ts",
9
+ "./server": "./src/client/server.ts",
10
+ "./app": "./src/client/app.ts",
10
11
  "./env": "./src/env.d.ts"
11
12
  },
12
13
  "bin": {
@@ -18,9 +19,9 @@
18
19
  ],
19
20
  "scripts": {
20
21
  "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"
22
+ "dev": "bun node_modules/@aklinker1/aframe/bin/aframe.ts demo",
23
+ "build": "bun node_modules/@aklinker1/aframe/bin/aframe.ts build demo",
24
+ "preview": "bun --cwd demo/.output --env-file ../.env server-entry.js"
24
25
  },
25
26
  "dependencies": {},
26
27
  "devDependencies": {
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Return's `true` when the app is prerendering (when URL includes `?prerendering`).
3
+ */
4
+ export function isPrerendering() {
5
+ return new URL(location.href).searchParams.has("prerendering");
6
+ }
@@ -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
  }
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
+ }
@@ -1,13 +1,15 @@
1
- import * as vite from "vite";
2
1
  import Prerenderer from "@prerenderer/prerenderer";
3
- import type { PrerendererOptions } from "@prerenderer/prerenderer";
4
- import { resolve, join, relative } from "node:path/posix";
5
- import { mkdir, rm, writeFile } from "node:fs/promises";
6
- import { lstatSync } from "node:fs";
7
2
  import type { BunPlugin } from "bun";
8
- import { RESET, DIM, GREEN, BLUE, MAGENTA, CYAN, BOLD } from "./color";
3
+ import { lstatSync } from "node:fs";
4
+ import { mkdir, rm, writeFile } from "node:fs/promises";
5
+ import { join, relative } from "node:path/posix";
6
+ import * as vite from "vite";
7
+ import { BLUE, BOLD, CYAN, DIM, GREEN, MAGENTA, RESET } from "./color";
8
+ import type { ResolvedConfig } from "./config";
9
9
  import { createTimer } from "./timer";
10
10
 
11
+ export * from "./config";
12
+
11
13
  export async function createServer(
12
14
  config: ResolvedConfig,
13
15
  ): Promise<vite.ViteDevServer> {
@@ -63,7 +65,11 @@ export async function build(config: ResolvedConfig) {
63
65
  const prerenderer = new Prerenderer(await config.prerenderer());
64
66
  const prerendered = await prerenderer
65
67
  .initialize()
66
- .then(() => prerenderer.renderRoutes(config.prerenderedRoutes))
68
+ .then(() =>
69
+ prerenderer.renderRoutes(
70
+ config.prerenderedRoutes.map((route) => `${route}?prerender`),
71
+ ),
72
+ )
67
73
  .then((renderedRoutes) =>
68
74
  Promise.all(
69
75
  renderedRoutes.map(async (route) => {
@@ -145,151 +151,3 @@ function prettyBytes(bytes: number) {
145
151
  const value = bytes / Math.pow(base, exponent);
146
152
  return `${unit === "B" ? value : value.toFixed(2)} ${unit}`;
147
153
  }
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
- }