@hashrock/ono 0.1.1 → 0.1.3
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/package.json +18 -4
- package/src/browser/compiler.js +253 -0
- package/src/browser/unocss.js +29 -0
- package/src/builder.js +316 -0
- package/src/bundler.js +12 -4
- package/src/cli.js +178 -945
- package/src/content.js +272 -0
- package/src/jsx-runtime.js +19 -0
- package/src/renderer.js +11 -4
- package/src/server.js +99 -0
- package/src/unocss.js +28 -2
- package/src/watcher.js +167 -0
- package/README.md +0 -299
package/package.json
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hashrock/ono",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "Minimalist SSG framework with JSX, powered by TypeScript's JSX transformer",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/jsx-runtime.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/jsx-runtime.js",
|
|
9
|
+
"./jsx-runtime": "./src/jsx-runtime.js",
|
|
10
|
+
"./renderer": "./src/renderer.js",
|
|
11
|
+
"./transformer": "./src/transformer.js",
|
|
12
|
+
"./bundler": "./src/bundler.js",
|
|
13
|
+
"./content": "./src/content.js",
|
|
14
|
+
"./browser/compiler": "./src/browser/compiler.js",
|
|
15
|
+
"./browser/unocss": "./src/browser/unocss.js"
|
|
16
|
+
},
|
|
7
17
|
"bin": {
|
|
8
18
|
"ono": "src/cli.js"
|
|
9
19
|
},
|
|
@@ -14,7 +24,10 @@
|
|
|
14
24
|
],
|
|
15
25
|
"scripts": {
|
|
16
26
|
"test": "node --test test/**/*.test.js",
|
|
17
|
-
"test:watch": "node --test --watch test/**/*.test.js"
|
|
27
|
+
"test:watch": "node --test --watch test/**/*.test.js",
|
|
28
|
+
"test:snapshot": "node --test test/**/*.snapshot.test.js",
|
|
29
|
+
"test:snapshot:update": "UPDATE_SNAPSHOTS=true node --test test/**/*.snapshot.test.js",
|
|
30
|
+
"test:all": "npm run test && npm run test:snapshot"
|
|
18
31
|
},
|
|
19
32
|
"keywords": [
|
|
20
33
|
"jsx",
|
|
@@ -38,9 +51,10 @@
|
|
|
38
51
|
"engines": {
|
|
39
52
|
"node": ">=18.0.0"
|
|
40
53
|
},
|
|
41
|
-
"devDependencies": {},
|
|
42
54
|
"dependencies": {
|
|
43
55
|
"@unocss/preset-uno": "^66.5.4",
|
|
56
|
+
"h3": "^2.0.1-rc.5",
|
|
57
|
+
"marked": "^16.4.1",
|
|
44
58
|
"typescript": "^5.9.3",
|
|
45
59
|
"unocss": "^66.5.4",
|
|
46
60
|
"ws": "^8.18.3"
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser compiler utilities shared with the REPL worker.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { transformJSX } from '../transformer.js';
|
|
6
|
+
import { renderToString } from '../renderer.js';
|
|
7
|
+
import { h } from '../jsx-runtime.js';
|
|
8
|
+
import { getUnoGenerator } from './unocss.js';
|
|
9
|
+
|
|
10
|
+
function resolveImport(from, to) {
|
|
11
|
+
if (to.startsWith('./') || to.startsWith('../')) {
|
|
12
|
+
const fromParts = from.split('/').slice(0, -1);
|
|
13
|
+
const targetParts = to.split('/');
|
|
14
|
+
const resolved = [...fromParts];
|
|
15
|
+
|
|
16
|
+
for (const part of targetParts) {
|
|
17
|
+
if (!part || part === '.') continue;
|
|
18
|
+
if (part === '..') {
|
|
19
|
+
resolved.pop();
|
|
20
|
+
} else {
|
|
21
|
+
resolved.push(part);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return resolved.join('/');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return to;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function topoSortModules(files, entryPoint) {
|
|
32
|
+
const filenames = Object.keys(files);
|
|
33
|
+
const dependencyMap = new Map();
|
|
34
|
+
const importPattern = /import\s+(?:[\s\S]+?)?from\s+['"]([^'"]+)['"]/g;
|
|
35
|
+
|
|
36
|
+
for (const filename of filenames) {
|
|
37
|
+
const source = files[filename] || '';
|
|
38
|
+
const deps = [];
|
|
39
|
+
source.replace(importPattern, (match, specifier) => {
|
|
40
|
+
const resolved = resolveImport(filename, specifier);
|
|
41
|
+
if (files[resolved]) {
|
|
42
|
+
deps.push(resolved);
|
|
43
|
+
}
|
|
44
|
+
return match;
|
|
45
|
+
});
|
|
46
|
+
dependencyMap.set(filename, deps);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const visited = new Set();
|
|
50
|
+
const order = [];
|
|
51
|
+
|
|
52
|
+
const visit = (file) => {
|
|
53
|
+
if (!files[file] || visited.has(file)) return;
|
|
54
|
+
visited.add(file);
|
|
55
|
+
const deps = dependencyMap.get(file) || [];
|
|
56
|
+
for (const dep of deps) {
|
|
57
|
+
visit(dep);
|
|
58
|
+
}
|
|
59
|
+
order.push(file);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
if (entryPoint) {
|
|
63
|
+
visit(entryPoint);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
for (const filename of filenames) {
|
|
67
|
+
if (!visited.has(filename)) {
|
|
68
|
+
visit(filename);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return order;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function bundleModules(files, entryPoint) {
|
|
76
|
+
const order = topoSortModules(files, entryPoint);
|
|
77
|
+
let bundledCode = 'const __modules = {};\n';
|
|
78
|
+
|
|
79
|
+
for (const filename of order) {
|
|
80
|
+
const transformedCode = transformJSX(files[filename], filename);
|
|
81
|
+
let code = transformedCode;
|
|
82
|
+
const exportMappings = new Map();
|
|
83
|
+
|
|
84
|
+
const addExport = (exportName, localName = exportName) => {
|
|
85
|
+
if (!exportName || exportMappings.has(exportName)) return;
|
|
86
|
+
exportMappings.set(exportName, localName);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
code = code.replace(/import\s+{([^}]+)}\s+from\s+['"]([^'"]+)['"]/g, (match, imports, path) => {
|
|
90
|
+
const resolvedPath = resolveImport(filename, path);
|
|
91
|
+
const importNames = imports.split(',').map(part => part.trim()).filter(Boolean);
|
|
92
|
+
|
|
93
|
+
return importNames
|
|
94
|
+
.map(name => {
|
|
95
|
+
const [local, alias] = name.split(/\s+as\s+/).map(token => token && token.trim());
|
|
96
|
+
const localName = alias || local;
|
|
97
|
+
const exportName = local;
|
|
98
|
+
return `const ${localName} = __modules['${resolvedPath}']['${exportName}'];`;
|
|
99
|
+
})
|
|
100
|
+
.join('\n');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
code = code.replace(/import\s+(\w+)\s+from\s+['"]([^'"]+)['"]/g, (match, localName, path) => {
|
|
104
|
+
const resolvedPath = resolveImport(filename, path);
|
|
105
|
+
return `const ${localName} = __modules['${resolvedPath}']['default'];`;
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
code = code.replace(/import\s+\*\s+as\s+(\w+)\s+from\s+['"]([^'"]+)['"]/g, (match, localName, path) => {
|
|
109
|
+
const resolvedPath = resolveImport(filename, path);
|
|
110
|
+
return `const ${localName} = __modules['${resolvedPath}'];`;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
code = code.replace(/export\s+default\s+function\s+([A-Za-z_$][\w$]*)/g, (match, name) => {
|
|
114
|
+
addExport('default', name);
|
|
115
|
+
return `function ${name}`;
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
code = code.replace(/export\s+default\s+([A-Za-z_$][\w$]*)/g, (match, name) => {
|
|
119
|
+
addExport('default', name);
|
|
120
|
+
return `${name}`;
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
code = code.replace(/export\s+(const|let|var)\s+([A-Za-z_$][\w$]*)/g, (match, kind, name) => {
|
|
124
|
+
addExport(name);
|
|
125
|
+
return `${kind} ${name}`;
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
code = code.replace(/export\s+function\s+([A-Za-z_$][\w$]*)/g, (match, name) => {
|
|
129
|
+
addExport(name);
|
|
130
|
+
return `function ${name}`;
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
code = code.replace(/export\s+class\s+([A-Za-z_$][\w$]*)/g, (match, name) => {
|
|
134
|
+
addExport(name);
|
|
135
|
+
return `class ${name}`;
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
code = code.replace(/export\s*{\s*([^}]+)\s*};?/g, (match, names) => {
|
|
139
|
+
names
|
|
140
|
+
.split(',')
|
|
141
|
+
.map(part => part.trim())
|
|
142
|
+
.filter(Boolean)
|
|
143
|
+
.forEach(part => {
|
|
144
|
+
const [local, alias] = part.split(/\s+as\s+/).map(token => token && token.trim());
|
|
145
|
+
addExport(alias || local, local);
|
|
146
|
+
});
|
|
147
|
+
return '';
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
code = code.replace(/export\s*{\s*};?/g, '');
|
|
151
|
+
|
|
152
|
+
if (filename === entryPoint) {
|
|
153
|
+
const functionMatches = code.matchAll(/function\s+([A-Za-z_$][\w$]*)/g);
|
|
154
|
+
for (const match of functionMatches) {
|
|
155
|
+
const name = match[1];
|
|
156
|
+
addExport(name);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const exportEntries = Array.from(exportMappings.entries()).map(([exportName, localName]) => {
|
|
161
|
+
if (exportName === 'default') {
|
|
162
|
+
return `'default': ${localName}`;
|
|
163
|
+
}
|
|
164
|
+
if (exportName === localName) {
|
|
165
|
+
return exportName;
|
|
166
|
+
}
|
|
167
|
+
return `'${exportName}': ${localName}`;
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const exportsObject = exportEntries.length > 0 ? `{ ${exportEntries.join(', ')} }` : '{}';
|
|
171
|
+
|
|
172
|
+
bundledCode += `
|
|
173
|
+
__modules['${filename}'] = (() => {
|
|
174
|
+
${code}
|
|
175
|
+
return ${exportsObject};
|
|
176
|
+
})();
|
|
177
|
+
`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
bundledCode += `const __entry = __modules['${entryPoint}'];\n`;
|
|
181
|
+
|
|
182
|
+
return bundledCode;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function extractRenderCandidate(entryModule, entryCode) {
|
|
186
|
+
if (!entryModule || typeof entryModule !== 'object') {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const defaultExport = entryModule.default;
|
|
191
|
+
if (typeof defaultExport === 'function') {
|
|
192
|
+
return defaultExport;
|
|
193
|
+
}
|
|
194
|
+
if (defaultExport !== undefined) {
|
|
195
|
+
return () => defaultExport;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const lastCallMatch = entryCode.match(/([A-Za-z_$][\w$]*)\s*\([^)]*\)\s*$/m);
|
|
199
|
+
if (lastCallMatch) {
|
|
200
|
+
const lastCallName = lastCallMatch[1];
|
|
201
|
+
const candidate = entryModule[lastCallName];
|
|
202
|
+
if (typeof candidate === 'function') {
|
|
203
|
+
return candidate;
|
|
204
|
+
}
|
|
205
|
+
if (candidate !== undefined) {
|
|
206
|
+
return () => candidate;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const fallbackFunction = Object.values(entryModule).find(value => typeof value === 'function');
|
|
211
|
+
if (fallbackFunction) {
|
|
212
|
+
return fallbackFunction;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const firstValue = Object.values(entryModule)[0];
|
|
216
|
+
if (firstValue !== undefined) {
|
|
217
|
+
return () => firstValue;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function evaluateEntryModule(bundledCode) {
|
|
224
|
+
const getEntryModule = new Function('h', `
|
|
225
|
+
${bundledCode}
|
|
226
|
+
return __entry;
|
|
227
|
+
`);
|
|
228
|
+
|
|
229
|
+
return getEntryModule(h);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export async function compileProject(files, entryPoint = 'index.jsx', options = {}) {
|
|
233
|
+
const bundled = bundleModules(files, entryPoint);
|
|
234
|
+
const entryModule = evaluateEntryModule(bundled);
|
|
235
|
+
const entryCode = files[entryPoint] || '';
|
|
236
|
+
const renderCandidate = extractRenderCandidate(entryModule, entryCode);
|
|
237
|
+
|
|
238
|
+
if (!renderCandidate) {
|
|
239
|
+
throw new Error('Unable to determine a render function in the entry module.');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const vnode = renderCandidate();
|
|
243
|
+
const html = renderToString(vnode);
|
|
244
|
+
|
|
245
|
+
let css = '';
|
|
246
|
+
if (options.enableUno !== false) {
|
|
247
|
+
const uno = await getUnoGenerator(options.unoConfig);
|
|
248
|
+
const result = await uno.generate(html, { preflights: true });
|
|
249
|
+
css = result.css;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return { html, css };
|
|
253
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight UnoCSS helpers for browser environments.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { createGenerator } from '@unocss/core';
|
|
6
|
+
import { presetUno } from '@unocss/preset-uno';
|
|
7
|
+
|
|
8
|
+
let cachedGeneratorKey = null;
|
|
9
|
+
let cachedGeneratorPromise = null;
|
|
10
|
+
|
|
11
|
+
function serializeConfig(config = {}) {
|
|
12
|
+
try {
|
|
13
|
+
return JSON.stringify(config);
|
|
14
|
+
} catch {
|
|
15
|
+
return '__dynamic__';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function getUnoGenerator(config = {}) {
|
|
20
|
+
const key = serializeConfig(config);
|
|
21
|
+
if (!cachedGeneratorPromise || cachedGeneratorKey !== key) {
|
|
22
|
+
cachedGeneratorKey = key;
|
|
23
|
+
cachedGeneratorPromise = createGenerator({
|
|
24
|
+
presets: [presetUno()],
|
|
25
|
+
...config,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return cachedGeneratorPromise;
|
|
29
|
+
}
|
package/src/builder.js
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
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
|
+
import { bundle } from "./bundler.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Check if a route is dynamic (contains [param])
|
|
13
|
+
*/
|
|
14
|
+
export function isDynamicRoute(filePath) {
|
|
15
|
+
return /\[([^\]]+)\]/.test(filePath);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Inline JSX runtime for bundled output
|
|
19
|
+
const INLINE_JSX_RUNTIME = `
|
|
20
|
+
function flattenChildren(children) {
|
|
21
|
+
const result = [];
|
|
22
|
+
for (const child of children) {
|
|
23
|
+
if (child === null || child === undefined || typeof child === 'boolean') continue;
|
|
24
|
+
if (Array.isArray(child)) {
|
|
25
|
+
result.push(...flattenChildren(child));
|
|
26
|
+
} else {
|
|
27
|
+
result.push(child);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
function h(tag, props, ...children) {
|
|
33
|
+
return { tag, props: props || {}, children: flattenChildren(children) };
|
|
34
|
+
}
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Build a single JSX file
|
|
39
|
+
*/
|
|
40
|
+
export async function buildFile(inputFile, options = {}) {
|
|
41
|
+
const { outputDir = "dist", unocssConfig, silent = false } = options;
|
|
42
|
+
|
|
43
|
+
const outDir = resolve(process.cwd(), outputDir);
|
|
44
|
+
const resolvedInput = resolve(process.cwd(), inputFile);
|
|
45
|
+
|
|
46
|
+
// Bundle the file with all its dependencies
|
|
47
|
+
const bundledCode = await bundle(resolvedInput);
|
|
48
|
+
|
|
49
|
+
// Add inline JSX runtime
|
|
50
|
+
const codeWithRuntime = INLINE_JSX_RUNTIME + bundledCode;
|
|
51
|
+
|
|
52
|
+
// Write transformed JS temporarily
|
|
53
|
+
const tempFile = join(outDir, `_temp_${Date.now()}.js`);
|
|
54
|
+
await mkdir(dirname(tempFile), { recursive: true });
|
|
55
|
+
await writeFile(tempFile, codeWithRuntime);
|
|
56
|
+
|
|
57
|
+
// Import and render
|
|
58
|
+
const moduleUrl = new URL(`file://${tempFile}?t=${Date.now()}`);
|
|
59
|
+
const module = await import(moduleUrl.href);
|
|
60
|
+
|
|
61
|
+
const App = module.default;
|
|
62
|
+
if (!App) {
|
|
63
|
+
throw new Error(`No default export found in ${inputFile}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let vnode = typeof App === "function" ? App({}) : App;
|
|
67
|
+
if (vnode instanceof Promise) {
|
|
68
|
+
vnode = await vnode;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const html = renderToString(vnode);
|
|
72
|
+
|
|
73
|
+
// Determine output path
|
|
74
|
+
const baseName = basename(inputFile, ".jsx");
|
|
75
|
+
const outputFileName = baseName === "index" ? "index.html" : `${baseName}.html`;
|
|
76
|
+
const outputPath = join(outDir, outputFileName);
|
|
77
|
+
|
|
78
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
79
|
+
await writeFile(outputPath, html);
|
|
80
|
+
|
|
81
|
+
if (!silent) {
|
|
82
|
+
console.log(`✓ Built successfully: ${relative(process.cwd(), outputPath)}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Clean up temp file
|
|
86
|
+
await import("node:fs/promises").then((fs) => fs.unlink(tempFile).catch(() => {}));
|
|
87
|
+
|
|
88
|
+
return { outputPath, html };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Build a dynamic route (e.g., [slug].jsx)
|
|
93
|
+
*/
|
|
94
|
+
export async function buildDynamicRoute(inputFile, options = {}) {
|
|
95
|
+
const { outputDir = "dist", silent = false } = options;
|
|
96
|
+
|
|
97
|
+
const outDir = resolve(process.cwd(), outputDir);
|
|
98
|
+
const resolvedInput = resolve(process.cwd(), inputFile);
|
|
99
|
+
|
|
100
|
+
// Bundle the file with all its dependencies
|
|
101
|
+
const bundledCode = await bundle(resolvedInput);
|
|
102
|
+
|
|
103
|
+
// Add inline JSX runtime
|
|
104
|
+
const codeWithRuntime = INLINE_JSX_RUNTIME + bundledCode;
|
|
105
|
+
|
|
106
|
+
// Write transformed JS temporarily
|
|
107
|
+
const tempFile = join(outDir, `_temp_${Date.now()}.js`);
|
|
108
|
+
await mkdir(dirname(tempFile), { recursive: true });
|
|
109
|
+
await writeFile(tempFile, codeWithRuntime);
|
|
110
|
+
|
|
111
|
+
// Import module
|
|
112
|
+
const moduleUrl = new URL(`file://${tempFile}?t=${Date.now()}`);
|
|
113
|
+
const module = await import(moduleUrl.href);
|
|
114
|
+
|
|
115
|
+
if (!module.getStaticPaths) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
`Dynamic route ${inputFile} must export a getStaticPaths function`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const pathsData = await module.getStaticPaths();
|
|
122
|
+
const paths = Array.isArray(pathsData) ? pathsData : pathsData.paths || [];
|
|
123
|
+
|
|
124
|
+
const App = module.default;
|
|
125
|
+
if (!App) {
|
|
126
|
+
throw new Error(`No default export found in ${inputFile}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const outputs = [];
|
|
130
|
+
|
|
131
|
+
for (const pathData of paths) {
|
|
132
|
+
const params = pathData.params || {};
|
|
133
|
+
|
|
134
|
+
let vnode = typeof App === "function" ? App({ params }) : App;
|
|
135
|
+
if (vnode instanceof Promise) {
|
|
136
|
+
vnode = await vnode;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const html = renderToString(vnode);
|
|
140
|
+
|
|
141
|
+
// Determine output path from params
|
|
142
|
+
const routeDir = dirname(relative(join(process.cwd(), "pages"), resolvedInput));
|
|
143
|
+
const fileName = basename(resolvedInput, ".jsx");
|
|
144
|
+
|
|
145
|
+
// Replace [param] with actual value
|
|
146
|
+
let outputPath = fileName;
|
|
147
|
+
for (const [key, value] of Object.entries(params)) {
|
|
148
|
+
outputPath = outputPath.replace(`[${key}]`, value);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const fullOutputPath = join(
|
|
152
|
+
outDir,
|
|
153
|
+
routeDir === "." ? "" : routeDir,
|
|
154
|
+
`${outputPath}.html`
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
await mkdir(dirname(fullOutputPath), { recursive: true });
|
|
158
|
+
await writeFile(fullOutputPath, html);
|
|
159
|
+
|
|
160
|
+
outputs.push({ outputPath: fullOutputPath, html, params });
|
|
161
|
+
|
|
162
|
+
if (!silent) {
|
|
163
|
+
console.log(` ✓ ${relative(process.cwd(), fullOutputPath)}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Clean up temp file
|
|
168
|
+
await import("node:fs/promises").then((fs) => fs.unlink(tempFile).catch(() => {}));
|
|
169
|
+
|
|
170
|
+
return outputs;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Build multiple JSX files
|
|
175
|
+
*/
|
|
176
|
+
export async function buildFiles(inputPattern, options = {}) {
|
|
177
|
+
const { outputDir = "dist", unocssConfig, silent = false } = options;
|
|
178
|
+
|
|
179
|
+
const pagesDir = resolve(process.cwd(), inputPattern);
|
|
180
|
+
const files = await getAllJSXFiles(pagesDir);
|
|
181
|
+
|
|
182
|
+
if (!silent) {
|
|
183
|
+
console.log(`Found ${files.length} page(s) in ${inputPattern}/\n`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const results = [];
|
|
187
|
+
|
|
188
|
+
for (const file of files) {
|
|
189
|
+
if (isDynamicRoute(file)) {
|
|
190
|
+
if (!silent) {
|
|
191
|
+
const relativePath = relative(process.cwd(), file);
|
|
192
|
+
const pathsData = await getDynamicRoutePaths(file);
|
|
193
|
+
const count = Array.isArray(pathsData) ? pathsData.length : pathsData.paths?.length || 0;
|
|
194
|
+
console.log(`Building dynamic route ${relativePath} (${count} pages)...`);
|
|
195
|
+
}
|
|
196
|
+
const outputs = await buildDynamicRoute(file, { outputDir, silent: true });
|
|
197
|
+
results.push(...outputs);
|
|
198
|
+
} else {
|
|
199
|
+
if (!silent) {
|
|
200
|
+
console.log(`Building ${relative(process.cwd(), file)}...`);
|
|
201
|
+
}
|
|
202
|
+
const result = await buildFile(file, { outputDir, unocssConfig, silent: true });
|
|
203
|
+
results.push(result);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return results;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Get all JSX files recursively
|
|
212
|
+
*/
|
|
213
|
+
async function getAllJSXFiles(dir) {
|
|
214
|
+
const files = [];
|
|
215
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
216
|
+
|
|
217
|
+
for (const entry of entries) {
|
|
218
|
+
const fullPath = join(dir, entry.name);
|
|
219
|
+
|
|
220
|
+
if (entry.isDirectory()) {
|
|
221
|
+
const subFiles = await getAllJSXFiles(fullPath);
|
|
222
|
+
files.push(...subFiles);
|
|
223
|
+
} else if (entry.isFile() && entry.name.endsWith(".jsx")) {
|
|
224
|
+
files.push(fullPath);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return files;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Helper to get paths from a dynamic route
|
|
233
|
+
*/
|
|
234
|
+
/**
|
|
235
|
+
* Helper to get paths from a dynamic route
|
|
236
|
+
*/
|
|
237
|
+
export async function getDynamicRoutePaths(file) {
|
|
238
|
+
const outDir = resolve(process.cwd(), "dist");
|
|
239
|
+
|
|
240
|
+
// Bundle the file with all its dependencies
|
|
241
|
+
const bundledCode = await bundle(file);
|
|
242
|
+
|
|
243
|
+
// Add inline JSX runtime
|
|
244
|
+
const codeWithRuntime = INLINE_JSX_RUNTIME + bundledCode;
|
|
245
|
+
|
|
246
|
+
const tempFile = join(outDir, `_temp_paths_${Date.now()}.js`);
|
|
247
|
+
await mkdir(dirname(tempFile), { recursive: true });
|
|
248
|
+
await writeFile(tempFile, codeWithRuntime);
|
|
249
|
+
|
|
250
|
+
const moduleUrl = new URL(`file://${tempFile}?t=${Date.now()}`);
|
|
251
|
+
const module = await import(moduleUrl.href);
|
|
252
|
+
|
|
253
|
+
const pathsData = module.getStaticPaths ? await module.getStaticPaths() : [];
|
|
254
|
+
|
|
255
|
+
await import("node:fs/promises").then((fs) => fs.unlink(tempFile).catch(() => {}));
|
|
256
|
+
|
|
257
|
+
return pathsData;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Generate UnoCSS file
|
|
262
|
+
*/
|
|
263
|
+
export async function generateUnoCSS(options = {}) {
|
|
264
|
+
const { outputDir = "dist", unocssConfig, silent = false } = options;
|
|
265
|
+
|
|
266
|
+
const outDir = resolve(process.cwd(), outputDir);
|
|
267
|
+
|
|
268
|
+
// Scan all HTML files
|
|
269
|
+
const htmlFiles = await getAllHTMLFiles(outDir);
|
|
270
|
+
|
|
271
|
+
if (htmlFiles.length === 0) {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Generate CSS from HTML files
|
|
276
|
+
const css = await generateCSSFromFiles(htmlFiles, unocssConfig);
|
|
277
|
+
|
|
278
|
+
if (!css) {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const cssPath = join(outDir, "uno.css");
|
|
283
|
+
await writeFile(cssPath, css);
|
|
284
|
+
|
|
285
|
+
if (!silent) {
|
|
286
|
+
console.log(`\n⚡ Generated UnoCSS: ${relative(process.cwd(), cssPath)}`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return cssPath;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Get all HTML files recursively
|
|
294
|
+
*/
|
|
295
|
+
async function getAllHTMLFiles(dir) {
|
|
296
|
+
const files = [];
|
|
297
|
+
|
|
298
|
+
try {
|
|
299
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
300
|
+
|
|
301
|
+
for (const entry of entries) {
|
|
302
|
+
const fullPath = join(dir, entry.name);
|
|
303
|
+
|
|
304
|
+
if (entry.isDirectory()) {
|
|
305
|
+
const subFiles = await getAllHTMLFiles(fullPath);
|
|
306
|
+
files.push(...subFiles);
|
|
307
|
+
} else if (entry.isFile() && entry.name.endsWith(".html")) {
|
|
308
|
+
files.push(fullPath);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
} catch (error) {
|
|
312
|
+
// Directory might not exist yet
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return files;
|
|
316
|
+
}
|
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
|
|
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
|
-
|
|
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");
|