0x-lang 0.1.15 → 0.1.17
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 +142 -32
- package/dist/compiler.js +2 -0
- package/dist/compiler.js.map +1 -1
- package/dist/generators/react.js +82 -18
- package/dist/generators/react.js.map +1 -1
- package/dist/generators/shared.d.ts +21 -0
- package/dist/generators/shared.js +118 -0
- package/dist/generators/shared.js.map +1 -1
- package/dist/generators/svelte.js +92 -20
- package/dist/generators/svelte.js.map +1 -1
- package/dist/generators/vue.js +88 -22
- package/dist/generators/vue.js.map +1 -1
- package/dist/parser.js +76 -21
- package/dist/parser.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
8
|
<strong>Write 18 lines. Get 96 lines of production React.</strong><br/>
|
|
9
|
-
A
|
|
9
|
+
A full-stack language that compiles to React, Vue 3, Svelte 5, Express, React Native, and Terraform.
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
## What is this?
|
|
26
26
|
|
|
27
|
-
0x is a tiny language for building
|
|
27
|
+
0x is a tiny language for building apps. You describe what you want, and the compiler outputs production-ready React, Vue, Svelte, Express backends, React Native mobile apps, or Terraform infrastructure.
|
|
28
28
|
|
|
29
29
|
```python
|
|
30
30
|
page Counter:
|
|
@@ -39,9 +39,9 @@ page Counter:
|
|
|
39
39
|
button "+1" style=primary -> increment()
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
That's a complete component. Run `0x build counter.ai --target react` and you get a working React component with `useState`, event handlers, and full styling. Same source also compiles to Vue 3 and
|
|
42
|
+
That's a complete component. Run `0x build counter.ai --target react` and you get a working React component with `useState`, event handlers, and full styling. Same source also compiles to Vue 3, Svelte 5, and React Native.
|
|
43
43
|
|
|
44
|
-
**Why?** Most
|
|
44
|
+
**Why?** Most code is boilerplate — imports, hook calls, JSX wrappers, style objects, closing tags, Express middleware, Terraform blocks. 0x skips all of that. You write what matters, the compiler handles the rest.
|
|
45
45
|
|
|
46
46
|
## The numbers
|
|
47
47
|
|
|
@@ -75,12 +75,21 @@ page Hello:
|
|
|
75
75
|
Compile it:
|
|
76
76
|
|
|
77
77
|
```bash
|
|
78
|
-
#
|
|
78
|
+
# Frontend
|
|
79
79
|
0x build hello.ai --target react
|
|
80
80
|
0x build hello.ai --target vue
|
|
81
81
|
0x build hello.ai --target svelte
|
|
82
82
|
|
|
83
|
-
#
|
|
83
|
+
# Mobile
|
|
84
|
+
0x build app.ai --target react-native
|
|
85
|
+
|
|
86
|
+
# Backend
|
|
87
|
+
0x build server.ai --target backend
|
|
88
|
+
|
|
89
|
+
# Infrastructure
|
|
90
|
+
0x build infra.ai --target terraform
|
|
91
|
+
|
|
92
|
+
# Or multiple at once
|
|
84
93
|
0x build hello.ai --target react,vue,svelte
|
|
85
94
|
```
|
|
86
95
|
|
|
@@ -272,6 +281,72 @@ page Shop:
|
|
|
272
281
|
button "Add to Cart" style=primary -> addToCart(product)
|
|
273
282
|
```
|
|
274
283
|
|
|
284
|
+
### Backend API — Express server
|
|
285
|
+
|
|
286
|
+
```python
|
|
287
|
+
page API:
|
|
288
|
+
model User:
|
|
289
|
+
field name: str
|
|
290
|
+
field email: str
|
|
291
|
+
field role: str = "user"
|
|
292
|
+
|
|
293
|
+
auth:
|
|
294
|
+
secret: JWT_SECRET
|
|
295
|
+
endpoint: /auth
|
|
296
|
+
|
|
297
|
+
route GET /api/users:
|
|
298
|
+
users = await db.find("users")
|
|
299
|
+
respond 200 users
|
|
300
|
+
|
|
301
|
+
route POST /api/users:
|
|
302
|
+
user = await db.create("users", body)
|
|
303
|
+
respond 201 user
|
|
304
|
+
|
|
305
|
+
env:
|
|
306
|
+
DATABASE_URL: "postgres://localhost/mydb"
|
|
307
|
+
JWT_SECRET: secret
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Compiles to a full Express.js server with JWT auth, CORS, error handling, and CRUD routes.
|
|
311
|
+
|
|
312
|
+
### React Native Mobile App
|
|
313
|
+
|
|
314
|
+
```python
|
|
315
|
+
page MobileCounter:
|
|
316
|
+
state count: int = 0
|
|
317
|
+
|
|
318
|
+
fn increment():
|
|
319
|
+
count += 1
|
|
320
|
+
|
|
321
|
+
layout col gap=16 padding=24 center:
|
|
322
|
+
text "Counter" size=2xl bold
|
|
323
|
+
text "{count}" size=4xl color=cyan
|
|
324
|
+
button "+1" style=primary -> increment()
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Same 0x syntax compiles to React Native with `View`, `Text`, `TouchableOpacity`, and `StyleSheet`.
|
|
328
|
+
|
|
329
|
+
### Infrastructure — Terraform
|
|
330
|
+
|
|
331
|
+
```python
|
|
332
|
+
page Infra:
|
|
333
|
+
deploy myApp:
|
|
334
|
+
provider: aws
|
|
335
|
+
region: us-east-1
|
|
336
|
+
instance: t3.medium
|
|
337
|
+
|
|
338
|
+
storage assets:
|
|
339
|
+
provider: s3
|
|
340
|
+
bucket: my-app-assets
|
|
341
|
+
versioning: true
|
|
342
|
+
|
|
343
|
+
domain mysite:
|
|
344
|
+
provider: route53
|
|
345
|
+
name: "myapp.com"
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
Compiles to Terraform HCL with provider blocks, resource definitions, and variable declarations.
|
|
349
|
+
|
|
275
350
|
More examples in [`examples/`](examples/).
|
|
276
351
|
|
|
277
352
|
---
|
|
@@ -430,14 +505,46 @@ page Hello:
|
|
|
430
505
|
text "Hello, {name}!" size=2xl bold
|
|
431
506
|
`;
|
|
432
507
|
|
|
508
|
+
// Frontend targets
|
|
433
509
|
const react = compile(source, { target: 'react' });
|
|
434
510
|
const vue = compile(source, { target: 'vue' });
|
|
435
511
|
const svelte = compile(source, { target: 'svelte' });
|
|
436
512
|
|
|
513
|
+
// Mobile
|
|
514
|
+
const rn = compile(source, { target: 'react-native' });
|
|
515
|
+
|
|
516
|
+
// Backend & infrastructure
|
|
517
|
+
const server = compile(backendSource, { target: 'backend' });
|
|
518
|
+
const infra = compile(infraSource, { target: 'terraform' });
|
|
519
|
+
|
|
520
|
+
// Options
|
|
521
|
+
const result = compile(source, {
|
|
522
|
+
target: 'react',
|
|
523
|
+
validate: true, // Run validator (default: true)
|
|
524
|
+
sourceMap: true, // Add source line comments (default: true)
|
|
525
|
+
useClient: true, // Add 'use client' for Next.js (default: auto)
|
|
526
|
+
compact: true, // AI-optimized compact output — strips comments
|
|
527
|
+
});
|
|
528
|
+
|
|
437
529
|
console.log(react.code); // Full React component
|
|
438
530
|
console.log(react.lineCount); // Line count
|
|
439
531
|
```
|
|
440
532
|
|
|
533
|
+
### AI Bridge
|
|
534
|
+
|
|
535
|
+
```typescript
|
|
536
|
+
import { getLanguageSpec, generatePrompt, compileFromDescription } from '0x-lang';
|
|
537
|
+
|
|
538
|
+
// Get the full 0x language spec for an LLM
|
|
539
|
+
const spec = getLanguageSpec();
|
|
540
|
+
|
|
541
|
+
// Generate a structured prompt for AI code generation
|
|
542
|
+
const prompt = generatePrompt("todo app with authentication", 'react');
|
|
543
|
+
|
|
544
|
+
// Auto-generate a 0x skeleton from a natural language description
|
|
545
|
+
const skeleton = compileFromDescription("dashboard with charts and API");
|
|
546
|
+
```
|
|
547
|
+
|
|
441
548
|
### Pipeline access
|
|
442
549
|
|
|
443
550
|
```typescript
|
|
@@ -461,6 +568,10 @@ const output = generateReact(ast); // React JSX string
|
|
|
461
568
|
| `0x-lang/generators/react` | `generateReact()` |
|
|
462
569
|
| `0x-lang/generators/vue` | `generateVue()` |
|
|
463
570
|
| `0x-lang/generators/svelte` | `generateSvelte()` |
|
|
571
|
+
| `0x-lang/generators/backend` | `generateBackend()` |
|
|
572
|
+
| `0x-lang/generators/react-native` | `generateReactNative()` |
|
|
573
|
+
| `0x-lang/generators/terraform` | `generateTerraform()` |
|
|
574
|
+
| `0x-lang/generators/ai-bridge` | `getLanguageSpec()`, `generatePrompt()`, `compileFromDescription()` |
|
|
464
575
|
|
|
465
576
|
---
|
|
466
577
|
|
|
@@ -541,39 +652,30 @@ See [mcp-server/](mcp-server/) for full docs.
|
|
|
541
652
|
|
|
542
653
|
## For AI agent builders
|
|
543
654
|
|
|
544
|
-
If you're building tools that generate
|
|
655
|
+
If you're building tools that generate code, 0x works well as an intermediate representation:
|
|
545
656
|
|
|
546
657
|
```typescript
|
|
547
658
|
import { compile } from '0x-lang/compiler';
|
|
659
|
+
import { compileFromDescription, generatePrompt } from '0x-lang';
|
|
548
660
|
|
|
549
|
-
//
|
|
550
|
-
const
|
|
551
|
-
page Dashboard:
|
|
552
|
-
state metrics: list[object] = []
|
|
553
|
-
|
|
554
|
-
on mount:
|
|
555
|
-
metrics = await api.getMetrics()
|
|
661
|
+
// Auto-generate 0x skeleton from natural language
|
|
662
|
+
const skeleton = compileFromDescription("e-commerce product page");
|
|
556
663
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
layout grid cols=3 gap=16:
|
|
560
|
-
for metric in metrics:
|
|
561
|
-
layout col padding=20 bg=white rounded=lg shadow:
|
|
562
|
-
text "{metric.label}" size=sm color=gray
|
|
563
|
-
text "{metric.value}" size=2xl bold
|
|
564
|
-
`;
|
|
664
|
+
// Or use the AI bridge to generate a prompt for your LLM
|
|
665
|
+
const prompt = generatePrompt("todo app with auth", 'react');
|
|
565
666
|
|
|
566
|
-
// Compile
|
|
567
|
-
const react = compile(
|
|
568
|
-
const
|
|
569
|
-
const svelte = compile(aiOutput, { target: 'svelte' });
|
|
667
|
+
// Compile with compact mode for AI-optimized output
|
|
668
|
+
const react = compile(source, { target: 'react', compact: true });
|
|
669
|
+
const server = compile(source, { target: 'backend', compact: true });
|
|
570
670
|
```
|
|
571
671
|
|
|
572
672
|
Why this matters for AI:
|
|
573
673
|
|
|
574
674
|
- **80% fewer output tokens** — less generation cost, lower latency
|
|
575
675
|
- **One syntax, zero decisions** — no "which React pattern?" hallucinations
|
|
576
|
-
- **
|
|
676
|
+
- **Full-stack** — frontend, backend, mobile, and infrastructure from one language
|
|
677
|
+
- **Compact mode** — strips comments and whitespace for minimal token usage
|
|
678
|
+
- **AI Bridge** — built-in spec, prompt generation, and skeleton creation
|
|
577
679
|
- **Deterministic** — same input always produces the same output
|
|
578
680
|
- **Validated** — the compiler catches errors before they reach the user
|
|
579
681
|
|
|
@@ -591,12 +693,16 @@ Parser ────── Recursive descent → typed AST
|
|
|
591
693
|
Validator ─── Circular deps · unused state · type checks
|
|
592
694
|
↓
|
|
593
695
|
Generator
|
|
594
|
-
├── React
|
|
595
|
-
├── Vue
|
|
596
|
-
|
|
696
|
+
├── React ────────── JSX + hooks + CSS-in-JS
|
|
697
|
+
├── Vue ──────────── SFC + Composition API + scoped styles
|
|
698
|
+
├── Svelte ──────── Runes ($state, $derived) + styles
|
|
699
|
+
├── React Native ── View + StyleSheet + TouchableOpacity
|
|
700
|
+
├── Backend ─────── Express + JWT + CRUD + middleware
|
|
701
|
+
├── Terraform ───── HCL + providers + resources
|
|
702
|
+
└── AI Bridge ───── Spec + prompts + skeleton generation
|
|
597
703
|
```
|
|
598
704
|
|
|
599
|
-
~
|
|
705
|
+
~12,000 lines of TypeScript. Zero runtime dependencies (except gen-mapping for source maps).
|
|
600
706
|
|
|
601
707
|
| Module | Lines | |
|
|
602
708
|
|:---|---:|:---|
|
|
@@ -605,8 +711,12 @@ Generator
|
|
|
605
711
|
| AST Types | 1,065 | TypeScript definitions |
|
|
606
712
|
| Vue Generator | 822 | SFC with `<script setup>` and `ref()` |
|
|
607
713
|
| Svelte Generator | 743 | Svelte 5 with `$state()` and `$derived()` |
|
|
714
|
+
| React Native Generator | 690 | View + StyleSheet + native components |
|
|
715
|
+
| Backend Generator | 412 | Express + JWT + CRUD + middleware |
|
|
716
|
+
| Terraform Generator | 370 | HCL + multi-provider (AWS, Vercel, Fly.io) |
|
|
608
717
|
| Validator | 355 | Static analysis + error reporting |
|
|
609
718
|
| Tokenizer | 348 | Indentation-aware lexer |
|
|
719
|
+
| AI Bridge | 291 | Spec + prompt generation + skeleton |
|
|
610
720
|
| CLI | 223 | build · dev · bench · init |
|
|
611
721
|
|
|
612
722
|
## CLI
|
|
@@ -618,7 +728,7 @@ Generator
|
|
|
618
728
|
0x init [project-name]
|
|
619
729
|
|
|
620
730
|
Flags:
|
|
621
|
-
--target, -t react, vue, svelte (comma-separated)
|
|
731
|
+
--target, -t react, vue, svelte, react-native, backend, terraform (comma-separated)
|
|
622
732
|
--output, -o Output directory (default: ./dist/)
|
|
623
733
|
--help, -h Show help
|
|
624
734
|
```
|
package/dist/compiler.js
CHANGED
|
@@ -69,6 +69,8 @@ function compactify(result) {
|
|
|
69
69
|
code = code.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
70
70
|
// Strip single-line comments (but not URLs or sourceMappingURL)
|
|
71
71
|
code = code.replace(/^(\s*)\/\/(?!#).*$/gm, '');
|
|
72
|
+
// Strip hash comments (Terraform-style)
|
|
73
|
+
code = code.replace(/^#\s+Generated by 0x.*$/gm, '');
|
|
72
74
|
// Strip "Generated by 0x" header comments
|
|
73
75
|
code = code.replace(/^\/\/ Generated by 0x.*$/gm, '');
|
|
74
76
|
// Collapse multiple empty lines to single empty line
|
package/dist/compiler.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compiler.js","sourceRoot":"","sources":["../src/compiler.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"compiler.js","sourceRoot":"","sources":["../src/compiler.ts"],"names":[],"mappings":"AAAA,uBAAuB;AAEvB,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAgB1C,MAAM,UAAU,OAAO,CAAC,MAAc,EAAE,OAAuB;IAC7D,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;IAE1B,8BAA8B;IAC9B,IAAI,OAAO,CAAC,QAAQ,KAAK,KAAK,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC7B,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAClG,MAAM,IAAI,KAAK,CAAC,uBAAuB,MAAM,EAAE,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;IAED,IAAI,MAAqB,CAAC;IAC1B,QAAQ,OAAO,CAAC,MAAM,EAAE,CAAC;QACvB,KAAK,OAAO;YACV,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;YAC5B,MAAM;QACR,KAAK,KAAK;YACR,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;YAC1B,MAAM;QACR,KAAK,QAAQ;YACX,MAAM,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;YAC7B,MAAM;QACR,KAAK,SAAS;YACZ,MAAM,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;YAC9B,MAAM;QACR,KAAK,cAAc;YACjB,MAAM,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC;YAClC,MAAM;QACR,KAAK,WAAW;YACd,MAAM,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;YAChC,MAAM;QACR;YACE,MAAM,IAAI,KAAK,CAAC,mBAAmB,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACzD,CAAC;IAED,wEAAwE;IACxE,IAAI,OAAO,CAAC,SAAS,KAAK,KAAK,EAAE,CAAC;QAChC,MAAM,GAAG,EAAE,GAAG,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,wBAAwB,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC;IACzI,CAAC;SAAM,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;QAC5B,oDAAoD;QACpD,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC7D,MAAM,GAAG,EAAE,GAAG,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,GAAG,qEAAqE,GAAG,IAAI,EAAE,CAAC;IAC3H,CAAC;IAED,0DAA0D;IAC1D,IAAI,OAAO,CAAC,SAAS,KAAK,KAAK,IAAI,OAAO,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;QAC9D,MAAM,GAAG,EAAE,GAAG,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,sBAAsB,EAAE,EAAE,CAAC,EAAE,CAAC;IAChF,CAAC;IAED,qDAAqD;IACrD,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IAC9B,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,UAAU,CAAC,MAAqB;IACvC,IAAI,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;IACvB,4BAA4B;IAC5B,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,wBAAwB,EAAE,EAAE,CAAC,CAAC;IAClD,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC;IAC7C,uBAAuB;IACvB,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,uBAAuB,EAAE,EAAE,CAAC,CAAC;IACjD,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC;IAC7C,gEAAgE;IAChE,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,sBAAsB,EAAE,EAAE,CAAC,CAAC;IAChD,wCAAwC;IACxC,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,2BAA2B,EAAE,EAAE,CAAC,CAAC;IACrD,0CAA0C;IAC1C,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,4BAA4B,EAAE,EAAE,CAAC,CAAC;IACtD,qDAAqD;IACrD,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IACvC,0CAA0C;IAC1C,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IACrC,OAAO;IACP,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAEnB,OAAO;QACL,GAAG,MAAM;QACT,IAAI;QACJ,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM;QAClC,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,MAAM;QAC9D,SAAS,EAAE,SAAS;KACrB,CAAC;AACJ,CAAC"}
|
package/dist/generators/react.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// 0x → React Code Generator
|
|
2
|
-
import { SIZE_MAP, unquote, capitalize, parseGradient, addPx } from './shared.js';
|
|
2
|
+
import { SIZE_MAP, unquote, capitalize, parseGradient, addPx, getPassthroughProps, KNOWN_LAYOUT_PROPS, KNOWN_TEXT_PROPS, KNOWN_BUTTON_PROPS, KNOWN_INPUT_PROPS, KNOWN_IMAGE_PROPS, KNOWN_LINK_PROPS, KNOWN_TOGGLE_PROPS, KNOWN_SELECT_PROPS } from './shared.js';
|
|
3
3
|
import { SourceMapBuilder } from './source-map.js';
|
|
4
4
|
export function ctx() {
|
|
5
5
|
return {
|
|
@@ -369,7 +369,8 @@ function genDerived(node, c) {
|
|
|
369
369
|
c.readOnly = prevReadOnly;
|
|
370
370
|
const { deps, warning } = extractDepsWithWarning(node.expression, c);
|
|
371
371
|
const warnComment = warning ? ` ${warning}` : '';
|
|
372
|
-
|
|
372
|
+
const sc = node.loc?.line ? `// 0x:L${node.loc.line}\n ` : '';
|
|
373
|
+
return `${sc}${warnComment ? warnComment + '\n ' : ''}const ${node.name} = useMemo(() => ${expr}, [${deps.join(', ')}]);`;
|
|
373
374
|
}
|
|
374
375
|
function genCheck(node, c) {
|
|
375
376
|
const cond = genExpr(node.condition, c);
|
|
@@ -400,32 +401,36 @@ function genFunction(node, c) {
|
|
|
400
401
|
// requires
|
|
401
402
|
const requireChecks = node.requires.map(r => `if (!(${genExpr(r, c)})) throw new Error('Precondition failed');`).join('\n ');
|
|
402
403
|
const allBody = [requireChecks, body].filter(Boolean).join('\n ');
|
|
403
|
-
|
|
404
|
+
const sc = node.loc?.line ? `// 0x:L${node.loc.line}\n ` : '';
|
|
405
|
+
return `${sc}const ${node.name} = ${asyncKw}(${params}) => {\n ${allBody}\n };`;
|
|
404
406
|
}
|
|
405
407
|
// ── Lifecycle ───────────────────────────────────────
|
|
406
408
|
function genOnMount(node, c) {
|
|
407
409
|
c.imports.add('useEffect');
|
|
408
410
|
const body = node.body.map(s => genStatement(s, c)).join('\n ');
|
|
409
411
|
const hasAwait = bodyContainsAwait(node.body);
|
|
412
|
+
const sc = node.loc?.line ? `// 0x:L${node.loc.line}\n ` : '';
|
|
410
413
|
if (hasAwait) {
|
|
411
|
-
return
|
|
414
|
+
return `${sc}useEffect(() => {\n (async () => {\n ${body}\n })();\n }, []);`;
|
|
412
415
|
}
|
|
413
|
-
return
|
|
416
|
+
return `${sc}useEffect(() => {\n ${body}\n }, []);`;
|
|
414
417
|
}
|
|
415
418
|
function genOnDestroy(node, c) {
|
|
416
419
|
c.imports.add('useEffect');
|
|
417
420
|
const body = node.body.map(s => genStatement(s, c)).join('\n ');
|
|
418
|
-
|
|
421
|
+
const sc = node.loc?.line ? `// 0x:L${node.loc.line}\n ` : '';
|
|
422
|
+
return `${sc}useEffect(() => {\n return () => {\n ${body}\n };\n }, []);`;
|
|
419
423
|
}
|
|
420
424
|
function genWatch(node, c) {
|
|
421
425
|
c.imports.add('useEffect');
|
|
422
426
|
const body = node.body.map(s => genStatement(s, c)).join('\n ');
|
|
423
427
|
const vars = (node.variables || [node.variable]).join(', ');
|
|
424
428
|
const hasAwait = bodyContainsAwait(node.body);
|
|
429
|
+
const sc = node.loc?.line ? `// 0x:L${node.loc.line}\n ` : '';
|
|
425
430
|
if (hasAwait) {
|
|
426
|
-
return
|
|
431
|
+
return `${sc}useEffect(() => {\n (async () => {\n ${body}\n })();\n }, [${vars}]);`;
|
|
427
432
|
}
|
|
428
|
-
return
|
|
433
|
+
return `${sc}useEffect(() => {\n ${body}\n }, [${vars}]);`;
|
|
429
434
|
}
|
|
430
435
|
// ── UI Nodes ────────────────────────────────────────
|
|
431
436
|
function srcComment(node) {
|
|
@@ -522,10 +527,14 @@ function genLayout(node, c) {
|
|
|
522
527
|
style['flexDirection'] = node.direction === 'row' ? 'row' : 'column';
|
|
523
528
|
}
|
|
524
529
|
// Process layout props
|
|
530
|
+
let className = null;
|
|
525
531
|
for (const [key, val] of Object.entries(node.props)) {
|
|
526
532
|
const v = genExpr(val, c);
|
|
527
533
|
const isDynamic = val.kind === 'braced' || val.kind === 'ternary' || val.kind === 'binary' || val.kind === 'member' || val.kind === 'call';
|
|
528
534
|
switch (key) {
|
|
535
|
+
case 'class':
|
|
536
|
+
className = unquote(v);
|
|
537
|
+
break;
|
|
529
538
|
case 'gap':
|
|
530
539
|
style['gap'] = addPx(v);
|
|
531
540
|
break;
|
|
@@ -600,17 +609,23 @@ function genLayout(node, c) {
|
|
|
600
609
|
}
|
|
601
610
|
}
|
|
602
611
|
const styleStr = genStyleObj(style, dynamicKeys);
|
|
612
|
+
const classAttr = className ? ` className="${className}"` : '';
|
|
613
|
+
const extra = getPassthroughProps(node.props, KNOWN_LAYOUT_PROPS, e => genExpr(e, c), 'react');
|
|
603
614
|
const children = node.children.map(ch => genUINode(ch, c)).join('\n');
|
|
604
615
|
const sc = srcComment(node);
|
|
605
|
-
return `${sc}<div style={${styleStr}}>\n${children}\n</div>`;
|
|
616
|
+
return `${sc}<div${classAttr} style={${styleStr}}${extra}>\n${children}\n</div>`;
|
|
606
617
|
}
|
|
607
618
|
function genText(node, c) {
|
|
608
619
|
const style = {};
|
|
609
620
|
const dynamicKeys = new Set();
|
|
621
|
+
let className = null;
|
|
610
622
|
for (const [key, val] of Object.entries(node.props)) {
|
|
611
623
|
const v = genExpr(val, c);
|
|
612
624
|
const isDynamic = val.kind === 'braced' || val.kind === 'ternary' || val.kind === 'binary' || val.kind === 'member' || val.kind === 'call';
|
|
613
625
|
switch (key) {
|
|
626
|
+
case 'class':
|
|
627
|
+
className = unquote(v);
|
|
628
|
+
break;
|
|
614
629
|
case 'size': {
|
|
615
630
|
const uv = unquote(v);
|
|
616
631
|
style['fontSize'] = SIZE_MAP[uv] || `${uv}px`;
|
|
@@ -658,12 +673,14 @@ function genText(node, c) {
|
|
|
658
673
|
}
|
|
659
674
|
}
|
|
660
675
|
const content = genTextContent(node.content, c);
|
|
676
|
+
const classAttr = className ? ` className="${className}"` : '';
|
|
661
677
|
const styleStr = Object.keys(style).length > 0 ? ` style={${genStyleObj(style, dynamicKeys)}}` : '';
|
|
678
|
+
const extra = getPassthroughProps(node.props, KNOWN_TEXT_PROPS, e => genExpr(e, c), 'react');
|
|
662
679
|
// Badge prop: render a badge indicator next to content
|
|
663
680
|
const badgeExpr = node.props['badge'];
|
|
664
681
|
const tooltipExpr = node.props['tooltip'];
|
|
665
682
|
const sc = srcComment(node);
|
|
666
|
-
let result = `${sc}<span${styleStr}>${content}</span>`;
|
|
683
|
+
let result = `${sc}<span${classAttr}${styleStr}${extra}>${content}</span>`;
|
|
667
684
|
if (badgeExpr) {
|
|
668
685
|
const badge = genExpr(badgeExpr, c);
|
|
669
686
|
result = `<span style={{ position: 'relative', display: 'inline-flex', alignItems: 'center' }}>\n<span${styleStr}>${content}</span>\n<span style={{ marginLeft: '6px', padding: '2px 6px', fontSize: '12px', fontWeight: 'bold', borderRadius: '9999px', backgroundColor: '#ef4444', color: '#fff', minWidth: '20px', textAlign: 'center' }}>{${badge}}</span>\n</span>`;
|
|
@@ -678,24 +695,37 @@ function genButton(node, c) {
|
|
|
678
695
|
const label = genTextContent(node.label, c);
|
|
679
696
|
const actionCode = genActionExpr(node.action, c);
|
|
680
697
|
const styleProps = [];
|
|
698
|
+
let className = null;
|
|
681
699
|
for (const [key, val] of Object.entries(node.props)) {
|
|
682
700
|
const v = genExpr(val, c);
|
|
683
701
|
switch (key) {
|
|
684
|
-
case '
|
|
685
|
-
|
|
702
|
+
case 'class':
|
|
703
|
+
className = unquote(v);
|
|
686
704
|
break;
|
|
705
|
+
case 'style': {
|
|
706
|
+
const sv = unquote(v);
|
|
707
|
+
if (sv === 'primary')
|
|
708
|
+
styleProps.push('style={{ backgroundColor: "#3b82f6", color: "white", border: "none", padding: "8px 16px", borderRadius: "6px", cursor: "pointer" }}');
|
|
709
|
+
else if (sv === 'danger')
|
|
710
|
+
styleProps.push('style={{ backgroundColor: "#ef4444", color: "white", border: "none", padding: "8px 16px", borderRadius: "6px", cursor: "pointer" }}');
|
|
711
|
+
break;
|
|
712
|
+
}
|
|
687
713
|
case 'disabled':
|
|
688
714
|
styleProps.push(`disabled={${v}}`);
|
|
689
715
|
break;
|
|
690
716
|
case 'size': /* handled in style */ break;
|
|
691
717
|
}
|
|
692
718
|
}
|
|
719
|
+
if (className)
|
|
720
|
+
styleProps.push(`className="${className}"`);
|
|
721
|
+
const extra = getPassthroughProps(node.props, KNOWN_BUTTON_PROPS, e => genExpr(e, c), 'react');
|
|
693
722
|
const propsStr = styleProps.join(' ');
|
|
694
723
|
const sc = srcComment(node);
|
|
695
|
-
return `${sc}<button onClick={() => ${actionCode}}${propsStr ? ' ' + propsStr : ''}>${label}</button>`;
|
|
724
|
+
return `${sc}<button onClick={() => ${actionCode}}${propsStr ? ' ' + propsStr : ''}${extra}>${label}</button>`;
|
|
696
725
|
}
|
|
697
726
|
function genInput(node, c) {
|
|
698
727
|
const setter = 'set' + capitalize(node.binding);
|
|
728
|
+
let className = null;
|
|
699
729
|
const props = [
|
|
700
730
|
`value={${node.binding}}`,
|
|
701
731
|
`onChange={e => ${setter}(e.target.value)}`,
|
|
@@ -703,6 +733,9 @@ function genInput(node, c) {
|
|
|
703
733
|
for (const [key, val] of Object.entries(node.props)) {
|
|
704
734
|
const v = genExpr(val, c);
|
|
705
735
|
switch (key) {
|
|
736
|
+
case 'class':
|
|
737
|
+
className = unquote(v);
|
|
738
|
+
break;
|
|
706
739
|
case 'placeholder':
|
|
707
740
|
props.push(`placeholder=${quoteJsx(v)}`);
|
|
708
741
|
break;
|
|
@@ -718,16 +751,23 @@ function genInput(node, c) {
|
|
|
718
751
|
props.push(`onKeyPress={e => ${handler}(e.key)}`);
|
|
719
752
|
}
|
|
720
753
|
}
|
|
754
|
+
if (className)
|
|
755
|
+
props.push(`className="${className}"`);
|
|
756
|
+
const extra = getPassthroughProps(node.props, KNOWN_INPUT_PROPS, e => genExpr(e, c), 'react');
|
|
721
757
|
const sc = srcComment(node);
|
|
722
|
-
return `${sc}<input ${props.join(' ')} />`;
|
|
758
|
+
return `${sc}<input ${props.join(' ')}${extra} />`;
|
|
723
759
|
}
|
|
724
760
|
function genImage(node, c) {
|
|
725
761
|
const src = genExpr(node.src, c);
|
|
726
762
|
const props = [`src={${src}}`];
|
|
727
763
|
const style = {};
|
|
764
|
+
let className = null;
|
|
728
765
|
for (const [key, val] of Object.entries(node.props)) {
|
|
729
766
|
const v = genExpr(val, c);
|
|
730
767
|
switch (key) {
|
|
768
|
+
case 'class':
|
|
769
|
+
className = unquote(v);
|
|
770
|
+
break;
|
|
731
771
|
case 'width':
|
|
732
772
|
props.push(`width="${v}"`);
|
|
733
773
|
break;
|
|
@@ -751,16 +791,26 @@ function genImage(node, c) {
|
|
|
751
791
|
}
|
|
752
792
|
}
|
|
753
793
|
}
|
|
794
|
+
if (className)
|
|
795
|
+
props.push(`className="${className}"`);
|
|
754
796
|
if (Object.keys(style).length > 0) {
|
|
755
797
|
const entries = Object.entries(style).map(([k, v]) => `${k}: ${v}`).join(', ');
|
|
756
798
|
props.push(`style={{ ${entries} }}`);
|
|
757
799
|
}
|
|
758
|
-
|
|
800
|
+
const extra = getPassthroughProps(node.props, KNOWN_IMAGE_PROPS, e => genExpr(e, c), 'react');
|
|
801
|
+
return `<img ${props.join(' ')}${extra} />`;
|
|
759
802
|
}
|
|
760
803
|
function genLink(node, c) {
|
|
761
804
|
const label = genTextContent(node.label, c);
|
|
762
805
|
const href = genExpr(node.href, c);
|
|
763
|
-
|
|
806
|
+
let className = null;
|
|
807
|
+
for (const [key, val] of Object.entries(node.props || {})) {
|
|
808
|
+
if (key === 'class')
|
|
809
|
+
className = unquote(genExpr(val, c));
|
|
810
|
+
}
|
|
811
|
+
const classAttr = className ? ` className="${className}"` : '';
|
|
812
|
+
const extra = getPassthroughProps(node.props || {}, KNOWN_LINK_PROPS, e => genExpr(e, c), 'react');
|
|
813
|
+
return `<a href={${href}}${classAttr}${extra}>${label}</a>`;
|
|
764
814
|
}
|
|
765
815
|
function genToggle(node, c) {
|
|
766
816
|
const binding = node.binding;
|
|
@@ -773,12 +823,26 @@ function genToggle(node, c) {
|
|
|
773
823
|
else {
|
|
774
824
|
setter = `set${capitalize(parts[0])}(prev => ({...prev, ${parts.slice(1).join('.')}: !prev.${parts.slice(1).join('.')}}))`;
|
|
775
825
|
}
|
|
776
|
-
|
|
826
|
+
let className = null;
|
|
827
|
+
for (const [key, val] of Object.entries(node.props || {})) {
|
|
828
|
+
if (key === 'class')
|
|
829
|
+
className = unquote(genExpr(val, c));
|
|
830
|
+
}
|
|
831
|
+
const classAttr = className ? ` className="${className}"` : '';
|
|
832
|
+
const extra = getPassthroughProps(node.props || {}, KNOWN_TOGGLE_PROPS, e => genExpr(e, c), 'react');
|
|
833
|
+
return `<input type="checkbox" checked={${binding}} onChange={() => ${setter}}${classAttr}${extra} />`;
|
|
777
834
|
}
|
|
778
835
|
function genSelect(node, c) {
|
|
779
836
|
const setter = 'set' + capitalize(node.binding);
|
|
780
837
|
const options = genExpr(node.options, c);
|
|
781
|
-
|
|
838
|
+
let className = null;
|
|
839
|
+
for (const [key, val] of Object.entries(node.props || {})) {
|
|
840
|
+
if (key === 'class')
|
|
841
|
+
className = unquote(genExpr(val, c));
|
|
842
|
+
}
|
|
843
|
+
const classAttr = className ? ` className="${className}"` : '';
|
|
844
|
+
const extra = getPassthroughProps(node.props || {}, KNOWN_SELECT_PROPS, e => genExpr(e, c), 'react');
|
|
845
|
+
return `<select value={${node.binding}} onChange={e => ${setter}(e.target.value)}${classAttr}${extra}>\n {${options}.map(opt => <option key={opt} value={opt}>{opt}</option>)}\n</select>`;
|
|
782
846
|
}
|
|
783
847
|
function genComponentCall(node, c) {
|
|
784
848
|
const parts = [];
|