@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 +3 -3
- package/bin/aframe.ts +1 -1
- package/package.json +8 -4
- package/src/client/server.ts +54 -53
- package/src/config.ts +19 -15
- package/src/dev-server.ts +11 -14
- package/src/env.d.ts +4 -9
- package/src/index.ts +174 -44
- package/src/{prerenderer.ts → prerender.ts} +12 -10
- package/src/server-entry.ts +0 -6
- package/src/virtual-modules.d.ts +0 -4
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
|
-
//
|
|
32
|
-
|
|
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,
|
|
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
|
-
"packageManager": "bun@1.2
|
|
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
|
}
|
package/src/client/server.ts
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import type { BunFile } from "bun";
|
|
2
|
-
import {
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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 (
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
17
|
+
prerender?: PrerenderConfig | false;
|
|
15
18
|
appPort?: number;
|
|
16
19
|
serverPort?: number;
|
|
20
|
+
hooks?: AframeHooks;
|
|
17
21
|
};
|
|
18
22
|
|
|
19
|
-
export type
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
146
|
+
prerenderedDir,
|
|
144
147
|
appPort,
|
|
145
148
|
serverPort,
|
|
146
149
|
proxyPaths,
|
|
147
150
|
|
|
148
151
|
prerenderedRoutes: userConfig.prerenderedRoutes ?? ["/"],
|
|
149
152
|
vite: viteConfig,
|
|
150
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
13
|
-
}
|
|
8
|
+
};
|
|
14
9
|
}
|
|
15
10
|
|
|
16
11
|
export {};
|
package/src/index.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
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 "./
|
|
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
|
|
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
|
-
|
|
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.
|
|
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-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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:
|
|
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
|
-
|
|
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.
|
|
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.
|
|
24
|
+
} = config.prerender ?? {};
|
|
25
25
|
|
|
26
26
|
const server = Bun.spawn({
|
|
27
|
-
cmd: ["bun", join(config.
|
|
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
|
|
71
|
-
const
|
|
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(
|
|
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 {
|
package/src/server-entry.ts
DELETED
package/src/virtual-modules.d.ts
DELETED