@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/README.md +35 -320
- package/package.json +14 -13
- package/src/barrels.js +230 -0
- package/src/browser/compiler.js +71 -193
- package/src/browser/unocss.js +5 -1
- package/src/builder.js +110 -232
- package/src/bundler.js +215 -64
- package/src/cli.js +22 -191
- package/src/commands/build.js +141 -0
- package/src/commands/dev.js +54 -0
- package/src/constants.js +74 -0
- package/src/jsx-runtime.js +15 -5
- package/src/parser.js +555 -0
- package/src/renderer.js +29 -48
- package/src/server.js +88 -74
- package/src/transformer.js +1 -18
- package/src/unocss.js +20 -24
- package/src/utils.js +55 -0
- package/src/watcher.js +98 -125
- package/src/content.js +0 -272
- package/src/resolver.js +0 -137
package/src/server.js
CHANGED
|
@@ -1,99 +1,113 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Dev server
|
|
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 {
|
|
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<
|
|
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 =
|
|
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
|
|
38
|
+
const server = createServer(async (req, res) => {
|
|
39
|
+
const url = (req.url || "/").split("?")[0];
|
|
25
40
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
51
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
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
|
-
|
|
76
|
-
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
109
|
+
server.removeListener("error", onError);
|
|
110
|
+
resolvePromise({ server, port, reload });
|
|
97
111
|
});
|
|
98
112
|
});
|
|
99
113
|
}
|
package/src/transformer.js
CHANGED
|
@@ -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
|
|
2
|
+
* UnoCSS Integration for Ono
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { createGenerator
|
|
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 {
|
|
10
|
+
import { pathToFileURL } from "node:url";
|
|
11
|
+
import { createRequire } from "node:module";
|
|
9
12
|
|
|
10
|
-
const
|
|
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(
|
|
21
|
+
return await fs.readFile(require.resolve("@unocss/reset/tailwind.css"), "utf-8");
|
|
21
22
|
} catch {
|
|
22
|
-
|
|
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<
|
|
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
|
-
*
|
|
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
|
-
|
|
51
|
-
|
|
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
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
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
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
let
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
*
|
|
33
|
+
* Trigger onRebuild callback and browser reload.
|
|
34
|
+
* @param {{ onRebuild?: Function, reload?: Function }} opts
|
|
34
35
|
*/
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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 =
|
|
51
|
+
const { outputDir = DIRS.OUTPUT, onRebuild, reload } = options;
|
|
48
52
|
|
|
49
53
|
const pagesDir = resolve(process.cwd(), inputPattern);
|
|
50
|
-
const
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
82
|
+
let publicWatcher;
|
|
97
83
|
try {
|
|
98
|
-
|
|
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 =
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
}
|