a reactive (signals based) hypermedia web framework (wip)
stormlightlabs.github.io/volt/
hypermedia
frontend
signals
1#!/usr/bin/env node
2
3/**
4 * Post-build script to finalize the distribution package:
5 * 1. Copy index.d.ts to voltx.d.ts for cleaner imports
6 * 2. Compress voltx.min.js to voltx.min.js.gz
7 * 3. Clean up unwanted files (chunks, assets)
8 */
9import { writeFileSync } from "node:fs";
10import { copyFile, readdir, readFile, unlink, writeFile } from "node:fs/promises";
11import path from "node:path";
12import { fileURLToPath } from "node:url";
13import { createGzip } from "node:zlib";
14import { minify as terserMinify } from "terser";
15
16async function doGzip(gzPath, input) {
17 return new Promise(resolve => {
18 const gzip = createGzip({ level: 9 });
19 const output = [];
20 gzip.on("data", (chunk) => output.push(chunk));
21 gzip.on("end", () => {
22 writeFileSync(gzPath, Buffer.concat(output));
23 const sizes = {
24 original: (input.length / 1024).toFixed(2),
25 compressed: (Buffer.concat(output).length / 1024).toFixed(2),
26 };
27 console.log(`✓ Compressed voltx.min.js: ${sizes.original}KB → ${sizes.compressed}KB (gzip)`);
28 resolve(void 0);
29 });
30
31 gzip.write(input);
32 gzip.end();
33 });
34}
35
36async function minifyJS(code) {
37 const result = await terserMinify(code, {
38 compress: {
39 dead_code: true,
40 drop_debugger: true,
41 conditionals: true,
42 evaluate: true,
43 booleans: true,
44 loops: true,
45 unused: true,
46 hoist_funs: true,
47 keep_fargs: false,
48 hoist_vars: false,
49 if_return: true,
50 join_vars: true,
51 side_effects: true,
52 },
53 mangle: { toplevel: true },
54 format: { comments: false },
55 });
56
57 if (!result.code) {
58 throw new Error("Minification failed - no output generated");
59 }
60
61 return result.code;
62}
63
64async function main() {
65 const __dirname = path.dirname(fileURLToPath(import.meta.url));
66 const distDir = path.resolve(__dirname, "../dist");
67
68 console.log("Finalizing build...\n");
69
70 try {
71 const indexDts = path.join(distDir, "index.d.ts");
72 const voltxDts = path.join(distDir, "voltx.d.ts");
73 await copyFile(indexDts, voltxDts);
74 console.log("✓ Copied index.d.ts → voltx.d.ts");
75 } catch (error) {
76 if (error instanceof Error) {
77 console.error("✗ Failed to copy type definitions:", error.message);
78 }
79 process.exit(1);
80 }
81
82 try {
83 const minJsPath = path.join(distDir, "voltx.min.js");
84 const gzPath = path.join(distDir, "voltx.min.js.gz");
85 const input = await readFile(minJsPath);
86 const minified = await minifyJS(input.toString());
87
88 await writeFile(minJsPath, minified);
89 await doGzip(gzPath, minified);
90 } catch (error) {
91 if (error instanceof Error) {
92 console.error("✗ Failed to compress voltx.min.js:", error.message);
93 }
94 process.exit(1);
95 }
96
97 try {
98 const files = await readdir(distDir);
99 const unwantedPatterns = [/\.svg$/, /\.png$/, /\.jpg$/];
100 let cleanedCount = 0;
101 for (const file of files) {
102 const shouldDelete = unwantedPatterns.some((pattern) => pattern.test(file));
103 if (shouldDelete) {
104 await unlink(path.join(distDir, file));
105 console.log(`✓ Removed unwanted file: ${file}`);
106 cleanedCount++;
107 }
108 }
109
110 if (cleanedCount === 0) {
111 console.log("✓ No unwanted files to clean");
112 }
113 } catch (error) {
114 if (error instanceof Error) {
115 console.error("✗ Failed to clean unwanted files:", error.message);
116 }
117 process.exit(1);
118 }
119
120 console.log("\nBuild finalization complete!");
121 process.exit(0);
122}
123
124main();