a reactive (signals based) hypermedia web framework (wip) stormlightlabs.github.io/volt/
hypermedia frontend signals
at main 6.2 kB view raw
1import { echo } from "$console/echo.js"; 2import { type BuildArtifacts, buildLibrary, copyBuildArtifacts, findBuildArtifacts } from "$utils/build.js"; 3import { getExamplesPath } from "$utils/paths.js"; 4import { mkdir, readFile, writeFile } from "node:fs/promises"; 5import path from "node:path"; 6 7export type BuildMode = "markup" | "programmatic"; 8 9/** 10 * Create minified build artifacts in examples/dist/ 11 */ 12async function createMinifiedArtifacts(artifacts: BuildArtifacts, examplesDir: string): Promise<void> { 13 const examplesDistDir = path.join(examplesDir, "dist"); 14 await copyBuildArtifacts(artifacts, { outDir: examplesDistDir, minify: true, includeCss: true }); 15} 16 17function generateHTML(name: string, mode: BuildMode, standalone: boolean): string { 18 const cssPath = standalone ? "volt.min.css" : "../dist/volt.min.css"; 19 const jsPath = standalone ? "volt.min.js" : "../dist/volt.min.js"; 20 21 if (mode === "markup") { 22 return `<!DOCTYPE html> 23<html lang="en"> 24<head> 25 <meta charset="UTF-8"> 26 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 27 <title>${name} - Volt.js Example</title> 28 <link rel="stylesheet" href="${cssPath}"> 29</head> 30<body> 31 <div data-volt data-volt-state='{"message": "Hello Volt!"}'> 32 <h1 data-volt-text="message">Loading...</h1> 33 <!-- Add your HTML here with data-volt-* attributes --> 34 </div> 35 36 <script type="module"> 37 import { charge, registerPlugin, persistPlugin, scrollPlugin, urlPlugin } from './${jsPath}'; 38 39 // Register plugins 40 registerPlugin('persist', persistPlugin); 41 registerPlugin('scroll', scrollPlugin); 42 registerPlugin('url', urlPlugin); 43 44 // Initialize Volt roots 45 charge(); 46 </script> 47</body> 48</html> 49`; 50 } 51 52 return `<!DOCTYPE html> 53<html lang="en"> 54<head> 55 <meta charset="UTF-8"> 56 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 57 <title>${name} - Volt.js Example</title> 58 <link rel="stylesheet" href="${cssPath}"> 59 <link rel="stylesheet" href="app.css"> 60</head> 61<body> 62 <div id="app"> 63 <h1>${name}</h1> 64 <!-- Add your HTML here with data-volt-* attributes --> 65 </div> 66 67 <script type="module" src="${jsPath}"></script> 68 <script type="module" src="app.js"></script> 69</body> 70</html> 71`; 72} 73 74function generateREADME(name: string): string { 75 return `# ${name} 76 77## Description 78 79Brief description of what this example demonstrates. 80 81## Features 82 83- Feature 1 84- Feature 2 85- Feature 3 86 87## Running the Example 88 891. Make sure the project is built: \`pnpm build\` from the root 902. Open \`index.html\` in a browser 913. Or use a local server: \`python3 -m http.server 8000\` 92 93## Code Highlights 94 95Explain key parts of the implementation here. 96`; 97} 98 99function generateAppJS(): string { 100 return `// Import volt.js functions if needed 101// import { mount, signal, computed, effect } from '../dist/volt.min.js'; 102 103// Add your custom JavaScript here 104// Example: 105// const state = { 106// count: signal(0) 107// }; 108 109// mount(document.querySelector('#app'), state); 110`; 111} 112 113function generateAppCSS(): string { 114 return "/* Add your custom styles here */\n"; 115} 116 117/** 118 * Create example directory with all files 119 */ 120async function createExampleFiles( 121 exampleDir: string, 122 name: string, 123 mode: BuildMode, 124 standalone: boolean, 125): Promise<void> { 126 await mkdir(exampleDir, { recursive: true }); 127 128 const files = [{ path: "index.html", content: generateHTML(name, mode, standalone) }, { 129 path: "README.md", 130 content: generateREADME(name), 131 }]; 132 133 if (mode === "programmatic") { 134 files.push({ path: "app.js", content: generateAppJS() }, { path: "app.css", content: generateAppCSS() }); 135 } 136 137 for (const file of files) { 138 const filePath = path.join(exampleDir, file.path); 139 await writeFile(filePath, file.content, "utf8"); 140 echo.ok(` Created: examples/${name}/${file.path}`); 141 } 142} 143 144async function copyStandaloneFiles(examplesDir: string, exampleDir: string): Promise<void> { 145 const distDir = path.join(examplesDir, "dist"); 146 const jsSource = path.join(distDir, "volt.min.js"); 147 const cssSource = path.join(distDir, "volt.min.css"); 148 149 const jsDest = path.join(exampleDir, "volt.min.js"); 150 const cssDest = path.join(exampleDir, "volt.min.css"); 151 152 const jsContent = await readFile(jsSource, "utf8"); 153 const cssContent = await readFile(cssSource, "utf8"); 154 155 await writeFile(jsDest, jsContent, "utf8"); 156 await writeFile(cssDest, cssContent, "utf8"); 157 158 echo.ok(` Copied: volt.min.js (${Math.round(jsContent.length / 1024)} KB)`); 159 echo.ok(` Copied: volt.min.css (${Math.round(cssContent.length / 1024)} KB)`); 160} 161 162/** 163 * Example (generator) command implementation 164 * 165 * Creates a new example scaffold with minified volt.js build artifacts 166 */ 167export async function exampleCommand( 168 name: string, 169 options: { mode?: BuildMode; standalone?: boolean } = {}, 170): Promise<void> { 171 const mode = options.mode || "programmatic"; 172 const standalone = options.standalone || false; 173 174 const examplesDir = await getExamplesPath(); 175 const exampleDir = path.join(examplesDir, name); 176 177 echo.title(`\nCreating example: ${name}\n`); 178 echo.info(`Mode: ${mode}`); 179 echo.info(`Standalone: ${standalone ? "Yes" : "No (shared)"}\n`); 180 181 echo.info("Building Volt.js library..."); 182 await buildLibrary(); 183 184 echo.info("Finding build artifacts..."); 185 const artifacts = await findBuildArtifacts(); 186 187 echo.info("Creating minified build artifacts..."); 188 await createMinifiedArtifacts(artifacts, examplesDir); 189 190 echo.info(`\nScaffolding example files...`); 191 await createExampleFiles(exampleDir, name, mode, standalone); 192 193 if (standalone) { 194 echo.info("\nCopying standalone files..."); 195 await copyStandaloneFiles(examplesDir, exampleDir); 196 } 197 198 echo.success(`\nExample created successfully!\n`); 199 echo.info(`Location: examples/${name}/`); 200 echo.info(`Next steps:`); 201 202 if (mode === "markup") { 203 echo.text(` 1. Edit examples/${name}/index.html to add your UI with data-volt-* attributes`); 204 echo.text(` 2. Open examples/${name}/index.html in a browser\n`); 205 } else { 206 echo.text(` 1. Edit examples/${name}/index.html to add your UI`); 207 echo.text(` 2. Edit examples/${name}/app.js to add your logic`); 208 echo.text(` 3. Open examples/${name}/index.html in a browser\n`); 209 } 210}