@hashrock/ono 0.1.2 → 0.2.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/src/server.js CHANGED
@@ -1,99 +1,113 @@
1
1
  /**
2
- * Dev server using h3
2
+ * Dev server - static files plus SSE live reload, no dependencies.
3
+ *
4
+ * The server injects a small EventSource script into served HTML and
5
+ * exposes a reload() function that broadcasts to connected browsers.
3
6
  */
4
- import { createApp, createRouter, eventHandler, setResponseStatus, setResponseHeader, createError } from "h3";
5
- import { toNodeHandler } from "h3/node";
6
7
  import { createServer } from "node:http";
7
- import { resolve, join, extname } from "node:path";
8
+ import { resolve, join, extname, normalize } from "node:path";
8
9
  import { readFile } from "node:fs/promises";
10
+ import { DIRS, PORTS, MIME_TYPES } from "./constants.js";
11
+
12
+ const RELOAD_PATH = "/__ono_reload";
13
+ const RELOAD_SCRIPT = `<script>new EventSource("${RELOAD_PATH}").onmessage = () => location.reload();</script>`;
14
+
15
+ /** @param {string} html */
16
+ function injectReloadScript(html) {
17
+ if (/<\/body>/i.test(html)) {
18
+ return html.replace(/<\/body>/i, `${RELOAD_SCRIPT}</body>`);
19
+ }
20
+ return html + RELOAD_SCRIPT;
21
+ }
9
22
 
10
23
  /**
11
24
  * Create a development server
12
- * @param {object} options - Server options
13
- * @param {string} options.outputDir - Output directory to serve
14
- * @param {number} options.port - HTTP port
15
- * @param {string} options.mode - Server mode: 'pages' or 'single'
16
- * @param {string} options.indexFile - Index file for single mode
17
- * @returns {Promise<object>} Server instance
25
+ * @param {Object} options - Server options
26
+ * @param {string} [options.outputDir] - Output directory to serve
27
+ * @param {number} [options.port] - HTTP port
28
+ * @param {string} [options.mode] - Server mode: 'pages' or 'single'
29
+ * @param {string} [options.indexFile] - Index file for single mode
30
+ * @returns {Promise<{server: import('http').Server, port: number, reload: () => void}>}
18
31
  */
