@hashrock/ono 0.1.1 → 0.1.2

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/builder.js ADDED
@@ -0,0 +1,317 @@
1
+ /**
2
+ * Build utilities for Ono SSG
3
+ */
4
+ import { readFile, writeFile, mkdir, readdir, stat } from "node:fs/promises";
5
+ import { resolve, join, dirname, basename, relative, extname } from "node:path";
6
+ import { transformJSX } from "./transformer.js";
7
+ import { renderToString } from "./renderer.js";
8
+ import { generateCSSFromFiles } from "./unocss.js";
9
+
10
+ /**
11
+ * Check if a route is dynamic (contains [param])
12
+ */
13
+ export function isDynamicRoute(filePath) {
14
+ return /\[([^\]]+)\]/.test(filePath);
15
+ }
16
+
17
+ // Inline JSX runtime for bundled output
18
+ const INLINE_JSX_RUNTIME = `
19
+ function flattenChildren(children) {
20
+ const result = [];
21
+ for (const child of children) {
22
+ if (child === null || child === undefined || typeof child === 'boolean') continue;
23
+ if (Array.isArray(child)) {
24
+ result.push(...flattenChildren(child));
25
+ } else {
26
+ result.push(child);
27
+ }
28
+ }
29
+ return result;
30
+ }
31
+ function h(tag, props, ...children) {
32
+ return { tag, props: props || {}, children: flattenChildren(children) };
33
+ }
34
+ `;
35
+
36
+ /**
37
+ * Build a single JSX file
38
+ */
39
+ export async function buildFile(inputFile, options = {}) {
40
+ const { outputDir = "dist", unocssConfig, silent = false } = options;
41
+
42
+ const outDir = resolve(process.cwd(), outputDir);
43
+ const resolvedInput = resolve(process.cwd(), inputFile);
44
+
45
+ // Read and transform JSX
46
+ const jsx = await readFile(resolvedInput, "utf-8");
47
+ const transformed = await transformJSX(jsx, resolvedInput);
48
+
49
+ // Remove import statements for local components (they need to be bundled separately)
50
+ // For now, just add the h function inline
51
+ const codeWithRuntime = INLINE_JSX_RUNTIME + transformed;
52
+
53
+ // Write transformed JS temporarily
54
+ const tempFile = join(outDir, `_temp_${Date.now()}.js`);
55
+ await mkdir(dirname(tempFile), { recursive: true });
56
+ await writeFile(tempFile, codeWithRuntime);
57
+
58
+ // Import and render
59
+ const moduleUrl = new URL(`file://${tempFile}?t=${Date.now()}`);
60
+ const module = await import(moduleUrl.href);
61
+
62
+ const App = module.default;
63
+ if (!App) {
64
+ throw new Error(`No default export found in ${inputFile}`);
65
+ }
66
+
67
+ let vnode = typeof App === "function" ? App({}) : App;
68
+ if (vnode instanceof Promise) {
69
+ vnode = await vnode;
70
+ }
71
+
72
+ const html = renderToString(vnode);
73
+
74
+ // Determine output path
75
+ const baseName = basename(inputFile, ".jsx");
76
+ const outputFileName = baseName === "index" ? "index.html" : `${baseName}.html`;
77
+ const outputPath = join(outDir, outputFileName);
78
+
79
+ await mkdir(dirname(outputPath), { recursive: true });
80
+ await writeFile(outputPath, html);
81
+
82
+ if (!silent) {
83
+ console.log(`✓ Built successfully: ${relative(process.cwd(), outputPath)}`);
84
+ }
85
+
86
+ // Clean up temp file
87
+ await import("node:fs/promises").then((fs) => fs.unlink(tempFile).catch(() => {}));
88
+
89
+ return { outputPath, html };
90
+ }
91
+
92
+ /**
93
+ * Build a dynamic route (e.g., [slug].jsx)
94
+ */
95
+ export async function buildDynamicRoute(inputFile, options = {}) {
96
+ const { outputDir = "dist", silent = false } = options;
97
+
98
+ const outDir = resolve(process.cwd(), outputDir);
99
+ const resolvedInput = resolve(process.cwd(), inputFile);
100
+
101
+ // Read and transform JSX
102
+ const jsx = await readFile(resolvedInput, "utf-8");
103
+ const transformed = await transformJSX(jsx, resolvedInput);
104
+
105
+ // Add inline JSX runtime
106
+ const codeWithRuntime = INLINE_JSX_RUNTIME + transformed;
107
+
108
+ // Write transformed JS temporarily
109
+ const tempFile = join(outDir, `_temp_${Date.now()}.js`);
110
+ await mkdir(dirname(tempFile), { recursive: true });
111
+ await writeFile(tempFile, codeWithRuntime);
112
+
113
+ // Import module
114
+ const moduleUrl = new URL(`file://${tempFile}?t=${Date.now()}`);
115
+ const module = await import(moduleUrl.href);
116
+
117
+ if (!module.getStaticPaths) {
118
+ throw new Error(
119
+ `Dynamic route ${inputFile} must export a getStaticPaths function`
120
+ );
121
+ }
122
+
123
+ const pathsData = await module.getStaticPaths();
124
+ const paths = Array.isArray(pathsData) ? pathsData : pathsData.paths || [];
125
+
126
+ const App = module.default;
127
+ if (!App) {
128
+ throw new Error(`No default export found in ${inputFile}`);
129
+ }
130
+
131
+ const outputs = [];
132
+
133
+ for (const pathData of paths) {
134
+ const params = pathData.params || {};
135
+
136
+ let vnode = typeof App === "function" ? App({ params }) : App;
137
+ if (vnode instanceof Promise) {
138
+ vnode = await vnode;
139
+ }
140
+
141
+ const html = renderToString(vnode);
142
+
143
+ // Determine output path from params
144
+ const routeDir = dirname(relative(join(process.cwd(), "pages"), resolvedInput));
145
+ const fileName = basename(resolvedInput, ".jsx");
146
+
147
+ // Replace [param] with actual value
148
+ let outputPath = fileName;
149
+ for (const [key, value] of Object.entries(params)) {
150
+ outputPath = outputPath.replace(`[${key}]`, value);
151
+ }
152
+
153
+ const fullOutputPath = join(
154
+ outDir,
155
+ routeDir === "." ? "" : routeDir,
156
+ `${outputPath}.html`
157
+ );
158
+
159
+ await mkdir(dirname(fullOutputPath), { recursive: true });
160
+ await writeFile(fullOutputPath, html);
161
+
162
+ outputs.push({ outputPath: fullOutputPath, html, params });
163
+
164
+ if (!silent) {
165
+ console.log(` ✓ ${relative(process.cwd(), fullOutputPath)}`);
166
+ }
167
+ }
168
+
169
+ // Clean up temp file
170
+ await import("node:fs/promises").then((fs) => fs.unlink(tempFile).catch(() => {}));
171
+
172
+ return outputs;
173
+ }
174
+
175
+ /**
176
+ * Build multiple JSX files
177
+ */
178
+ export async function buildFiles(inputPattern, options = {}) {
179
+ const { outputDir = "dist", unocssConfig, silent = false } = options;
180
+
181
+ const pagesDir = resolve(process.cwd(), inputPattern);
182
+ const files = await getAllJSXFiles(pagesDir);
183
+
184
+ if (!silent) {
185
+ console.log(`Found ${files.length} page(s) in ${inputPattern}/\n`);
186
+ }
187
+
188
+ const results = [];
189
+
190
+ for (const file of files) {
191
+ if (isDynamicRoute(file)) {
192
+ if (!silent) {
193
+ const relativePath = relative(process.cwd(), file);
194
+ const pathsData = await getDynamicRoutePaths(file);
195
+ const count = Array.isArray(pathsData) ? pathsData.length : pathsData.paths?.length || 0;
196
+ console.log(`Building dynamic route ${relativePath} (${count} pages)...`);
197
+ }
198
+ const outputs = await buildDynamicRoute(file, { outputDir, silent: true });
199
+ results.push(...outputs);
200
+ } else {
201
+ if (!silent) {
202
+ console.log(`Building ${relative(process.cwd(), file)}...`);
203
+ }
204
+ const result = await buildFile(file, { outputDir, unocssConfig, silent: true });
205
+ results.push(result);
206
+ }
207
+ }
208
+
209
+ return results;
210
+ }
211
+
212
+ /**
213
+ * Get all JSX files recursively
214
+ */
215
+ async function getAllJSXFiles(dir) {
216
+ const files = [];
217
+ const entries = await readdir(dir, { withFileTypes: true });
218
+
219
+ for (const entry of entries) {
220
+ const fullPath = join(dir, entry.name);
221
+
222
+ if (entry.isDirectory()) {
223
+ const subFiles = await getAllJSXFiles(fullPath);
224
+ files.push(...subFiles);
225
+ } else if (entry.isFile() && entry.name.endsWith(".jsx")) {
226
+ files.push(fullPath);
227
+ }
228
+ }
229
+
230
+ return files;
231
+ }
232
+
233
+ /**
234
+ * Helper to get paths from a dynamic route
235
+ */
236
+ /**
237
+ * Helper to get paths from a dynamic route
238
+ */
239
+ export async function getDynamicRoutePaths(file) {
240
+ const outDir = resolve(process.cwd(), "dist");
241
+ const jsx = await readFile(file, "utf-8");
242
+ const transformed = await transformJSX(jsx, file);
243
+
244
+ // Add inline JSX runtime
245
+ const codeWithRuntime = INLINE_JSX_RUNTIME + transformed;
246
+
247
+ const tempFile = join(outDir, `_temp_paths_${Date.now()}.js`);
248
+ await mkdir(dirname(tempFile), { recursive: true });
249
+ await writeFile(tempFile, codeWithRuntime);
250
+
251
+ const moduleUrl = new URL(`file://${tempFile}?t=${Date.now()}`);
252
+ const module = await import(moduleUrl.href);
253
+
254
+ const pathsData = module.getStaticPaths ? await module.getStaticPaths() : [];
255
+
256
+ await import("node:fs/promises").then((fs) => fs.unlink(tempFile).catch(() => {}));
257
+
258
+ return pathsData;
259
+ }
260
+
261
+ /**
262
+ * Generate UnoCSS file
263
+ */
264
+ export async function generateUnoCSS(options = {}) {
265
+ const { outputDir = "dist", unocssConfig, silent = false } = options;
266
+
267
+ const outDir = resolve(process.cwd(), outputDir);
268
+
269
+ // Scan all HTML files
270
+ const htmlFiles = await getAllHTMLFiles(outDir);
271
+
272
+ if (htmlFiles.length === 0) {
273
+ return null;
274
+ }
275
+
276
+ // Generate CSS from HTML files
277
+ const css = await generateCSSFromFiles(htmlFiles, unocssConfig);
278
+
279
+ if (!css) {
280
+ return null;
281
+ }
282
+
283
+ const cssPath = join(outDir, "uno.css");
284
+ await writeFile(cssPath, css);
285
+
286
+ if (!silent) {
287
+ console.log(`\n⚡ Generated UnoCSS: ${relative(process.cwd(), cssPath)}`);
288
+ }
289
+
290
+ return cssPath;
291
+ }
292
+
293
+ /**
294
+ * Get all HTML files recursively
295
+ */
296
+ async function getAllHTMLFiles(dir) {
297
+ const files = [];
298
+
299
+ try {
300
+ const entries = await readdir(dir, { withFileTypes: true });
301
+
302
+ for (const entry of entries) {
303
+ const fullPath = join(dir, entry.name);
304
+
305
+ if (entry.isDirectory()) {
306
+ const subFiles = await getAllHTMLFiles(fullPath);
307
+ files.push(...subFiles);
308
+ } else if (entry.isFile() && entry.name.endsWith(".html")) {
309
+ files.push(fullPath);
310
+ }
311
+ }
312
+ } catch (error) {
313
+ // Directory might not exist yet
314
+ }
315
+
316
+ return files;
317
+ }
package/src/bundler.js CHANGED
@@ -48,16 +48,24 @@ export async function bundle(entryFile) {
48
48
  }
49
49
 
50
50
  /**
51
- * Remove import statements from code
51
+ * Remove import statements from code (but keep package imports)
52
52
  * @param {string} code - JavaScript code
53
- * @returns {string} Code without imports
53
+ * @returns {string} Code with relative imports removed, package imports kept
54
54
  */
55
55
  function removeImports(code) {
56
- // Remove import statements
56
+ // Remove only relative import statements, keep package imports
57
57
  const lines = code.split("\n");
58
58
  const filteredLines = lines.filter(line => {
59
59
  const trimmed = line.trim();
60
- return !trimmed.startsWith("import ") && trimmed !== "import";
60
+ if (!trimmed.startsWith("import ")) return true;
61
+
62
+ // Keep package imports (not starting with . or /)
63
+ const importMatch = trimmed.match(/from\s+['"]([^'"]+)['"]/);
64
+ if (importMatch && !importMatch[1].startsWith('.') && !importMatch[1].startsWith('/')) {
65
+ return true; // Keep package import
66
+ }
67
+
68
+ return false; // Remove relative import
61
69
  });
62
70
 
63
71
  return filteredLines.join("\n");