@hashrock/ono 0.1.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/LICENSE +21 -0
- package/README.md +299 -0
- package/package.json +49 -0
- package/src/bundler.js +73 -0
- package/src/cli.js +1014 -0
- package/src/jsx-runtime.js +45 -0
- package/src/renderer.js +128 -0
- package/src/resolver.js +137 -0
- package/src/transformer.js +48 -0
- package/src/unocss.js +75 -0
package/src/cli.js
ADDED
|
@@ -0,0 +1,1014 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CLI Tool for mini-jsx
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from "node:fs/promises";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import http from "node:http";
|
|
10
|
+
import net from "node:net";
|
|
11
|
+
import { bundle } from "./bundler.js";
|
|
12
|
+
import { renderToString } from "./renderer.js";
|
|
13
|
+
import { WebSocketServer } from "ws";
|
|
14
|
+
import { generateCSSFromFiles, loadUnoConfig } from "./unocss.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Find an available port starting from the given port
|
|
18
|
+
* @param {number} startPort - Port to start searching from
|
|
19
|
+
* @param {number} maxAttempts - Maximum number of ports to try
|
|
20
|
+
* @returns {Promise<number>} Available port number
|
|
21
|
+
*/
|
|
22
|
+
async function findAvailablePort(startPort, maxAttempts = 10) {
|
|
23
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
24
|
+
const port = startPort + i;
|
|
25
|
+
try {
|
|
26
|
+
await new Promise((resolve, reject) => {
|
|
27
|
+
const server = net.createServer();
|
|
28
|
+
server.once("error", reject);
|
|
29
|
+
server.once("listening", () => {
|
|
30
|
+
server.close();
|
|
31
|
+
resolve();
|
|
32
|
+
});
|
|
33
|
+
server.listen(port);
|
|
34
|
+
});
|
|
35
|
+
return port;
|
|
36
|
+
} catch (err) {
|
|
37
|
+
if (err.code !== "EADDRINUSE") {
|
|
38
|
+
throw err;
|
|
39
|
+
}
|
|
40
|
+
// Port is in use, try next one
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
throw new Error(`Could not find available port in range ${startPort}-${startPort + maxAttempts - 1}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Parse command line arguments into options object
|
|
48
|
+
* @param {string[]} args - Command line arguments
|
|
49
|
+
* @returns {object} Parsed options
|
|
50
|
+
*/
|
|
51
|
+
function parseArgs(args) {
|
|
52
|
+
const options = {
|
|
53
|
+
_: [], // Positional arguments
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
for (let i = 0; i < args.length; i++) {
|
|
57
|
+
const arg = args[i];
|
|
58
|
+
|
|
59
|
+
if (arg === "--watch" || arg === "-w") {
|
|
60
|
+
options.watch = true;
|
|
61
|
+
} else if (arg === "--port" || arg === "-p") {
|
|
62
|
+
options.port = parseInt(args[++i]);
|
|
63
|
+
} else if (arg === "--output" || arg === "-o") {
|
|
64
|
+
options.output = args[++i];
|
|
65
|
+
} else if (arg === "--help" || arg === "-h") {
|
|
66
|
+
options.help = true;
|
|
67
|
+
} else if (arg === "--version" || arg === "-v") {
|
|
68
|
+
options.version = true;
|
|
69
|
+
} else if (!arg.startsWith("-")) {
|
|
70
|
+
options._.push(arg);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return options;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Discover all JSX files in the pages directory
|
|
79
|
+
* @param {string} pagesDir - Path to pages directory
|
|
80
|
+
* @returns {Promise<string[]>} Array of JSX file paths
|
|
81
|
+
*/
|
|
82
|
+
async function discoverPages(pagesDir) {
|
|
83
|
+
const pages = [];
|
|
84
|
+
|
|
85
|
+
async function walk(dir) {
|
|
86
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
87
|
+
|
|
88
|
+
for (const entry of entries) {
|
|
89
|
+
const fullPath = path.join(dir, entry.name);
|
|
90
|
+
|
|
91
|
+
if (entry.isDirectory()) {
|
|
92
|
+
await walk(fullPath);
|
|
93
|
+
} else if (entry.isFile() && entry.name.endsWith(".jsx")) {
|
|
94
|
+
pages.push(fullPath);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
await walk(pagesDir);
|
|
101
|
+
return pages;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (error.code === "ENOENT") {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Generate UnoCSS file from built HTML files
|
|
112
|
+
* @param {string} outputDir - Output directory containing HTML files
|
|
113
|
+
* @param {boolean} silent - Suppress console output
|
|
114
|
+
* @returns {Promise<boolean>} True if CSS was generated
|
|
115
|
+
*/
|
|
116
|
+
async function generateUnoCSSFile(outputDir, silent = false) {
|
|
117
|
+
const outputDirAbs = path.resolve(process.cwd(), outputDir);
|
|
118
|
+
|
|
119
|
+
// Find all HTML files
|
|
120
|
+
const htmlFiles = [];
|
|
121
|
+
|
|
122
|
+
async function findHTMLFiles(dir) {
|
|
123
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
124
|
+
|
|
125
|
+
for (const entry of entries) {
|
|
126
|
+
const fullPath = path.join(dir, entry.name);
|
|
127
|
+
|
|
128
|
+
if (entry.isDirectory()) {
|
|
129
|
+
await findHTMLFiles(fullPath);
|
|
130
|
+
} else if (entry.isFile() && entry.name.endsWith(".html")) {
|
|
131
|
+
htmlFiles.push(fullPath);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
await findHTMLFiles(outputDirAbs);
|
|
138
|
+
|
|
139
|
+
if (htmlFiles.length === 0) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Load UnoCSS config
|
|
144
|
+
const unoConfig = await loadUnoConfig(path.resolve(process.cwd(), "uno.config.js"));
|
|
145
|
+
|
|
146
|
+
// Generate CSS from all HTML files
|
|
147
|
+
const css = await generateCSSFromFiles(htmlFiles, unoConfig);
|
|
148
|
+
|
|
149
|
+
if (css) {
|
|
150
|
+
// Write CSS file
|
|
151
|
+
const cssPath = path.join(outputDirAbs, "uno.css");
|
|
152
|
+
await fs.writeFile(cssPath, css);
|
|
153
|
+
|
|
154
|
+
if (!silent) {
|
|
155
|
+
console.log(`\nā” Generated UnoCSS: uno.css`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return false;
|
|
162
|
+
} catch (error) {
|
|
163
|
+
if (!silent) {
|
|
164
|
+
console.error(`Warning: UnoCSS generation failed: ${error.message}`);
|
|
165
|
+
}
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Copy public directory to output directory
|
|
172
|
+
* @param {string} publicDir - Path to public directory
|
|
173
|
+
* @param {string} outputDir - Path to output directory
|
|
174
|
+
* @param {boolean} silent - Suppress console output
|
|
175
|
+
* @returns {Promise<number>} Number of files copied
|
|
176
|
+
*/
|
|
177
|
+
async function copyPublicFiles(publicDir, outputDir, silent = false) {
|
|
178
|
+
const publicDirAbs = path.resolve(process.cwd(), publicDir);
|
|
179
|
+
const outputDirAbs = path.resolve(process.cwd(), outputDir);
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
// Check if public directory exists
|
|
183
|
+
await fs.access(publicDirAbs);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
// Public directory doesn't exist, skip
|
|
186
|
+
return 0;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
let fileCount = 0;
|
|
190
|
+
|
|
191
|
+
async function copyRecursive(src, dest) {
|
|
192
|
+
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
193
|
+
|
|
194
|
+
for (const entry of entries) {
|
|
195
|
+
const srcPath = path.join(src, entry.name);
|
|
196
|
+
const destPath = path.join(dest, entry.name);
|
|
197
|
+
|
|
198
|
+
if (entry.isDirectory()) {
|
|
199
|
+
await fs.mkdir(destPath, { recursive: true });
|
|
200
|
+
await copyRecursive(srcPath, destPath);
|
|
201
|
+
} else {
|
|
202
|
+
await fs.copyFile(srcPath, destPath);
|
|
203
|
+
fileCount++;
|
|
204
|
+
if (!silent) {
|
|
205
|
+
const relativePath = path.relative(publicDirAbs, srcPath);
|
|
206
|
+
console.log(`š Copied: public/${relativePath}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
await copyRecursive(publicDirAbs, outputDirAbs);
|
|
213
|
+
return fileCount;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Build a JSX file to HTML
|
|
218
|
+
* @param {string} inputFile - Path to input JSX file
|
|
219
|
+
* @param {object} options - Build options
|
|
220
|
+
* @param {boolean} options.liveReload - Inject live reload script
|
|
221
|
+
* @param {boolean} options.silent - Suppress console output
|
|
222
|
+
* @param {number} options.wsPort - WebSocket port for live reload
|
|
223
|
+
* @param {string} options.outputDir - Custom output directory
|
|
224
|
+
* @param {string} options.pagesDir - Pages directory for relative path calculation
|
|
225
|
+
* @returns {Promise<{outputPath: string, html: string}>}
|
|
226
|
+
*/
|
|
227
|
+
async function buildFile(inputFile, options = {}) {
|
|
228
|
+
const { liveReload = false, silent = false, wsPort = 35729, outputDir = "dist", pagesDir = null } = options;
|
|
229
|
+
const absolutePath = path.resolve(process.cwd(), inputFile);
|
|
230
|
+
|
|
231
|
+
if (!silent) {
|
|
232
|
+
console.log(`Building ${inputFile}...`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Bundle the JSX file
|
|
236
|
+
const bundledCode = await bundle(absolutePath);
|
|
237
|
+
|
|
238
|
+
// Create a temporary module to execute
|
|
239
|
+
// We need to inject our runtime inline (no external dependencies)
|
|
240
|
+
const codeWithRuntime = `
|
|
241
|
+
// Inline JSX Runtime - No external dependencies required
|
|
242
|
+
function flattenChildren(children) {
|
|
243
|
+
const result = [];
|
|
244
|
+
for (const child of children) {
|
|
245
|
+
if (child === null || child === undefined || typeof child === 'boolean') {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if (Array.isArray(child)) {
|
|
249
|
+
result.push(...flattenChildren(child));
|
|
250
|
+
} else {
|
|
251
|
+
result.push(child);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return result;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function h(tag, props, ...children) {
|
|
258
|
+
return {
|
|
259
|
+
tag,
|
|
260
|
+
props: props || {},
|
|
261
|
+
children: flattenChildren(children)
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
${bundledCode}
|
|
266
|
+
`;
|
|
267
|
+
|
|
268
|
+
// Write to temporary file
|
|
269
|
+
const tmpFile = path.join(process.cwd(), ".mini-jsx-tmp.js");
|
|
270
|
+
await fs.writeFile(tmpFile, codeWithRuntime);
|
|
271
|
+
|
|
272
|
+
// Import and execute
|
|
273
|
+
const moduleUrl = `${tmpFile}?t=${Date.now()}`;
|
|
274
|
+
const module = await import(moduleUrl);
|
|
275
|
+
const App = module.default;
|
|
276
|
+
|
|
277
|
+
if (!App) {
|
|
278
|
+
throw new Error("No default export found in entry file");
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Render to HTML
|
|
282
|
+
const vnode = typeof App === "function" ? App({}) : App;
|
|
283
|
+
let html = renderToString(vnode);
|
|
284
|
+
|
|
285
|
+
// Inject live reload script if requested
|
|
286
|
+
if (liveReload) {
|
|
287
|
+
const liveReloadScript = `
|
|
288
|
+
<script>
|
|
289
|
+
(function() {
|
|
290
|
+
const ws = new WebSocket('ws://localhost:${wsPort}');
|
|
291
|
+
ws.onmessage = function(event) {
|
|
292
|
+
if (event.data === 'reload') {
|
|
293
|
+
console.log('Reloading...');
|
|
294
|
+
window.location.reload();
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
ws.onclose = function() {
|
|
298
|
+
console.log('Live reload disconnected. Retrying...');
|
|
299
|
+
setTimeout(function() { window.location.reload(); }, 1000);
|
|
300
|
+
};
|
|
301
|
+
})();
|
|
302
|
+
</script>`;
|
|
303
|
+
html = html.replace("</body>", `${liveReloadScript}\n</body>`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Add DOCTYPE
|
|
307
|
+
const fullHtml = `<!DOCTYPE html>\n${html}`;
|
|
308
|
+
|
|
309
|
+
// Output to specified directory
|
|
310
|
+
const outDir = path.resolve(process.cwd(), outputDir);
|
|
311
|
+
await fs.mkdir(outDir, { recursive: true });
|
|
312
|
+
|
|
313
|
+
// Output file name - preserve directory structure if pagesDir is specified
|
|
314
|
+
let outputPath;
|
|
315
|
+
let relativeOutput;
|
|
316
|
+
|
|
317
|
+
if (pagesDir) {
|
|
318
|
+
// Preserve the directory structure from pages folder
|
|
319
|
+
const pagesDirAbs = path.resolve(process.cwd(), pagesDir);
|
|
320
|
+
const relativePath = path.relative(pagesDirAbs, absolutePath);
|
|
321
|
+
const outputRelative = relativePath.replace(/\.jsx$/, ".html");
|
|
322
|
+
outputPath = path.join(outDir, outputRelative);
|
|
323
|
+
|
|
324
|
+
// Create subdirectories if needed
|
|
325
|
+
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
326
|
+
|
|
327
|
+
relativeOutput = path.relative(process.cwd(), outputPath);
|
|
328
|
+
} else {
|
|
329
|
+
// Single file mode - just use basename
|
|
330
|
+
const inputBasename = path.basename(inputFile, ".jsx");
|
|
331
|
+
const outputFilename = `${inputBasename}.html`;
|
|
332
|
+
outputPath = path.join(outDir, outputFilename);
|
|
333
|
+
relativeOutput = path.relative(process.cwd(), outputPath);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
await fs.writeFile(outputPath, fullHtml);
|
|
337
|
+
|
|
338
|
+
// Clean up temp file
|
|
339
|
+
await fs.unlink(tmpFile);
|
|
340
|
+
|
|
341
|
+
if (!silent) {
|
|
342
|
+
console.log(`ā Built successfully: ${relativeOutput}`);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return { outputPath, html: fullHtml };
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async function main() {
|
|
349
|
+
const args = process.argv.slice(2);
|
|
350
|
+
const opts = parseArgs(args);
|
|
351
|
+
|
|
352
|
+
// Show version
|
|
353
|
+
if (opts.version) {
|
|
354
|
+
const pkg = JSON.parse(
|
|
355
|
+
await fs.readFile(new URL("../package.json", import.meta.url), "utf-8")
|
|
356
|
+
);
|
|
357
|
+
console.log(`ono v${pkg.version}`);
|
|
358
|
+
process.exit(0);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Show help
|
|
362
|
+
if (args.length === 0 || opts.help) {
|
|
363
|
+
console.log(`
|
|
364
|
+
Ono - A lightweight JSX library for static site generation
|
|
365
|
+
|
|
366
|
+
Usage:
|
|
367
|
+
ono init [dir] Initialize a new Ono project
|
|
368
|
+
ono build <file|dir> [options] Build JSX file(s) to HTML
|
|
369
|
+
ono dev <file|dir> [options] Start dev server with live reload
|
|
370
|
+
|
|
371
|
+
Arguments:
|
|
372
|
+
dir Project directory (default: current directory)
|
|
373
|
+
file Single JSX file to build/serve
|
|
374
|
+
pages Pages directory (default: pages/)
|
|
375
|
+
|
|
376
|
+
Options:
|
|
377
|
+
-w, --watch Watch for changes and rebuild (build only)
|
|
378
|
+
-p, --port <port> Port number (default: 3000) (dev only)
|
|
379
|
+
-o, --output <dir> Output directory (default: dist)
|
|
380
|
+
-h, --help Show this help message
|
|
381
|
+
-v, --version Show version number
|
|
382
|
+
|
|
383
|
+
Examples:
|
|
384
|
+
# Initialize new project
|
|
385
|
+
ono init
|
|
386
|
+
ono init my-project
|
|
387
|
+
|
|
388
|
+
# Single file mode
|
|
389
|
+
ono build example/index.jsx
|
|
390
|
+
ono build example/index.jsx --watch
|
|
391
|
+
ono dev example/index.jsx
|
|
392
|
+
|
|
393
|
+
# Pages mode (build all .jsx files in directory)
|
|
394
|
+
ono build pages
|
|
395
|
+
ono build pages --watch
|
|
396
|
+
ono dev pages
|
|
397
|
+
ono dev # Same as: ono dev pages
|
|
398
|
+
|
|
399
|
+
# Custom options
|
|
400
|
+
ono build pages -o public
|
|
401
|
+
ono dev pages -p 8080
|
|
402
|
+
`);
|
|
403
|
+
process.exit(0);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const command = opts._[0];
|
|
407
|
+
const inputFile = opts._[1];
|
|
408
|
+
|
|
409
|
+
if (command === "init") {
|
|
410
|
+
const projectDir = inputFile || ".";
|
|
411
|
+
const projectPath = path.resolve(process.cwd(), projectDir);
|
|
412
|
+
|
|
413
|
+
try {
|
|
414
|
+
// Check if directory exists and is not empty
|
|
415
|
+
try {
|
|
416
|
+
const files = await fs.readdir(projectPath);
|
|
417
|
+
if (files.length > 0 && projectDir !== ".") {
|
|
418
|
+
console.error(`Error: Directory ${projectDir} is not empty`);
|
|
419
|
+
process.exit(1);
|
|
420
|
+
}
|
|
421
|
+
} catch (error) {
|
|
422
|
+
if (error.code === "ENOENT") {
|
|
423
|
+
// Directory doesn't exist, create it
|
|
424
|
+
await fs.mkdir(projectPath, { recursive: true });
|
|
425
|
+
} else {
|
|
426
|
+
throw error;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
console.log(`\nš Initializing Ono project in ${projectDir === "." ? "current directory" : projectDir}...\n`);
|
|
431
|
+
|
|
432
|
+
// Create directory structure
|
|
433
|
+
await fs.mkdir(path.join(projectPath, "pages"), { recursive: true });
|
|
434
|
+
await fs.mkdir(path.join(projectPath, "components"), { recursive: true });
|
|
435
|
+
await fs.mkdir(path.join(projectPath, "public", "css"), { recursive: true });
|
|
436
|
+
|
|
437
|
+
// Create Layout.jsx
|
|
438
|
+
const layoutContent = `// Layout component with slot support
|
|
439
|
+
export default function Layout(props) {
|
|
440
|
+
return (
|
|
441
|
+
<html lang="en">
|
|
442
|
+
<head>
|
|
443
|
+
<meta charset="UTF-8" />
|
|
444
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
445
|
+
<title>{props.title || "Ono Site"}</title>
|
|
446
|
+
<link rel="stylesheet" href="/uno.css" />
|
|
447
|
+
<link rel="stylesheet" href="/css/style.css" />
|
|
448
|
+
</head>
|
|
449
|
+
<body class="font-sans max-w-800px mx-auto px-8 py-8 leading-relaxed">
|
|
450
|
+
<header class="mb-8 pb-4 border-b-2 border-gray-200">
|
|
451
|
+
<h1 class="text-3xl font-bold">{props.title}</h1>
|
|
452
|
+
{props.header}
|
|
453
|
+
</header>
|
|
454
|
+
<main>
|
|
455
|
+
{props.children}
|
|
456
|
+
</main>
|
|
457
|
+
<footer class="mt-12 pt-4 border-t border-gray-200 text-secondary text-sm">
|
|
458
|
+
{props.footer || <p>Ā© 2025 Ono</p>}
|
|
459
|
+
</footer>
|
|
460
|
+
</body>
|
|
461
|
+
</html>
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
`;
|
|
465
|
+
await fs.writeFile(path.join(projectPath, "components", "Layout.jsx"), layoutContent);
|
|
466
|
+
|
|
467
|
+
// Create Hello.jsx component
|
|
468
|
+
const helloContent = `export default function Hello(props) {
|
|
469
|
+
return (
|
|
470
|
+
<div class="p-6 bg-blue-50 rounded-lg border-2 border-blue-200">
|
|
471
|
+
<h2 class="text-2xl font-semibold text-blue-800 mb-2">
|
|
472
|
+
Hello, {props.name || "World"}!
|
|
473
|
+
</h2>
|
|
474
|
+
<p class="text-blue-600">
|
|
475
|
+
This is a reusable component. Edit components/Hello.jsx to customize it.
|
|
476
|
+
</p>
|
|
477
|
+
</div>
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
`;
|
|
481
|
+
await fs.writeFile(path.join(projectPath, "components", "Hello.jsx"), helloContent);
|
|
482
|
+
|
|
483
|
+
// Create index.jsx
|
|
484
|
+
const indexContent = `import Layout from "../components/Layout.jsx";
|
|
485
|
+
import Hello from "../components/Hello.jsx";
|
|
486
|
+
|
|
487
|
+
export default function Home() {
|
|
488
|
+
return (
|
|
489
|
+
<Layout title="Welcome to Ono">
|
|
490
|
+
<Hello name="Developer" />
|
|
491
|
+
|
|
492
|
+
<div class="mt-8 space-y-6">
|
|
493
|
+
<section>
|
|
494
|
+
<h2 class="text-2xl font-bold mb-4">Getting Started</h2>
|
|
495
|
+
<p class="mb-4 text-lg">
|
|
496
|
+
You've successfully initialized an Ono project! Here's what you can do next:
|
|
497
|
+
</p>
|
|
498
|
+
<ul class="list-disc ml-6 space-y-2">
|
|
499
|
+
<li>Edit <code class="bg-gray-100 px-2 py-1 rounded text-sm">pages/index.jsx</code> to customize this page</li>
|
|
500
|
+
<li>Create new pages in the <code class="bg-gray-100 px-2 py-1 rounded text-sm">pages/</code> directory</li>
|
|
501
|
+
<li>Add components to <code class="bg-gray-100 px-2 py-1 rounded text-sm">components/</code></li>
|
|
502
|
+
<li>Put static assets in <code class="bg-gray-100 px-2 py-1 rounded text-sm">public/</code></li>
|
|
503
|
+
</ul>
|
|
504
|
+
</section>
|
|
505
|
+
|
|
506
|
+
<section>
|
|
507
|
+
<h2 class="text-2xl font-bold mb-4">Commands</h2>
|
|
508
|
+
<div class="space-y-2">
|
|
509
|
+
<div class="p-4 bg-gray-50 rounded border border-gray-200">
|
|
510
|
+
<code class="text-green-600 font-semibold">ono dev</code>
|
|
511
|
+
<p class="text-sm text-secondary mt-1">Start development server with live reload</p>
|
|
512
|
+
</div>
|
|
513
|
+
<div class="p-4 bg-gray-50 rounded border border-gray-200">
|
|
514
|
+
<code class="text-green-600 font-semibold">ono build pages</code>
|
|
515
|
+
<p class="text-sm text-secondary mt-1">Build your site to the dist/ directory</p>
|
|
516
|
+
</div>
|
|
517
|
+
</div>
|
|
518
|
+
</section>
|
|
519
|
+
|
|
520
|
+
<section>
|
|
521
|
+
<h2 class="text-2xl font-bold mb-4">Features</h2>
|
|
522
|
+
<div class="grid grid-cols-2 gap-4">
|
|
523
|
+
<div class="p-4 border border-gray-200 rounded">
|
|
524
|
+
<h3 class="font-semibold mb-2">šØ UnoCSS</h3>
|
|
525
|
+
<p class="text-sm text-secondary">Atomic CSS with Tailwind-compatible utilities</p>
|
|
526
|
+
</div>
|
|
527
|
+
<div class="p-4 border border-gray-200 rounded">
|
|
528
|
+
<h3 class="font-semibold mb-2">ā” Live Reload</h3>
|
|
529
|
+
<p class="text-sm text-secondary">Instant updates during development</p>
|
|
530
|
+
</div>
|
|
531
|
+
<div class="p-4 border border-gray-200 rounded">
|
|
532
|
+
<h3 class="font-semibold mb-2">š¦ Component Based</h3>
|
|
533
|
+
<p class="text-sm text-secondary">Reusable JSX components</p>
|
|
534
|
+
</div>
|
|
535
|
+
<div class="p-4 border border-gray-200 rounded">
|
|
536
|
+
<h3 class="font-semibold mb-2">š Static Output</h3>
|
|
537
|
+
<p class="text-sm text-secondary">Fast, SEO-friendly HTML</p>
|
|
538
|
+
</div>
|
|
539
|
+
</div>
|
|
540
|
+
</section>
|
|
541
|
+
</div>
|
|
542
|
+
</Layout>
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
`;
|
|
546
|
+
await fs.writeFile(path.join(projectPath, "pages", "index.jsx"), indexContent);
|
|
547
|
+
|
|
548
|
+
// Create style.css
|
|
549
|
+
const styleContent = `/* Custom styles for your Ono site */
|
|
550
|
+
/* UnoCSS utilities will be automatically generated in uno.css */
|
|
551
|
+
|
|
552
|
+
body {
|
|
553
|
+
-webkit-font-smoothing: antialiased;
|
|
554
|
+
-moz-osx-font-smoothing: grayscale;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/* Add your custom styles here */
|
|
558
|
+
`;
|
|
559
|
+
await fs.writeFile(path.join(projectPath, "public", "css", "style.css"), styleContent);
|
|
560
|
+
|
|
561
|
+
// Create .gitignore
|
|
562
|
+
const gitignoreContent = `# Build output
|
|
563
|
+
dist/
|
|
564
|
+
.mini-jsx-tmp.js
|
|
565
|
+
|
|
566
|
+
# Dependencies
|
|
567
|
+
node_modules/
|
|
568
|
+
|
|
569
|
+
# Environment variables
|
|
570
|
+
.env
|
|
571
|
+
.env.local
|
|
572
|
+
`;
|
|
573
|
+
await fs.writeFile(path.join(projectPath, ".gitignore"), gitignoreContent);
|
|
574
|
+
|
|
575
|
+
// Create package.json
|
|
576
|
+
const projectName = projectDir === "." ? path.basename(projectPath) : projectDir;
|
|
577
|
+
const packageJsonContent = {
|
|
578
|
+
name: projectName,
|
|
579
|
+
version: "0.1.0",
|
|
580
|
+
type: "module",
|
|
581
|
+
scripts: {
|
|
582
|
+
dev: "ono dev",
|
|
583
|
+
build: "ono build pages",
|
|
584
|
+
"build:watch": "ono build pages --watch"
|
|
585
|
+
},
|
|
586
|
+
devDependencies: {}
|
|
587
|
+
};
|
|
588
|
+
await fs.writeFile(
|
|
589
|
+
path.join(projectPath, "package.json"),
|
|
590
|
+
JSON.stringify(packageJsonContent, null, 2) + "\n"
|
|
591
|
+
);
|
|
592
|
+
|
|
593
|
+
console.log("ā
Created project structure:");
|
|
594
|
+
console.log(" pages/");
|
|
595
|
+
console.log(" āāā index.jsx");
|
|
596
|
+
console.log(" components/");
|
|
597
|
+
console.log(" āāā Layout.jsx");
|
|
598
|
+
console.log(" āāā Hello.jsx");
|
|
599
|
+
console.log(" public/");
|
|
600
|
+
console.log(" āāā css/");
|
|
601
|
+
console.log(" ā āāā style.css");
|
|
602
|
+
console.log(" package.json");
|
|
603
|
+
console.log(" .gitignore");
|
|
604
|
+
console.log("");
|
|
605
|
+
console.log("š Project initialized successfully!");
|
|
606
|
+
console.log("");
|
|
607
|
+
console.log("Next steps:");
|
|
608
|
+
if (projectDir !== ".") {
|
|
609
|
+
console.log(` cd ${projectDir}`);
|
|
610
|
+
}
|
|
611
|
+
console.log(" npm run dev # Start development server");
|
|
612
|
+
console.log(" npm run build # Build for production");
|
|
613
|
+
console.log("");
|
|
614
|
+
console.log("Or use ono directly:");
|
|
615
|
+
console.log(" ono dev # Start development server");
|
|
616
|
+
console.log(" ono build pages # Build for production");
|
|
617
|
+
console.log("");
|
|
618
|
+
} catch (error) {
|
|
619
|
+
console.error(`Error: ${error.message}`);
|
|
620
|
+
if (error.stack) {
|
|
621
|
+
console.error(error.stack);
|
|
622
|
+
}
|
|
623
|
+
process.exit(1);
|
|
624
|
+
}
|
|
625
|
+
} else if (command === "build") {
|
|
626
|
+
const buildOptions = {
|
|
627
|
+
outputDir: opts.output || "dist",
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
try {
|
|
631
|
+
// Check if inputFile is a directory (pages mode)
|
|
632
|
+
const isDirectory = inputFile && (await fs.stat(path.resolve(process.cwd(), inputFile)).catch(() => null))?.isDirectory();
|
|
633
|
+
|
|
634
|
+
if (isDirectory || inputFile === "pages" || !inputFile) {
|
|
635
|
+
// Pages mode - build all pages in directory
|
|
636
|
+
const pagesDir = inputFile || "pages";
|
|
637
|
+
const pagesDirAbs = path.resolve(process.cwd(), pagesDir);
|
|
638
|
+
|
|
639
|
+
const pages = await discoverPages(pagesDirAbs);
|
|
640
|
+
|
|
641
|
+
if (pages.length === 0) {
|
|
642
|
+
console.error(`Error: No JSX files found in ${pagesDir}/`);
|
|
643
|
+
process.exit(1);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
console.log(`Found ${pages.length} page(s) in ${pagesDir}/\n`);
|
|
647
|
+
|
|
648
|
+
if (opts.watch) {
|
|
649
|
+
// Initial build all pages
|
|
650
|
+
for (const page of pages) {
|
|
651
|
+
await buildFile(page, { ...buildOptions, pagesDir: pagesDirAbs });
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// Copy public files
|
|
655
|
+
const publicCount = await copyPublicFiles("public", buildOptions.outputDir);
|
|
656
|
+
if (publicCount > 0) {
|
|
657
|
+
console.log(`\nš¦ Copied ${publicCount} file(s) from public/\n`);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// Generate UnoCSS
|
|
661
|
+
await generateUnoCSSFile(buildOptions.outputDir);
|
|
662
|
+
|
|
663
|
+
console.log(`š Watching for changes in ${pagesDir}/ and public/...`);
|
|
664
|
+
console.log("Press Ctrl+C to stop\n");
|
|
665
|
+
|
|
666
|
+
const { watch } = await import("node:fs");
|
|
667
|
+
|
|
668
|
+
// Watch pages directory
|
|
669
|
+
watch(pagesDirAbs, { recursive: true }, async (_eventType, filename) => {
|
|
670
|
+
if (filename && filename.endsWith(".jsx")) {
|
|
671
|
+
const changedFile = path.join(pagesDirAbs, filename);
|
|
672
|
+
try {
|
|
673
|
+
await buildFile(changedFile, { ...buildOptions, pagesDir: pagesDirAbs, silent: true });
|
|
674
|
+
await generateUnoCSSFile(buildOptions.outputDir, true);
|
|
675
|
+
console.log(`ā Rebuilt: ${filename}`);
|
|
676
|
+
} catch (error) {
|
|
677
|
+
console.error(`ā Build error: ${error.message}`);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
// Watch public directory
|
|
683
|
+
const publicDirAbs = path.resolve(process.cwd(), "public");
|
|
684
|
+
try {
|
|
685
|
+
await fs.access(publicDirAbs);
|
|
686
|
+
watch(publicDirAbs, { recursive: true }, async (_eventType, filename) => {
|
|
687
|
+
if (filename) {
|
|
688
|
+
try {
|
|
689
|
+
await copyPublicFiles("public", buildOptions.outputDir, true);
|
|
690
|
+
console.log(`ā Copied: public/${filename}`);
|
|
691
|
+
} catch (error) {
|
|
692
|
+
console.error(`ā Copy error: ${error.message}`);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
} catch {
|
|
697
|
+
// Public directory doesn't exist, skip watching
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
process.on("SIGINT", () => {
|
|
701
|
+
console.log("\n\nš Shutting down...");
|
|
702
|
+
process.exit(0);
|
|
703
|
+
});
|
|
704
|
+
} else {
|
|
705
|
+
// Build all pages
|
|
706
|
+
for (const page of pages) {
|
|
707
|
+
await buildFile(page, { ...buildOptions, pagesDir: pagesDirAbs });
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// Copy public files
|
|
711
|
+
const publicCount = await copyPublicFiles("public", buildOptions.outputDir);
|
|
712
|
+
if (publicCount > 0) {
|
|
713
|
+
console.log(`\nš¦ Copied ${publicCount} file(s) from public/`);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// Generate UnoCSS
|
|
717
|
+
await generateUnoCSSFile(buildOptions.outputDir);
|
|
718
|
+
}
|
|
719
|
+
} else if (inputFile) {
|
|
720
|
+
// Single file mode
|
|
721
|
+
if (opts.watch) {
|
|
722
|
+
// Initial build
|
|
723
|
+
await buildFile(inputFile, buildOptions);
|
|
724
|
+
|
|
725
|
+
// Generate UnoCSS
|
|
726
|
+
await generateUnoCSSFile(buildOptions.outputDir);
|
|
727
|
+
|
|
728
|
+
// Watch for changes
|
|
729
|
+
const absolutePath = path.resolve(process.cwd(), inputFile);
|
|
730
|
+
const watchDir = path.dirname(absolutePath);
|
|
731
|
+
|
|
732
|
+
console.log(`\nš Watching for changes in ${watchDir}...`);
|
|
733
|
+
console.log("Press Ctrl+C to stop\n");
|
|
734
|
+
|
|
735
|
+
const { watch } = await import("node:fs");
|
|
736
|
+
watch(watchDir, { recursive: true }, async (_eventType, filename) => {
|
|
737
|
+
if (filename && filename.endsWith(".jsx")) {
|
|
738
|
+
try {
|
|
739
|
+
await buildFile(inputFile, { ...buildOptions, silent: true });
|
|
740
|
+
await generateUnoCSSFile(buildOptions.outputDir, true);
|
|
741
|
+
console.log(`ā Rebuilt: ${filename}`);
|
|
742
|
+
} catch (error) {
|
|
743
|
+
console.error(`ā Build error: ${error.message}`);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
process.on("SIGINT", () => {
|
|
749
|
+
console.log("\n\nš Shutting down...");
|
|
750
|
+
process.exit(0);
|
|
751
|
+
});
|
|
752
|
+
} else {
|
|
753
|
+
// Single build
|
|
754
|
+
await buildFile(inputFile, buildOptions);
|
|
755
|
+
|
|
756
|
+
// Generate UnoCSS
|
|
757
|
+
await generateUnoCSSFile(buildOptions.outputDir);
|
|
758
|
+
}
|
|
759
|
+
} else {
|
|
760
|
+
console.error("Error: Please specify a file or pages directory to build");
|
|
761
|
+
console.error("Usage: mini-jsx build <file|pages> [options]");
|
|
762
|
+
process.exit(1);
|
|
763
|
+
}
|
|
764
|
+
} catch (error) {
|
|
765
|
+
console.error(`Error: ${error.message}`);
|
|
766
|
+
if (error.stack) {
|
|
767
|
+
console.error(error.stack);
|
|
768
|
+
}
|
|
769
|
+
process.exit(1);
|
|
770
|
+
}
|
|
771
|
+
} else if (command === "dev") {
|
|
772
|
+
const requestedPort = opts.port || 3000;
|
|
773
|
+
const outputDir = opts.output || "dist";
|
|
774
|
+
|
|
775
|
+
try {
|
|
776
|
+
// Check if inputFile is a directory (pages mode)
|
|
777
|
+
const isDirectory = inputFile && (await fs.stat(path.resolve(process.cwd(), inputFile)).catch(() => null))?.isDirectory();
|
|
778
|
+
const isPages = isDirectory || inputFile === "pages" || !inputFile;
|
|
779
|
+
|
|
780
|
+
// Find available ports
|
|
781
|
+
const wsPort = await findAvailablePort(35729);
|
|
782
|
+
const httpPort = await findAvailablePort(requestedPort);
|
|
783
|
+
|
|
784
|
+
if (wsPort !== 35729) {
|
|
785
|
+
console.log(`ā¹ļø WebSocket port 35729 is busy, using port ${wsPort} instead`);
|
|
786
|
+
}
|
|
787
|
+
if (httpPort !== requestedPort) {
|
|
788
|
+
console.log(`ā¹ļø Port ${requestedPort} is busy, using port ${httpPort} instead`);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
if (isPages) {
|
|
792
|
+
// Pages mode
|
|
793
|
+
const pagesDir = inputFile || "pages";
|
|
794
|
+
const pagesDirAbs = path.resolve(process.cwd(), pagesDir);
|
|
795
|
+
const pages = await discoverPages(pagesDirAbs);
|
|
796
|
+
|
|
797
|
+
if (pages.length === 0) {
|
|
798
|
+
console.error(`Error: No JSX files found in ${pagesDir}/`);
|
|
799
|
+
process.exit(1);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
console.log(`Found ${pages.length} page(s) in ${pagesDir}/\n`);
|
|
803
|
+
|
|
804
|
+
// Initial build all pages
|
|
805
|
+
for (const page of pages) {
|
|
806
|
+
await buildFile(page, { liveReload: true, wsPort, outputDir, pagesDir: pagesDirAbs });
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// Copy public files
|
|
810
|
+
const publicCount = await copyPublicFiles("public", outputDir);
|
|
811
|
+
if (publicCount > 0) {
|
|
812
|
+
console.log(`\nš¦ Copied ${publicCount} file(s) from public/\n`);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// Generate UnoCSS
|
|
816
|
+
await generateUnoCSSFile(outputDir);
|
|
817
|
+
|
|
818
|
+
// Setup WebSocket server
|
|
819
|
+
const wss = new WebSocketServer({ port: wsPort });
|
|
820
|
+
const clients = new Set();
|
|
821
|
+
|
|
822
|
+
wss.on("connection", (ws) => {
|
|
823
|
+
clients.add(ws);
|
|
824
|
+
ws.on("close", () => clients.delete(ws));
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
function notifyClients() {
|
|
828
|
+
clients.forEach((client) => {
|
|
829
|
+
if (client.readyState === 1) {
|
|
830
|
+
client.send("reload");
|
|
831
|
+
}
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// Watch for changes
|
|
836
|
+
console.log(`š Watching for changes in ${pagesDir}/ and public/...`);
|
|
837
|
+
|
|
838
|
+
const { watch } = await import("node:fs");
|
|
839
|
+
|
|
840
|
+
// Watch pages directory
|
|
841
|
+
watch(pagesDirAbs, { recursive: true }, async (_eventType, filename) => {
|
|
842
|
+
if (filename && filename.endsWith(".jsx")) {
|
|
843
|
+
const changedFile = path.join(pagesDirAbs, filename);
|
|
844
|
+
try {
|
|
845
|
+
await buildFile(changedFile, { liveReload: true, silent: true, wsPort, outputDir, pagesDir: pagesDirAbs });
|
|
846
|
+
await generateUnoCSSFile(outputDir, true);
|
|
847
|
+
console.log(`ā Rebuilt: ${filename}`);
|
|
848
|
+
notifyClients();
|
|
849
|
+
} catch (error) {
|
|
850
|
+
console.error(`ā Build error: ${error.message}`);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
});
|
|
854
|
+
|
|
855
|
+
// Watch public directory
|
|
856
|
+
const publicDirAbs = path.resolve(process.cwd(), "public");
|
|
857
|
+
try {
|
|
858
|
+
await fs.access(publicDirAbs);
|
|
859
|
+
watch(publicDirAbs, { recursive: true }, async (_eventType, filename) => {
|
|
860
|
+
if (filename) {
|
|
861
|
+
try {
|
|
862
|
+
await copyPublicFiles("public", outputDir, true);
|
|
863
|
+
console.log(`ā Copied: public/${filename}`);
|
|
864
|
+
notifyClients();
|
|
865
|
+
} catch (error) {
|
|
866
|
+
console.error(`ā Copy error: ${error.message}`);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
} catch {
|
|
871
|
+
// Public directory doesn't exist, skip watching
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// Create HTTP server
|
|
875
|
+
const outDir = path.resolve(process.cwd(), outputDir);
|
|
876
|
+
|
|
877
|
+
const server = http.createServer(async (req, res) => {
|
|
878
|
+
let requestPath = req.url === "/" ? "/index.html" : req.url;
|
|
879
|
+
let filePath = path.join(outDir, requestPath);
|
|
880
|
+
|
|
881
|
+
try {
|
|
882
|
+
const content = await fs.readFile(filePath);
|
|
883
|
+
const ext = path.extname(filePath);
|
|
884
|
+
const contentTypes = {
|
|
885
|
+
".html": "text/html",
|
|
886
|
+
".css": "text/css",
|
|
887
|
+
".js": "text/javascript",
|
|
888
|
+
".json": "application/json",
|
|
889
|
+
".png": "image/png",
|
|
890
|
+
".jpg": "image/jpeg",
|
|
891
|
+
".gif": "image/gif",
|
|
892
|
+
".svg": "image/svg+xml",
|
|
893
|
+
};
|
|
894
|
+
|
|
895
|
+
res.writeHead(200, { "Content-Type": contentTypes[ext] || "text/plain" });
|
|
896
|
+
res.end(content);
|
|
897
|
+
} catch (error) {
|
|
898
|
+
res.writeHead(404);
|
|
899
|
+
res.end("Not found");
|
|
900
|
+
}
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
server.listen(httpPort, () => {
|
|
904
|
+
console.log(`\nš Server running at http://localhost:${httpPort}`);
|
|
905
|
+
console.log(`š Serving: ${pagesDir}/ ā ${outputDir}/\n`);
|
|
906
|
+
});
|
|
907
|
+
} else {
|
|
908
|
+
// Single file mode
|
|
909
|
+
if (!inputFile) {
|
|
910
|
+
console.error("Error: Please specify a file or pages directory to serve");
|
|
911
|
+
console.error("Usage: mini-jsx dev <file|pages> [options]");
|
|
912
|
+
process.exit(1);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
// Initial build with live reload
|
|
916
|
+
await buildFile(inputFile, { liveReload: true, wsPort, outputDir });
|
|
917
|
+
|
|
918
|
+
// Generate UnoCSS
|
|
919
|
+
await generateUnoCSSFile(outputDir);
|
|
920
|
+
|
|
921
|
+
// Setup WebSocket server for live reload
|
|
922
|
+
const wss = new WebSocketServer({ port: wsPort });
|
|
923
|
+
const clients = new Set();
|
|
924
|
+
|
|
925
|
+
wss.on("connection", (ws) => {
|
|
926
|
+
clients.add(ws);
|
|
927
|
+
ws.on("close", () => clients.delete(ws));
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
function notifyClients() {
|
|
931
|
+
clients.forEach((client) => {
|
|
932
|
+
if (client.readyState === 1) {
|
|
933
|
+
client.send("reload");
|
|
934
|
+
}
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
// Watch for changes
|
|
939
|
+
const absolutePath = path.resolve(process.cwd(), inputFile);
|
|
940
|
+
const watchDir = path.dirname(absolutePath);
|
|
941
|
+
|
|
942
|
+
console.log(`\nš Watching for changes in ${watchDir}...`);
|
|
943
|
+
|
|
944
|
+
const { watch } = await import("node:fs");
|
|
945
|
+
watch(watchDir, { recursive: true }, async (_eventType, filename) => {
|
|
946
|
+
if (filename && filename.endsWith(".jsx")) {
|
|
947
|
+
try {
|
|
948
|
+
await buildFile(inputFile, { liveReload: true, silent: true, wsPort, outputDir });
|
|
949
|
+
await generateUnoCSSFile(outputDir, true);
|
|
950
|
+
console.log(`ā Rebuilt: ${filename}`);
|
|
951
|
+
notifyClients();
|
|
952
|
+
} catch (error) {
|
|
953
|
+
console.error(`ā Build error: ${error.message}`);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
});
|
|
957
|
+
|
|
958
|
+
// Create HTTP server
|
|
959
|
+
const outDir = path.resolve(process.cwd(), outputDir);
|
|
960
|
+
const inputBasename = path.basename(inputFile, ".jsx");
|
|
961
|
+
const outputFilename = `${inputBasename}.html`;
|
|
962
|
+
const relativeOutput = path.relative(process.cwd(), path.join(outDir, outputFilename));
|
|
963
|
+
|
|
964
|
+
const server = http.createServer(async (req, res) => {
|
|
965
|
+
let filePath = path.join(outDir, req.url === "/" ? outputFilename : req.url);
|
|
966
|
+
|
|
967
|
+
try {
|
|
968
|
+
const content = await fs.readFile(filePath);
|
|
969
|
+
const ext = path.extname(filePath);
|
|
970
|
+
const contentTypes = {
|
|
971
|
+
".html": "text/html",
|
|
972
|
+
".css": "text/css",
|
|
973
|
+
".js": "text/javascript",
|
|
974
|
+
".json": "application/json",
|
|
975
|
+
".png": "image/png",
|
|
976
|
+
".jpg": "image/jpeg",
|
|
977
|
+
".gif": "image/gif",
|
|
978
|
+
".svg": "image/svg+xml",
|
|
979
|
+
};
|
|
980
|
+
|
|
981
|
+
res.writeHead(200, { "Content-Type": contentTypes[ext] || "text/plain" });
|
|
982
|
+
res.end(content);
|
|
983
|
+
} catch (error) {
|
|
984
|
+
res.writeHead(404);
|
|
985
|
+
res.end("Not found");
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
server.listen(httpPort, () => {
|
|
990
|
+
console.log(`\nš Server running at http://localhost:${httpPort}`);
|
|
991
|
+
console.log(`š Serving: ${relativeOutput}\n`);
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
// Keep process running
|
|
996
|
+
process.on("SIGINT", () => {
|
|
997
|
+
console.log("\n\nš Shutting down...");
|
|
998
|
+
process.exit(0);
|
|
999
|
+
});
|
|
1000
|
+
} catch (error) {
|
|
1001
|
+
console.error(`Error: ${error.message}`);
|
|
1002
|
+
if (error.stack) {
|
|
1003
|
+
console.error(error.stack);
|
|
1004
|
+
}
|
|
1005
|
+
process.exit(1);
|
|
1006
|
+
}
|
|
1007
|
+
} else {
|
|
1008
|
+
console.error(`Unknown command: ${command}`);
|
|
1009
|
+
console.error('Run "mini-jsx --help" for usage information');
|
|
1010
|
+
process.exit(1);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
main();
|