@ozsarman/clarityjs 0.6.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 +178 -0
- package/package.json +168 -0
- package/src/analyze.js +534 -0
- package/src/async-state.js +555 -0
- package/src/bundle-runtime.js +35 -0
- package/src/clarity-bundle.js +332 -0
- package/src/clarity-test.js +622 -0
- package/src/cli.js +453 -0
- package/src/codegen.js +1934 -0
- package/src/dev-server.js +362 -0
- package/src/devtools.js +765 -0
- package/src/edge.js +606 -0
- package/src/error-overlay.js +535 -0
- package/src/file-conventions.js +472 -0
- package/src/font.js +513 -0
- package/src/game-loop.js +106 -0
- package/src/head.js +393 -0
- package/src/hydrate.js +292 -0
- package/src/i18n.js +403 -0
- package/src/image.js +352 -0
- package/src/index.js +193 -0
- package/src/islands.js +284 -0
- package/src/isr.js +306 -0
- package/src/layout.js +342 -0
- package/src/lexer.js +572 -0
- package/src/linter.js +547 -0
- package/src/pages-router.js +229 -0
- package/src/parser.js +1108 -0
- package/src/router.js +732 -0
- package/src/runtime.js +1465 -0
- package/src/scoped-css.js +641 -0
- package/src/server-actions.js +439 -0
- package/src/server-data.js +225 -0
- package/src/sourcemap.js +130 -0
- package/src/ssg.js +310 -0
- package/src/ssr.js +621 -0
- package/src/store.js +276 -0
- package/src/transitions.js +438 -0
- package/src/ts-plugin.js +613 -0
- package/src/typegen.js +240 -0
- package/src/vite-plugin.js +447 -0
- package/types/index.d.ts +366 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clarity.js Build Bundler
|
|
3
|
+
*
|
|
4
|
+
* Compiles all .clarity files in a directory and produces a production bundle
|
|
5
|
+
* with tree-shaking, dead-code elimination, and minification.
|
|
6
|
+
*
|
|
7
|
+
* Strategy
|
|
8
|
+
* ─────────
|
|
9
|
+
* 1. Compile every .clarity → .js using the Clarity compiler
|
|
10
|
+
* 2. Bundle with esbuild (if available) or produce a concatenated
|
|
11
|
+
* plain-JS bundle (zero-dep fallback)
|
|
12
|
+
* 3. In production mode, minify with esbuild's built-in minifier
|
|
13
|
+
* 4. Output a single dist/bundle.js (+ optional source map)
|
|
14
|
+
*
|
|
15
|
+
* esbuild is an optional peer dependency:
|
|
16
|
+
* npm install --save-dev esbuild
|
|
17
|
+
*
|
|
18
|
+
* Without esbuild the bundler still works — it compiles + concatenates files
|
|
19
|
+
* with an ES-module shim for environments that support <script type="module">.
|
|
20
|
+
*
|
|
21
|
+
* CLI (via clarity cli):
|
|
22
|
+
* clarity bundle <entry> [--out dist/] [--minify] [--sourcemap] [--watch]
|
|
23
|
+
*
|
|
24
|
+
* Programmatic:
|
|
25
|
+
* import { bundle } from '@ozsarman/clarityjs/bundle'
|
|
26
|
+
* await bundle({ entry: './examples/demo/main.js', outdir: './dist' })
|
|
27
|
+
*
|
|
28
|
+
* Author: Claude (Anthropic)
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import fs from 'fs';
|
|
32
|
+
import path from 'path';
|
|
33
|
+
import { compile } from './index.js';
|
|
34
|
+
|
|
35
|
+
const C = {
|
|
36
|
+
reset: '\x1b[0m', green: '\x1b[32m', yellow: '\x1b[33m',
|
|
37
|
+
red: '\x1b[31m', cyan: '\x1b[36m', gray: '\x1b[90m', bold: '\x1b[1m',
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function log(color, label, msg) {
|
|
41
|
+
const ts = new Date().toLocaleTimeString('en', { hour12: false });
|
|
42
|
+
console.log(`${C.gray}${ts}${C.reset} ${color}${label}${C.reset} ${msg}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ─── Step 1: Compile .clarity files to .js ───────────────────────────────────
|
|
46
|
+
/**
|
|
47
|
+
* Compile all .clarity files found in a directory tree.
|
|
48
|
+
* Returns a Map of { outputPath → compiledCode }.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} dir Root directory to scan
|
|
51
|
+
* @param {object} opts Compile options (runtimePath, sourceMap)
|
|
52
|
+
* @returns {{ compiled: Map<string,string>, errors: {file,message}[] }}
|
|
53
|
+
*/
|
|
54
|
+
export function compileDir(dir, opts = {}) {
|
|
55
|
+
const compiled = new Map();
|
|
56
|
+
const errors = [];
|
|
57
|
+
|
|
58
|
+
function scan(d) {
|
|
59
|
+
for (const entry of fs.readdirSync(d, { withFileTypes: true })) {
|
|
60
|
+
const full = path.join(d, entry.name);
|
|
61
|
+
if (entry.isDirectory()) { scan(full); continue; }
|
|
62
|
+
if (!entry.name.endsWith('.clarity')) continue;
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const src = fs.readFileSync(full, 'utf8');
|
|
66
|
+
const outPath = full.replace(/\.clarity$/, '.js');
|
|
67
|
+
const { code } = compile(src, {
|
|
68
|
+
filename: full,
|
|
69
|
+
sourceFile: full,
|
|
70
|
+
outputFile: outPath,
|
|
71
|
+
runtimePath: opts.runtimePath ?? './clarity-runtime.js',
|
|
72
|
+
routerPath: opts.routerPath ?? './clarity-router.js',
|
|
73
|
+
sourceMap: opts.sourceMap ?? false,
|
|
74
|
+
...opts,
|
|
75
|
+
});
|
|
76
|
+
compiled.set(outPath, code);
|
|
77
|
+
log(C.green, '✓ compiled', path.relative(dir, full));
|
|
78
|
+
} catch (err) {
|
|
79
|
+
errors.push({ file: full, message: err.message });
|
|
80
|
+
log(C.red, '✗ error', `${path.relative(dir, full)}: ${err.message}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
scan(dir);
|
|
86
|
+
return { compiled, errors };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ─── Step 2: Write compiled .js files ────────────────────────────────────────
|
|
90
|
+
/**
|
|
91
|
+
* Write all compiled files to disk (replaces .clarity → .js in-place).
|
|
92
|
+
*/
|
|
93
|
+
export function writeCompiled(compiled) {
|
|
94
|
+
for (const [outPath, code] of compiled) {
|
|
95
|
+
fs.writeFileSync(outPath, code, 'utf8');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ─── Step 3: Bundle with esbuild ─────────────────────────────────────────────
|
|
100
|
+
/**
|
|
101
|
+
* Bundle an entry-point file with esbuild.
|
|
102
|
+
* esbuild is an optional peer dep — gracefully falls back if absent.
|
|
103
|
+
*
|
|
104
|
+
* @param {object} opts
|
|
105
|
+
* @param {string} opts.entry Entry-point JS file (after clarity compile)
|
|
106
|
+
* @param {string} opts.outfile Output bundle path (default: dist/bundle.js)
|
|
107
|
+
* @param {boolean} opts.minify Minify output (default: false)
|
|
108
|
+
* @param {boolean} opts.sourcemap Emit source map (default: false)
|
|
109
|
+
* @param {string} opts.target JS target (default: 'es2020')
|
|
110
|
+
* @param {string[]} opts.external External package names (default: [])
|
|
111
|
+
* @returns {Promise<{ok: boolean, outfile: string, fallback?: boolean}>}
|
|
112
|
+
*/
|
|
113
|
+
export async function bundleWithEsbuild(opts = {}) {
|
|
114
|
+
const {
|
|
115
|
+
entry,
|
|
116
|
+
outfile = 'dist/bundle.js',
|
|
117
|
+
minify = false,
|
|
118
|
+
sourcemap = false,
|
|
119
|
+
target = 'es2020',
|
|
120
|
+
external = [],
|
|
121
|
+
} = opts;
|
|
122
|
+
|
|
123
|
+
// Ensure output directory exists
|
|
124
|
+
fs.mkdirSync(path.dirname(outfile), { recursive: true });
|
|
125
|
+
|
|
126
|
+
// Try esbuild
|
|
127
|
+
try {
|
|
128
|
+
const esbuild = await import('esbuild');
|
|
129
|
+
log(C.cyan, '⚙ esbuild', `${entry} → ${outfile}${minify ? ' (minified)' : ''}`);
|
|
130
|
+
|
|
131
|
+
const result = await esbuild.build({
|
|
132
|
+
entryPoints: [entry],
|
|
133
|
+
bundle: true,
|
|
134
|
+
outfile,
|
|
135
|
+
minify,
|
|
136
|
+
sourcemap,
|
|
137
|
+
target,
|
|
138
|
+
external,
|
|
139
|
+
format: 'esm',
|
|
140
|
+
treeShaking: true,
|
|
141
|
+
logLevel: 'warning',
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
if (result.errors.length > 0) {
|
|
145
|
+
result.errors.forEach(e => log(C.red, '✗ esbuild', e.text));
|
|
146
|
+
return { ok: false, outfile };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const size = fs.statSync(outfile).size;
|
|
150
|
+
log(C.green, '✓ bundle', `${outfile} (${_fmtBytes(size)}${minify ? ', minified' : ''})`);
|
|
151
|
+
return { ok: true, outfile };
|
|
152
|
+
} catch (err) {
|
|
153
|
+
if (err.code === 'ERR_MODULE_NOT_FOUND' || err.message?.includes('Cannot find module')) {
|
|
154
|
+
log(C.yellow, '⚠ esbuild', 'not installed — using fallback concatenation bundler');
|
|
155
|
+
log(C.yellow, ' tip', 'npm install --save-dev esbuild for full tree-shaking + minification');
|
|
156
|
+
return bundleFallback({ entry, outfile, sourcemap });
|
|
157
|
+
}
|
|
158
|
+
throw err;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ─── Fallback: concatenation bundler (no esbuild) ────────────────────────────
|
|
163
|
+
/**
|
|
164
|
+
* Zero-dependency fallback bundler.
|
|
165
|
+
* Resolves local ES module imports recursively and concatenates into one file.
|
|
166
|
+
* Does NOT tree-shake or minify — suitable for development or small projects.
|
|
167
|
+
*/
|
|
168
|
+
export async function bundleFallback({ entry, outfile = 'dist/bundle.js', sourcemap = false } = {}) {
|
|
169
|
+
fs.mkdirSync(path.dirname(outfile), { recursive: true });
|
|
170
|
+
const visited = new Set();
|
|
171
|
+
const chunks = [];
|
|
172
|
+
|
|
173
|
+
function collect(filePath) {
|
|
174
|
+
const abs = path.resolve(filePath);
|
|
175
|
+
if (visited.has(abs)) return;
|
|
176
|
+
visited.add(abs);
|
|
177
|
+
|
|
178
|
+
let code;
|
|
179
|
+
try {
|
|
180
|
+
code = fs.readFileSync(abs, 'utf8');
|
|
181
|
+
} catch (_) {
|
|
182
|
+
log(C.yellow, '⚠ skip', `cannot read ${abs}`);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Resolve local imports first (depth-first)
|
|
187
|
+
const importRe = /^import\s+.*?from\s+['"](\.[^'"]+)['"]/gm;
|
|
188
|
+
let m;
|
|
189
|
+
while ((m = importRe.exec(code)) !== null) {
|
|
190
|
+
const importPath = m[1];
|
|
191
|
+
const resolved = path.resolve(path.dirname(abs), importPath);
|
|
192
|
+
// Try with extension variants
|
|
193
|
+
for (const ext of ['', '.js', '.mjs', '/index.js']) {
|
|
194
|
+
if (fs.existsSync(resolved + ext)) {
|
|
195
|
+
collect(resolved + ext);
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Strip import declarations (already collected above) and export keywords
|
|
202
|
+
const stripped = code
|
|
203
|
+
.replace(/^import\s+.*?from\s+['"][^'"]+['"];?\s*\n?/gm, '')
|
|
204
|
+
.replace(/^export\s+default\s+/gm, 'var _default_export = ')
|
|
205
|
+
.replace(/^export\s+\{[^}]*\};?\s*\n?/gm, '')
|
|
206
|
+
.replace(/^export\s+(function|class|const|let|var)\s+/gm, '$1 ');
|
|
207
|
+
|
|
208
|
+
chunks.push(`/* ${path.relative(process.cwd(), abs)} */\n${stripped}`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
collect(entry);
|
|
212
|
+
const bundle = chunks.join('\n\n');
|
|
213
|
+
fs.writeFileSync(outfile, bundle, 'utf8');
|
|
214
|
+
const size = fs.statSync(outfile).size;
|
|
215
|
+
log(C.green, '✓ bundle', `${outfile} (${_fmtBytes(size)}, fallback — no tree-shaking)`);
|
|
216
|
+
return { ok: true, outfile, fallback: true };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ─── Main bundle() orchestrator ──────────────────────────────────────────────
|
|
220
|
+
/**
|
|
221
|
+
* Full build pipeline:
|
|
222
|
+
* 1. Compile all .clarity files in the project directory
|
|
223
|
+
* 2. Write compiled .js files
|
|
224
|
+
* 3. Bundle the entry-point with esbuild (or fallback)
|
|
225
|
+
* 4. Optionally watch for changes and rebuild
|
|
226
|
+
*
|
|
227
|
+
* @param {object} opts
|
|
228
|
+
* @param {string} opts.entry Entry-point .js file (e.g. './examples/demo/main.js')
|
|
229
|
+
* @param {string} opts.projectDir Directory containing .clarity sources (default: cwd)
|
|
230
|
+
* @param {string} opts.outfile Bundle output path (default: 'dist/bundle.js')
|
|
231
|
+
* @param {boolean} opts.minify Minify (default: false in dev, true in prod)
|
|
232
|
+
* @param {boolean} opts.sourcemap Emit source map (default: true)
|
|
233
|
+
* @param {boolean} opts.watch Re-bundle on file changes (default: false)
|
|
234
|
+
* @param {string} opts.mode 'development' | 'production' (default: 'development')
|
|
235
|
+
* @returns {Promise<{ ok: boolean, outfile: string, errors: [] }>}
|
|
236
|
+
*/
|
|
237
|
+
export async function bundle(opts = {}) {
|
|
238
|
+
const {
|
|
239
|
+
entry,
|
|
240
|
+
projectDir = process.cwd(),
|
|
241
|
+
outfile = 'dist/bundle.js',
|
|
242
|
+
minify = (opts.mode === 'production'),
|
|
243
|
+
sourcemap = true,
|
|
244
|
+
watch = false,
|
|
245
|
+
mode = 'development',
|
|
246
|
+
} = opts;
|
|
247
|
+
|
|
248
|
+
if (!entry) throw new Error('[Clarity Bundle] opts.entry is required');
|
|
249
|
+
|
|
250
|
+
const startMs = Date.now();
|
|
251
|
+
console.log(`\n${C.bold}${C.cyan}Clarity.js Build${C.reset} (${mode})\n`);
|
|
252
|
+
|
|
253
|
+
// ── Phase 1: Compile .clarity files ──────────────────────────────────────
|
|
254
|
+
const { compiled, errors } = compileDir(projectDir, { sourceMap: sourcemap && mode !== 'production' });
|
|
255
|
+
writeCompiled(compiled);
|
|
256
|
+
console.log('');
|
|
257
|
+
|
|
258
|
+
if (errors.length > 0 && compiled.size === 0) {
|
|
259
|
+
console.error(`${C.red}Build failed — ${errors.length} compile error(s)${C.reset}`);
|
|
260
|
+
return { ok: false, outfile, errors };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// ── Phase 2: Bundle ───────────────────────────────────────────────────────
|
|
264
|
+
const result = await bundleWithEsbuild({ entry, outfile, minify, sourcemap, target: 'es2020' });
|
|
265
|
+
|
|
266
|
+
const elapsed = Date.now() - startMs;
|
|
267
|
+
if (result.ok) {
|
|
268
|
+
console.log(`\n${C.green}✓ Build complete${C.reset} in ${elapsed}ms`);
|
|
269
|
+
if (errors.length > 0) {
|
|
270
|
+
console.log(`${C.yellow}⚠ ${errors.length} compile error(s) — bundle may be incomplete${C.reset}`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ── Phase 3: Watch mode ───────────────────────────────────────────────────
|
|
275
|
+
if (watch) {
|
|
276
|
+
log(C.cyan, '◆ watching', projectDir);
|
|
277
|
+
try {
|
|
278
|
+
fs.watch(projectDir, { recursive: true }, async (event, filename) => {
|
|
279
|
+
if (!filename) return;
|
|
280
|
+
if (filename.endsWith('.clarity') || filename.endsWith('.js') && !filename.endsWith('.bundle.js')) {
|
|
281
|
+
log(C.yellow, '◆ changed', filename);
|
|
282
|
+
// Incremental compile + re-bundle
|
|
283
|
+
if (filename.endsWith('.clarity')) {
|
|
284
|
+
const full = path.join(projectDir, filename);
|
|
285
|
+
try {
|
|
286
|
+
const src = fs.readFileSync(full, 'utf8');
|
|
287
|
+
const outPath = full.replace(/\.clarity$/, '.js');
|
|
288
|
+
const { code } = compile(src, { filename: full, runtimePath: './clarity-runtime.js' });
|
|
289
|
+
fs.writeFileSync(outPath, code);
|
|
290
|
+
log(C.green, '✓ compiled', filename);
|
|
291
|
+
} catch (e) {
|
|
292
|
+
log(C.red, '✗ error', e.message);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
await bundleWithEsbuild({ entry, outfile, minify, sourcemap, target: 'es2020' });
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
} catch (_) {
|
|
300
|
+
log(C.yellow, '⚠ watch', 'recursive watch not available in this environment');
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return { ok: result.ok, outfile, errors };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
308
|
+
function _fmtBytes(bytes) {
|
|
309
|
+
if (bytes < 1024) return bytes + ' B';
|
|
310
|
+
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
|
311
|
+
return (bytes / (1024 * 1024)).toFixed(2) + ' MB';
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// ─── CLI entry (when called directly) ────────────────────────────────────────
|
|
315
|
+
// Usage: node src/clarity-bundle.js <entry> [--out dist/bundle.js] [--minify] [--watch]
|
|
316
|
+
if (import.meta.url === new URL(process.argv[1], 'file://').href) {
|
|
317
|
+
const args = process.argv.slice(2);
|
|
318
|
+
const entry = args.find(a => !a.startsWith('--'));
|
|
319
|
+
const outfile = args[args.indexOf('--out') + 1] ?? 'dist/bundle.js';
|
|
320
|
+
const minify = args.includes('--minify');
|
|
321
|
+
const watch = args.includes('--watch');
|
|
322
|
+
const mode = minify ? 'production' : 'development';
|
|
323
|
+
|
|
324
|
+
if (!entry) {
|
|
325
|
+
console.error('Usage: node clarity-bundle.js <entry.js> [--out dist/bundle.js] [--minify] [--watch]');
|
|
326
|
+
process.exit(1);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
bundle({ entry, outfile, minify, watch, mode, sourcemap: true })
|
|
330
|
+
.then(({ ok }) => { if (!ok && !watch) process.exit(1); })
|
|
331
|
+
.catch(err => { console.error(err); process.exit(1); });
|
|
332
|
+
}
|