19
32
  export async function createDevServer(options) {
20
- const { outputDir = "dist", port = 3000, mode = "pages", indexFile = "index.html" } = options;
33
+ const { outputDir = DIRS.OUTPUT, port = PORTS.SERVER, mode = "pages", indexFile = "index.html" } = options;
21
34
 
22
35
  const outDir = resolve(process.cwd(), outputDir);
36
+ const clients = new Set();
23
37
 
24
- const app = createApp();
38
+ const server = createServer(async (req, res) => {
39
+ const url = (req.url || "/").split("?")[0];
25
40
 
26
- // Serve static files from output directory
27
- app.use(
28
- "/**",
29
- eventHandler(async (event) => {
30
- try {
31
- const url = event.path || "/";
32
- let filePath;
41
+ // SSE endpoint for live reload
42
+ if (url === RELOAD_PATH) {
43
+ res.writeHead(200, {
44
+ "Content-Type": "text/event-stream",
45
+ "Cache-Control": "no-cache",
46
+ Connection: "keep-alive",
47
+ });
48
+ res.write(": connected\n\n");
49
+ clients.add(res);
50
+ req.on("close", () => clients.delete(res));
51
+ return;
52
+ }
33
53
 
34
- if (mode === "single") {
35
- // Single file mode: serve specific file for root
36
- if (url === "/" || url === "") {
37
- filePath = join(outDir, indexFile);
38
- } else {
39
- filePath = join(outDir, url);
40
- }
41
- } else {
42
- // Pages mode: default routing
43
- if (url === "/" || url === "") {
44
- filePath = join(outDir, "index.html");
45
- } else {
46
- filePath = join(outDir, url);
47
- }
48
- }
54
+ const rootFile = mode === "single" ? indexFile : "index.html";
55
+ const relPath = url === "/" ? rootFile : url.endsWith("/") ? join(url, "index.html") : url;
56
+ const filePath = normalize(join(outDir, relPath));
49
57
 
50
- const content = await readFile(filePath);
51
- const ext = extname(filePath);
58
+ if (!filePath.startsWith(outDir)) {
59
+ res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" });
60
+ res.end("Forbidden");
61
+ return;
62
+ }
52
63
 
53
- const contentTypes = {
54
- ".html": "text/html; charset=utf-8",
55
- ".css": "text/css; charset=utf-8",
56
- ".js": "text/javascript; charset=utf-8",
57
- ".json": "application/json; charset=utf-8",
58
- ".png": "image/png",
59
- ".jpg": "image/jpeg",
60
- ".jpeg": "image/jpeg",
61
- ".gif": "image/gif",
62
- ".svg": "image/svg+xml",
63
- ".ico": "image/x-icon",
64
- ".woff": "font/woff",
65
- ".woff2": "font/woff2",
66
- ".ttf": "font/ttf",
67
- ".eot": "application/vnd.ms-fontobject",
68
- ".webp": "image/webp",
69
- };
64
+ try {
65
+ const content = await readFile(filePath);
66
+ const contentType =
67
+ MIME_TYPES[/** @type {keyof typeof MIME_TYPES} */ (extname(filePath))] ||
68
+ "application/octet-stream";
69
+ res.writeHead(200, { "Content-Type": contentType });
70
70
 
71
- setResponseHeader(event, "Content-Type", contentTypes[ext] || "application/octet-stream");
72
- return content;
73
- } catch (error) {
71
+ if (filePath.endsWith(".html")) {
72
+ res.end(injectReloadScript(content.toString()));
73
+ } else {
74
+ res.end(content);
75
+ }
76
+ } catch (error) {
77
+ if (error.code === "ENOENT") {
78
+ res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
79
+ res.end(`Not Found: ${url}`);
80
+ } else {
74
81
  console.error("Server error:", error);
75
- if (error.code === "ENOENT") {
76
- throw createError({
77
- statusCode: 404,
78
- statusMessage: "Not Found",
79
- message: `File not found: ${error.path}`,
80
- });
81
- } else {
82
- throw createError({
83
- statusCode: 500,
84
- statusMessage: "Internal Server Error",
85
- message: error.message,
86
- });
87
- }
82
+ res.writeHead(500, { "Content-Type": "text/plain; charset=utf-8" });
83
+ res.end(`Server Error: ${error.message}`);
88
84
  }
89
- })
90
- );
85
+ }
86
+ });
91
87
 
92
- const server = createServer(toNodeHandler(app));
88
+ /** Tell all connected browsers to reload */
89
+ const reload = () => {
90
+ for (const client of clients) {
91
+ client.write("data: reload\n\n");
92
+ }
93
+ };
93
94
 
94
- return new Promise((resolve) => {
95
+ return new Promise((resolvePromise, rejectPromise) => {
96
+ /** @param {any} err */
97
+ const onError = (err) => {
98
+ if (err.code === "EADDRINUSE") {
99
+ const nextPort = port + 1;
100
+ console.log(`ā„¹ļø Port ${port} is busy, using port ${nextPort} instead`);
101
+ server.removeListener("error", onError);
102
+ createDevServer({ ...options, port: nextPort }).then(resolvePromise, rejectPromise);
103
+ return;
104
+ }
105
+ rejectPromise(err);
106
+ };
107
+ server.once("error", onError);
95
108
  server.listen(port, () => {
96
- resolve({ server, app, port });
109
+ server.removeListener("error", onError);
110
+ resolvePromise({ server, port, reload });
97
111
  });
98
112
  });
99
113
  }
@@ -15,6 +15,7 @@ export function transformJSX(source, filename = 'input.jsx') {
15
15
  const compilerOptions = {
16
16
  jsx: ts.JsxEmit.React,
17
17
  jsxFactory: 'h',
18
+ jsxFragmentFactory: 'Fragment',
18
19
  module: ts.ModuleKind.ESNext,
19
20
  target: ts.ScriptTarget.ESNext,
20
21
  esModuleInterop: true,
@@ -28,21 +29,3 @@ export function transformJSX(source, filename = 'input.jsx') {
28
29
 
29
30
  return result.outputText;
30
31
  }
31
-
32
- /**
33
- * Transform JSX file and add necessary imports if not present
34
- * @param {string} source - JSX source code
35
- * @param {string} [filename='input.jsx'] - Optional filename
36
- * @returns {string} Transformed JavaScript with imports
37
- */
38
- export function transformJSXWithImports(source, filename = 'input.jsx') {
39
- let transformedCode = transformJSX(source, filename);
40
-
41
- // Check if the code uses 'h' function (JSX was transformed)
42
- if (transformedCode.includes('h(') && !source.includes('import') && !source.includes('from')) {
43
- // Add import statement for h function
44
- transformedCode = `import { h } from './jsx-runtime.js';\n${transformedCode}`;
45
- }
46
-
47
- return transformedCode;
48
- }
package/src/unocss.js CHANGED
@@ -1,60 +1,56 @@
1
1
  /**
2
- * UnoCSS Integration for Mini JSX
2
+ * UnoCSS Integration for Ono
3
3
  */
4
4
 
5
- import { createGenerator, presetUno } from "unocss";
5
+ import { createGenerator } from "@unocss/core";
6
+ import { presetUno } from "@unocss/preset-uno";
6
7
  import fs from "node:fs/promises";
8
+ import { existsSync } from "node:fs";
7
9
  import path from "node:path";
8
- import { fileURLToPath } from "node:url";
10
+ import { pathToFileURL } from "node:url";
11
+ import { createRequire } from "node:module";
9
12
 
10
- const __filename = fileURLToPath(import.meta.url);
11
- const __dirname = path.dirname(__filename);
13
+ const require = createRequire(import.meta.url);
12
14
 
13
15
  /**
14
16
  * Get the Tailwind reset CSS
15
17
  * @returns {Promise<string>} Reset CSS content
16
18
  */
17
19
  async function getResetCSS() {
18
- const resetPath = path.resolve(__dirname, "../node_modules/@unocss/reset/tailwind.css");
19
20
  try {
20
- return await fs.readFile(resetPath, "utf-8");
21
+ return await fs.readFile(require.resolve("@unocss/reset/tailwind.css"), "utf-8");
21
22
  } catch {
22
- // Fallback: try to find it relative to the package
23
- try {
24
- const fallbackPath = new URL("../node_modules/@unocss/reset/tailwind.css", import.meta.url);
25
- return await fs.readFile(fileURLToPath(fallbackPath), "utf-8");
26
- } catch {
27
- return "";
28
- }
23
+ return "";
29
24
  }
30
25
  }
31
26
 
32
27
  /**
33
28
  * Create UnoCSS generator with default config
34
29
  * @param {object} userConfig - User configuration
35
- * @returns {Promise<object>} UnoCSS generator instance
30
+ * @returns {Promise<any>} UnoCSS generator instance
36
31
  */
37
32
  export async function createUnoGenerator(userConfig = {}) {
38
33
  return await createGenerator({
39
- presets: [presetUno()],
34
+ presets: [/** @type {any} */ (presetUno())],
40
35
  ...userConfig,
41
36
  });
42
37
  }
43
38
 
44
39
  /**
45
- * Load UnoCSS config from file
46
- * @param {string} configPath - Path to config file
40
+ * Load UnoCSS config from file (defaults to uno.config.js in the project root).
41
+ * Returns an empty config when the file doesn't exist; errors inside an
42
+ * existing config file are NOT swallowed.
43
+ * @param {string} [configPath] - Path to config file
47
44
  * @returns {Promise<object>} Configuration object
48
45
  */
49
46
  export async function loadUnoConfig(configPath) {
50
- try {
51
- const configUrl = `file://${path.resolve(configPath)}?t=${Date.now()}`;
52
- const module = await import(configUrl);
53
- return module.default || module;
54
- } catch (error) {
55
- // Config file doesn't exist, return empty config
47
+ const resolved = path.resolve(process.cwd(), configPath || "uno.config.js");
48
+ if (!existsSync(resolved)) {
56
49
  return {};
57
50
  }
51
+ const configUrl = `${pathToFileURL(resolved).href}?t=${Date.now()}`;
52
+ const module = await import(configUrl);
53
+ return module.default || module;
58
54
  }
59
55
 
60
56
  /**
package/src/utils.js ADDED
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Shared utilities for Ono SSG
3
+ */
4
+ import { readdir } from "node:fs/promises";
5
+ import { join } from "node:path";
6
+
7
+ /**
8
+ * Check if a filename has a JSX extension (.jsx or .tsx)
9
+ * @param {string} filename - The filename to check
10
+ * @returns {boolean} True if the file has a JSX extension
11
+ */
12
+ export function isJSXFile(filename) {
13
+ return filename.endsWith(".jsx") || filename.endsWith(".tsx");
14
+ }
15
+
16
+ /**
17
+ * Check if a filename has an HTML extension
18
+ * @param {string} filename - The filename to check
19
+ * @returns {boolean} True if the file has an HTML extension
20
+ */
21
+ export function isHTMLFile(filename) {
22
+ return filename.endsWith(".html");
23
+ }
24
+
25
+ /**
26
+ * Get all files matching a predicate recursively from a directory
27
+ * @param {string} dir - Directory to search
28
+ * @param {(filename: string) => boolean} predicate - Function to test filenames
29
+ * @returns {Promise<string[]>} Array of file paths
30
+ */
31
+ export async function getFilesRecursively(dir, predicate) {
32
+ const files = [];
33
+
34
+ try {
35
+ const entries = await readdir(dir, { withFileTypes: true });
36
+
37
+ for (const entry of entries) {
38
+ const fullPath = join(dir, entry.name);
39
+
40
+ if (entry.isDirectory()) {
41
+ const subFiles = await getFilesRecursively(fullPath, predicate);
42
+ files.push(...subFiles);
43
+ } else if (entry.isFile() && predicate(entry.name)) {
44
+ files.push(fullPath);
45
+ }
46
+ }
47
+ } catch (error) {
48
+ // Directory might not exist
49
+ if (error.code !== "ENOENT") {
50
+ throw error;
51
+ }
52
+ }
53
+
54
+ return files;
55
+ }
package/src/watcher.js CHANGED
@@ -2,166 +2,139 @@
2
2
  * File watcher utilities for Ono SSG
3
3
  */
4
4
  import { watch } from "node:fs";
5
- import { resolve, join, relative, extname } from "node:path";
6
- import { readdir } from "node:fs/promises";
7
- import { WebSocketServer } from "ws";
8
- import { buildFile, buildFiles, buildDynamicRoute, generateUnoCSS, isDynamicRoute, getDynamicRoutePaths } from "./builder.js";
5
+ import { resolve, join, relative } from "node:path";
6
+ import { buildFile, buildFiles, generateUnoCSS } from "./builder.js";
7
+ import { generateBarrel } from "./barrels.js";
8
+ import { isJSXFile } from "./utils.js";
9
+ import { TIMING, DIRS } from "./constants.js";
9
10
 
10
11
  /**
11
- * Create a WebSocket server for live reload
12
+ * Create a debounced async runner that logs errors instead of throwing.
13
+ * @param {(...args: any[]) => Promise<void> | void} fn
14
+ * @param {number} [ms]
12
15
  */
13
- export function createWebSocketServer(port = 35729) {
14
- let wss;
15
- let actualPort = port;
16
-
17
- try {
18
- wss = new WebSocketServer({ port });
19
- } catch (error) {
20
- if (error.code === "EADDRINUSE") {
21
- actualPort = port + 1;
22
- console.log(`ā„¹ļø WebSocket port ${port} is busy, using port ${actualPort} instead`);
23
- wss = new WebSocketServer({ port: actualPort });
24
- } else {
25
- throw error;
26
- }
27
- }
28
-
29
- return { wss, port: actualPort };
16
+ function debounce(fn, ms = TIMING.DEBOUNCE_MS) {
17
+ /** @type {ReturnType<typeof setTimeout> | undefined} */
18
+ let timeout;
19
+ /** @param {...any} args */
20
+ return (...args) => {
21
+ clearTimeout(timeout);
22
+ timeout = setTimeout(async () => {
23
+ try {
24
+ await fn(...args);
25
+ } catch (error) {
26
+ console.error("āŒ Build error:", error.message);
27
+ }
28
+ }, ms);
29
+ };
30
30
  }
31
31
 
32
32
  /**
33
- * Broadcast reload message to all connected clients
33
+ * Trigger onRebuild callback and browser reload.
34
+ * @param {{ onRebuild?: Function, reload?: Function }} opts
34
35
  */
35
- export function broadcastReload(wss) {
36
- wss.clients.forEach((client) => {
37
- if (client.readyState === 1) {
38
- client.send("reload");
39
- }
40
- });
36
+ async function afterRebuild({ onRebuild, reload }) {
37
+ if (onRebuild) await onRebuild();
38
+ if (reload) reload();
41
39
  }
42
40
 
43
41
  /**
44
42
  * Watch for file changes and rebuild
43
+ * @param {string} inputPattern - Input directory to watch
44
+ * @param {Object} options - Watch options
45
+ * @param {string} [options.outputDir] - Output directory
46
+ * @param {Function} [options.onRebuild] - Callback after rebuild
47
+ * @param {Function} [options.reload] - Live-reload broadcast from the dev server
48
+ * @returns {Promise<{watcher: any, publicWatcher?: any, barrelsWatcher?: any}>}
45
49
  */
46
50
  export async function watchFiles(inputPattern, options = {}) {
47
- const { outputDir = "dist", unocssConfig, onRebuild, wss } = options;
51
+ const { outputDir = DIRS.OUTPUT, onRebuild, reload } = options;
48
52
 
49
53
  const pagesDir = resolve(process.cwd(), inputPattern);
50
- const publicDir = resolve(process.cwd(), "public");
54
+ const buildOpts = { outputDir, inputRoot: pagesDir, silent: false };
55
+ const publicDir = resolve(process.cwd(), DIRS.PUBLIC);
56
+ const barrelsDir = resolve(process.cwd(), DIRS.BARRELS);
51
57
 
52
58
  console.log(`šŸ‘€ Watching for changes in ${inputPattern}/ and public/...`);
53
59
 
54
- // Debounce rebuilds
55
- let rebuildTimeout;
56
- const debouncedRebuild = async (file) => {
57
- clearTimeout(rebuildTimeout);
58
- rebuildTimeout = setTimeout(async () => {
59
- try {
60
- console.log(`\nšŸ“ File changed: ${relative(process.cwd(), file)}`);
61
- console.log("šŸ”„ Rebuilding...\n");
62
-
63
- if (isDynamicRoute(file)) {
64
- const relativePath = relative(process.cwd(), file);
65
- const pathsData = await getDynamicRoutePaths(file);
66
- const count = Array.isArray(pathsData) ? pathsData.length : pathsData.paths?.length || 0;
67
- console.log(`Building dynamic route ${relativePath} (${count} pages)...`);
68
- await buildDynamicRoute(file, { outputDir, silent: true });
69
- } else {
70
- await buildFile(file, { outputDir, unocssConfig, silent: false });
71
- }
72
-
73
- await generateUnoCSS({ outputDir, unocssConfig, silent: false });
74
-
75
- if (onRebuild) {
76
- await onRebuild();
77
- }
78
-
79
- if (wss) {
80
- broadcastReload(wss);
81
- }
82
- } catch (error) {
83
- console.error("āŒ Build error:", error.message);
84
- }
85
- }, 100);
86
- };
60
+ const rebuildPage = debounce(async (file) => {
61
+ console.log(`\nšŸ“ File changed: ${relative(process.cwd(), file)}`);
62
+ console.log("šŸ”„ Rebuilding...\n");
63
+ await buildFile(file, buildOpts);
64
+ await generateUnoCSS({ outputDir });
65
+ await afterRebuild({ onRebuild, reload });
66
+ });
87
67
 
88
- // Watch pages directory
89
- const watcher = watch(pagesDir, { recursive: true }, async (eventType, filename) => {
90
- if (filename && filename.endsWith(".jsx")) {
91
- const filePath = join(pagesDir, filename);
92
- await debouncedRebuild(filePath);
68
+ const rebuildAll = debounce(async (reason) => {
69
+ console.log(`\nšŸ“ ${reason}`);
70
+ console.log("šŸ”„ Rebuilding...\n");
71
+ await buildFiles(inputPattern, buildOpts);
72
+ await generateUnoCSS({ outputDir });
73
+ await afterRebuild({ onRebuild, reload });
74
+ });
75
+
76
+ const watcher = watch(pagesDir, { recursive: true }, (_eventType, filename) => {
77
+ if (filename && isJSXFile(filename)) {
78
+ rebuildPage(join(pagesDir, filename));
93
79
  }
94
80
  });
95
81
 
96
- // Watch public directory if it exists
82
+ let publicWatcher;
97
83
  try {
98
- const publicWatcher = watch(publicDir, { recursive: true }, async (eventType, filename) => {
99
- if (filename) {
100
- console.log(`\nšŸ“ Public file changed: ${filename}`);
101
- console.log("šŸ”„ Rebuilding...\n");
102
-
103
- // Rebuild all files to update references
104
- await buildFiles(inputPattern, { outputDir, unocssConfig, silent: false });
105
- await generateUnoCSS({ outputDir, unocssConfig, silent: false });
106
-
107
- if (onRebuild) {
108
- await onRebuild();
109
- }
110
-
111
- if (wss) {
112
- broadcastReload(wss);
113
- }
114
- }
84
+ publicWatcher = watch(publicDir, { recursive: true }, (_eventType, filename) => {
85
+ if (filename) rebuildAll(`Public file changed: ${filename}`);
115
86
  });
116
-
117
- return { watcher, publicWatcher };
118
- } catch (error) {
87
+ } catch {
119
88
  // Public directory might not exist
120
- return { watcher };
121
89
  }
90
+
91
+ const regenerateBarrel = debounce(async (barrelDir, filename) => {
92
+ console.log(`\nšŸ“ Barrel file changed: ${filename}`);
93
+ console.log("šŸ”„ Regenerating barrel...\n");
94
+ await generateBarrel(barrelDir);
95
+ await buildFiles(inputPattern, buildOpts);
96
+ await generateUnoCSS({ outputDir });
97
+ await afterRebuild({ onRebuild, reload });
98
+ });
99
+
100
+ let barrelsWatcher;
101
+ try {
102
+ barrelsWatcher = watch(barrelsDir, { recursive: true }, (_eventType, filename) => {
103
+ if (filename && isJSXFile(filename)) {
104
+ const barrelName = filename.split("/")[0];
105
+ regenerateBarrel(join(barrelsDir, barrelName), filename);
106
+ }
107
+ });
108
+ } catch {
109
+ // Barrels directory might not exist
110
+ }
111
+
112
+ return { watcher, publicWatcher, barrelsWatcher };
122
113
  }
123
114
 
124
115
  /**
125
116
  * Watch a single file for changes
117
+ * @param {string} inputFile - Input file to watch
118
+ * @param {Object} options - Watch options
119
+ * @param {string} [options.outputDir] - Output directory
120
+ * @param {Function} [options.onRebuild] - Callback after rebuild
121
+ * @param {Function} [options.reload] - Live-reload broadcast from the dev server
122
+ * @returns {Promise<{watcher: any}>}
126
123
  */
127
124
  export async function watchFile(inputFile, options = {}) {
128
- const { outputDir = "dist", unocssConfig, onRebuild, wss } = options;
129
-
125
+ const { outputDir = DIRS.OUTPUT, onRebuild, reload } = options;
126
+ const buildOpts = { outputDir, silent: false };
130
127
  const resolvedInput = resolve(process.cwd(), inputFile);
131
128
 
132
129
  console.log(`šŸ‘€ Watching for changes in ${inputFile}...`);
133
130
 
134
- // Debounce rebuilds
135
- let rebuildTimeout;
136
- const debouncedRebuild = async () => {
137
- clearTimeout(rebuildTimeout);
138
- rebuildTimeout = setTimeout(async () => {
139
- try {
140
- console.log(`\nšŸ“ File changed: ${inputFile}`);
141
- console.log("šŸ”„ Rebuilding...\n");
142
-
143
- if (isDynamicRoute(inputFile)) {
144
- await buildDynamicRoute(resolvedInput, { outputDir, silent: false });
145
- } else {
146
- await buildFile(resolvedInput, { outputDir, unocssConfig, silent: false });
147
- }
148
-
149
- await generateUnoCSS({ outputDir, unocssConfig, silent: false });
150
-
151
- if (onRebuild) {
152
- await onRebuild();
153
- }
154
-
155
- if (wss) {
156
- broadcastReload(wss);
157
- }
158
- } catch (error) {
159
- console.error("āŒ Build error:", error.message);
160
- }
161
- }, 100);
162
- };
163
-
164
- const watcher = watch(resolvedInput, debouncedRebuild);
131
+ const rebuild = debounce(async () => {
132
+ console.log(`\nšŸ“ File changed: ${inputFile}`);
133
+ console.log("šŸ”„ Rebuilding...\n");
134
+ await buildFile(resolvedInput, buildOpts);
135
+ await generateUnoCSS({ outputDir });
136
+ await afterRebuild({ onRebuild, reload });
137
+ });
165
138
 
166
- return { watcher };
139
+ return { watcher: watch(resolvedInput, rebuild) };
167
140
  }