@bytefaceinc/web-lang 0.1.1
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/LICENSE +21 -0
- package/README.md +277 -0
- package/bin/web.js +10 -0
- package/compiler.js +4602 -0
- package/docs/cli.md +657 -0
- package/docs/compiler.md +1433 -0
- package/docs/error-handling.md +863 -0
- package/docs/getting-started.md +805 -0
- package/docs/language-guide.md +945 -0
- package/lib/cli/commands/compile.js +127 -0
- package/lib/cli/commands/init.js +172 -0
- package/lib/cli/commands/screenshot.js +257 -0
- package/lib/cli/commands/watch.js +458 -0
- package/lib/cli/compile-service.js +19 -0
- package/lib/cli/compile-worker.js +32 -0
- package/lib/cli/compiler-runner.js +37 -0
- package/lib/cli/index.js +154 -0
- package/lib/cli/init-service.js +204 -0
- package/lib/cli/screenshot-artifacts.js +81 -0
- package/lib/cli/screenshot-service.js +153 -0
- package/lib/cli/shared.js +261 -0
- package/lib/cli/targets.js +199 -0
- package/lib/cli/watch-settings.js +37 -0
- package/package.json +50 -0
package/docs/compiler.md
ADDED
|
@@ -0,0 +1,1433 @@
|
|
|
1
|
+
# WEB Compiler Reference For LLM Agents
|
|
2
|
+
|
|
3
|
+
This document is a compact but comprehensive reference for generating valid WEB source for the current compiler implementation.
|
|
4
|
+
|
|
5
|
+
Use this when you need to:
|
|
6
|
+
|
|
7
|
+
- write `.web` source
|
|
8
|
+
- predict what `compiler.js` will emit
|
|
9
|
+
- understand which constructs affect HTML vs CSS
|
|
10
|
+
- use compile-time JavaScript safely
|
|
11
|
+
- avoid features the compiler does not support
|
|
12
|
+
|
|
13
|
+
This is an implementation-oriented reference, not a marketing overview.
|
|
14
|
+
|
|
15
|
+
## 1. Fast Mental Model
|
|
16
|
+
|
|
17
|
+
WEB has three practical entry points:
|
|
18
|
+
|
|
19
|
+
- legacy script mode: `layout.web` -> `index.html`, `styles.css`
|
|
20
|
+
- CLI mode: `home.web` -> `home.html`, `home.css`
|
|
21
|
+
- CLI scaffold mode: `web init [directory]` creates `index.web`, `web-lang-agents.md`, and then compiles to `index.html` plus `index.css` in the chosen project directory
|
|
22
|
+
|
|
23
|
+
Run:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
node compiler.js
|
|
27
|
+
web init
|
|
28
|
+
web init .
|
|
29
|
+
web init website
|
|
30
|
+
web home.web
|
|
31
|
+
web home
|
|
32
|
+
web .
|
|
33
|
+
web ./code/*
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The compiler pipeline is:
|
|
37
|
+
|
|
38
|
+
1. tokenize source
|
|
39
|
+
2. parse into a small AST
|
|
40
|
+
3. build a variable table
|
|
41
|
+
4. build a type symbol table
|
|
42
|
+
5. build the HTML document model
|
|
43
|
+
6. build the CSS style model
|
|
44
|
+
7. emit `index.html`
|
|
45
|
+
8. emit `styles.css`
|
|
46
|
+
|
|
47
|
+
Generated HTML shell:
|
|
48
|
+
|
|
49
|
+
- `<!DOCTYPE html>`
|
|
50
|
+
- `<html lang="en">`
|
|
51
|
+
- `<meta charset="UTF-8" />`
|
|
52
|
+
- `<meta name="viewport" content="width=device-width, initial-scale=1.0" />`
|
|
53
|
+
- fixed `<title>WEB Output</title>`
|
|
54
|
+
- fixed stylesheet link to `styles.css`
|
|
55
|
+
- top-level `::head` entries are rendered before `</head>`
|
|
56
|
+
- all rendered WEB nodes are inserted inside `<body>`
|
|
57
|
+
- top-level `::script` blocks are rendered near the end of `<body>`
|
|
58
|
+
|
|
59
|
+
Key idea:
|
|
60
|
+
|
|
61
|
+
- one optional top-level `define { ... }` block owns variables, type declarations, and style inheritance
|
|
62
|
+
- normal WEB blocks describe DOM structure and CSS
|
|
63
|
+
- top-level `html { ... }` and `* { ... }` describe global CSS selectors only
|
|
64
|
+
- `::attrs { ... }` adds real HTML attributes to normal WEB nodes
|
|
65
|
+
- `::head { ... }` adds literal `meta` and `link` tags to `<head>` without entering the CSS pipeline
|
|
66
|
+
- `::script { ... }` adds literal `<script></script>` tags without entering the CSS pipeline
|
|
67
|
+
- `derivedName extends baseName;` declarations still describe CSS inheritance only, and they only appear inside `define { ... }`
|
|
68
|
+
- `styles { ... }`, `::pseudo`, and `::media` describe CSS only
|
|
69
|
+
- `::keyframes` describes global animation blocks only
|
|
70
|
+
- `js\`...\`` runs at compile time, never in the browser
|
|
71
|
+
|
|
72
|
+
If you remember only one rule, remember this:
|
|
73
|
+
|
|
74
|
+
- HTML structure comes from normal DOM-scope paths
|
|
75
|
+
- CSS comes from property assignments plus style scopes, pseudo blocks, media blocks, and raw CSS
|
|
76
|
+
|
|
77
|
+
## 2. Minimal Working Shape
|
|
78
|
+
|
|
79
|
+
This is the simplest useful WEB file shape:
|
|
80
|
+
|
|
81
|
+
```web
|
|
82
|
+
define {
|
|
83
|
+
@pageWidth = "960px";
|
|
84
|
+
Main pageMain;
|
|
85
|
+
Heading heroTitle;
|
|
86
|
+
Paragraph heroCopy;
|
|
87
|
+
Button heroButton;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
pageMain {
|
|
91
|
+
width = @pageWidth;
|
|
92
|
+
display = "grid";
|
|
93
|
+
gap = "16px";
|
|
94
|
+
|
|
95
|
+
heroTitle {
|
|
96
|
+
textContent = "Hello";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
heroCopy {
|
|
100
|
+
textContent = "WEB compiles to HTML and CSS.";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
heroButton {
|
|
104
|
+
textContent = "Launch";
|
|
105
|
+
padding = "12px 18px";
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## 3. Top-Level Rule Order
|
|
111
|
+
|
|
112
|
+
The compiler is strict about what may appear at the top of the file.
|
|
113
|
+
|
|
114
|
+
Recommended order:
|
|
115
|
+
|
|
116
|
+
1. one optional `define { ... }` block
|
|
117
|
+
2. optional top-level global selector blocks with `html { ... }` or `* { ... }`
|
|
118
|
+
3. rules and blocks
|
|
119
|
+
4. optional top-level `::head` blocks
|
|
120
|
+
5. optional top-level `::script` blocks
|
|
121
|
+
6. top-level `::keyframes` may appear with other top-level rules, but never inside a block
|
|
122
|
+
|
|
123
|
+
Hard constraints:
|
|
124
|
+
|
|
125
|
+
- variables must be declared inside `define { ... }`
|
|
126
|
+
- type declarations must be declared inside `define { ... }`
|
|
127
|
+
- style inheritance declarations must be declared inside `define { ... }`
|
|
128
|
+
- `define { ... }` may appear at most once
|
|
129
|
+
- `define { ... }` must appear before rules
|
|
130
|
+
- variables cannot use `js\`...\``
|
|
131
|
+
- `html { ... }` and `* { ... }` are reserved for global CSS selector blocks
|
|
132
|
+
- `::head` must be top-level
|
|
133
|
+
- `::script` must be top-level
|
|
134
|
+
- `::keyframes` must be top-level
|
|
135
|
+
|
|
136
|
+
## 4. Core Syntax
|
|
137
|
+
|
|
138
|
+
### 4.1 `define { ... }`
|
|
139
|
+
|
|
140
|
+
Syntax:
|
|
141
|
+
|
|
142
|
+
```web
|
|
143
|
+
define {
|
|
144
|
+
@space = "1rem";
|
|
145
|
+
Button actionButton;
|
|
146
|
+
actionButton heroButton;
|
|
147
|
+
heroButton extends actionButton;
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
What belongs inside `define`:
|
|
152
|
+
|
|
153
|
+
- variable declarations
|
|
154
|
+
- type declarations
|
|
155
|
+
- style inheritance declarations
|
|
156
|
+
|
|
157
|
+
What does not belong inside `define`:
|
|
158
|
+
|
|
159
|
+
- DOM rules
|
|
160
|
+
- CSS property assignments
|
|
161
|
+
- `styles { ... }`
|
|
162
|
+
- `::pseudo`
|
|
163
|
+
- `::media`
|
|
164
|
+
- `::attrs`
|
|
165
|
+
- `::head`
|
|
166
|
+
- `::script`
|
|
167
|
+
- `::keyframes`
|
|
168
|
+
|
|
169
|
+
### 4.2 Type declarations
|
|
170
|
+
|
|
171
|
+
Syntax:
|
|
172
|
+
|
|
173
|
+
```web
|
|
174
|
+
BaseType NewType;
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Examples:
|
|
178
|
+
|
|
179
|
+
```web
|
|
180
|
+
Button actionButton;
|
|
181
|
+
actionButton heroButton;
|
|
182
|
+
Heading2 sectionHeading;
|
|
183
|
+
Article featureCard;
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
What this does:
|
|
187
|
+
|
|
188
|
+
- creates a named type alias
|
|
189
|
+
- allows semantic inheritance
|
|
190
|
+
- affects HTML tag inference
|
|
191
|
+
|
|
192
|
+
What this does not do:
|
|
193
|
+
|
|
194
|
+
- it does not automatically share CSS properties
|
|
195
|
+
- use `storeCard extends card;` for CSS inheritance
|
|
196
|
+
- it does not create output by itself
|
|
197
|
+
|
|
198
|
+
Type declarations must live inside the top-level `define { ... }` block.
|
|
199
|
+
|
|
200
|
+
### 4.3 Style inheritance
|
|
201
|
+
|
|
202
|
+
Syntax:
|
|
203
|
+
|
|
204
|
+
```web
|
|
205
|
+
derivedName extends baseName;
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Example:
|
|
209
|
+
|
|
210
|
+
```web
|
|
211
|
+
define {
|
|
212
|
+
Article card;
|
|
213
|
+
card storeCard;
|
|
214
|
+
storeCard extends card;
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
What this does:
|
|
219
|
+
|
|
220
|
+
- copies CSS from `card` into `storeCard`
|
|
221
|
+
- keeps HTML tag inference separate from CSS reuse
|
|
222
|
+
- supports transitive inheritance such as `featuredStoreCard extends storeCard;`
|
|
223
|
+
|
|
224
|
+
What this does not do:
|
|
225
|
+
|
|
226
|
+
- it does not change HTML tag inference by itself
|
|
227
|
+
- it does not create output by itself
|
|
228
|
+
|
|
229
|
+
Important behavior:
|
|
230
|
+
|
|
231
|
+
- style inheritance is CSS-only
|
|
232
|
+
- explicit derived styles override inherited base styles
|
|
233
|
+
- closer ancestors override farther ancestors
|
|
234
|
+
- circular style inheritance is not allowed
|
|
235
|
+
- inheritance applies to normal rules, descendant rules, style scopes, pseudo blocks, media blocks, and `raw`
|
|
236
|
+
|
|
237
|
+
If you want the derived name to share both:
|
|
238
|
+
|
|
239
|
+
- the same semantic HTML tag
|
|
240
|
+
- and the same CSS
|
|
241
|
+
|
|
242
|
+
combine type inheritance and style inheritance:
|
|
243
|
+
|
|
244
|
+
```web
|
|
245
|
+
define {
|
|
246
|
+
Article card;
|
|
247
|
+
card storeCard;
|
|
248
|
+
storeCard extends card;
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Style inheritance declarations must live inside the top-level `define { ... }` block.
|
|
253
|
+
|
|
254
|
+
### 4.4 `::head`
|
|
255
|
+
|
|
256
|
+
Use `::head { ... }` when you want WEB to emit literal `meta` and `link` tags inside the document head.
|
|
257
|
+
|
|
258
|
+
Syntax:
|
|
259
|
+
|
|
260
|
+
```web
|
|
261
|
+
::head {
|
|
262
|
+
meta {
|
|
263
|
+
name = "description";
|
|
264
|
+
content = "Landing page built in WEB.";
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
link {
|
|
268
|
+
rel = "help";
|
|
269
|
+
href = "compiler.md";
|
|
270
|
+
type = "text/markdown";
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Behavior:
|
|
276
|
+
|
|
277
|
+
- `::head` is top-level only
|
|
278
|
+
- it creates HTML only, not CSS
|
|
279
|
+
- it only supports `meta { ... }` and `link { ... }` entries
|
|
280
|
+
- each `meta` or `link` entry supports direct HTML attribute assignments only
|
|
281
|
+
- attribute names are written in camelCase and compile to kebab-case
|
|
282
|
+
- rendered head entries are inserted before `</head>`
|
|
283
|
+
|
|
284
|
+
### 4.5 `::script`
|
|
285
|
+
|
|
286
|
+
Use `::script { ... }` when you want WEB to emit a literal script tag.
|
|
287
|
+
|
|
288
|
+
Syntax:
|
|
289
|
+
|
|
290
|
+
```web
|
|
291
|
+
::script {
|
|
292
|
+
src = "/assets/app.js";
|
|
293
|
+
defer = "true";
|
|
294
|
+
|
|
295
|
+
code {
|
|
296
|
+
function helloWorld () {
|
|
297
|
+
alert("hello world");
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Behavior:
|
|
304
|
+
|
|
305
|
+
- `::script` is top-level only
|
|
306
|
+
- it creates HTML only, not CSS
|
|
307
|
+
- it supports direct HTML attribute assignments
|
|
308
|
+
- it supports one optional `code { ... }` block
|
|
309
|
+
- the `code { ... }` body is copied as raw script text
|
|
310
|
+
- rendered script tags are inserted near the end of `<body>`
|
|
311
|
+
|
|
312
|
+
Attribute notes:
|
|
313
|
+
|
|
314
|
+
- `defer = true;` emits a bare boolean attribute
|
|
315
|
+
- `defer = "true";` emits `defer="true"`
|
|
316
|
+
- camelCase names compile to kebab-case, so `dataMode` becomes `data-mode`
|
|
317
|
+
|
|
318
|
+
### 4.6 Global selector blocks
|
|
319
|
+
|
|
320
|
+
WEB has two special global CSS blocks:
|
|
321
|
+
|
|
322
|
+
```web
|
|
323
|
+
* {
|
|
324
|
+
boxSizing = "border-box";
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
html {
|
|
328
|
+
background = "#101522";
|
|
329
|
+
color = "#f7f6ff";
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Behavior:
|
|
334
|
+
|
|
335
|
+
- these compile directly to `*` and `html` CSS selectors
|
|
336
|
+
- they are CSS-only and never create DOM nodes
|
|
337
|
+
- they must appear at the top level
|
|
338
|
+
- they are also allowed inside a top-level `::media`
|
|
339
|
+
- nested `html { ... }` or `* { ... }` inside an element scope is invalid
|
|
340
|
+
|
|
341
|
+
These are not normal WEB nodes. They bypass tag inference and the document model.
|
|
342
|
+
|
|
343
|
+
### 4.7 Variables
|
|
344
|
+
|
|
345
|
+
Syntax:
|
|
346
|
+
|
|
347
|
+
```web
|
|
348
|
+
define {
|
|
349
|
+
@name = value;
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Examples:
|
|
354
|
+
|
|
355
|
+
```web
|
|
356
|
+
@pageWidth = "min(1120px, calc(100vw - 48px))";
|
|
357
|
+
@panelRadius = "24px";
|
|
358
|
+
@borderRule = "1px solid rgba(0, 0, 0, 0.08)";
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
Rules:
|
|
362
|
+
|
|
363
|
+
- variable names must start with `@`
|
|
364
|
+
- variables must be declared inside `define { ... }`
|
|
365
|
+
- variables may reference other variables
|
|
366
|
+
- variables may not use `js\`...\``
|
|
367
|
+
|
|
368
|
+
### 4.8 Dot notation
|
|
369
|
+
|
|
370
|
+
Syntax:
|
|
371
|
+
|
|
372
|
+
```web
|
|
373
|
+
pageMain.heroSection.heroTitle.textContent = "Hello";
|
|
374
|
+
pageMain.heroSection.padding = "24px";
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
Meaning:
|
|
378
|
+
|
|
379
|
+
- the path before the final segment identifies a node
|
|
380
|
+
- the final segment is the property name
|
|
381
|
+
|
|
382
|
+
### 4.9 Nested blocks
|
|
383
|
+
|
|
384
|
+
Syntax:
|
|
385
|
+
|
|
386
|
+
```web
|
|
387
|
+
pageMain {
|
|
388
|
+
heroSection {
|
|
389
|
+
heroTitle {
|
|
390
|
+
textContent = "Hello";
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
Equivalent to dot notation:
|
|
397
|
+
|
|
398
|
+
```web
|
|
399
|
+
pageMain.heroSection.heroTitle.textContent = "Hello";
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### 4.10 Dotted block scopes
|
|
403
|
+
|
|
404
|
+
Also valid:
|
|
405
|
+
|
|
406
|
+
```web
|
|
407
|
+
pageMain.heroSection {
|
|
408
|
+
heroTitle {
|
|
409
|
+
textContent = "Hello";
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### 4.11 Bare identifiers are not valid rules
|
|
415
|
+
|
|
416
|
+
This is invalid:
|
|
417
|
+
|
|
418
|
+
```web
|
|
419
|
+
pageMain
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
After a path, the parser expects either:
|
|
423
|
+
|
|
424
|
+
- `=` for an assignment
|
|
425
|
+
- `{` for a block
|
|
426
|
+
|
|
427
|
+
## 5. HTML Structure Model
|
|
428
|
+
|
|
429
|
+
WEB does not let you write literal HTML tags in normal source. It creates HTML by rebuilding a tree from rule paths.
|
|
430
|
+
|
|
431
|
+
Example:
|
|
432
|
+
|
|
433
|
+
```web
|
|
434
|
+
pageMain.heroSection.heroTitle.textContent = "Hello";
|
|
435
|
+
pageMain.heroSection.heroButton.textContent = "Launch";
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
This produces a DOM tree like:
|
|
439
|
+
|
|
440
|
+
- `pageMain`
|
|
441
|
+
- `heroSection` inside `pageMain`
|
|
442
|
+
- `heroTitle` inside `heroSection`
|
|
443
|
+
- `heroButton` inside `heroSection`
|
|
444
|
+
|
|
445
|
+
Important:
|
|
446
|
+
|
|
447
|
+
- each unique path maps to one element
|
|
448
|
+
- later assignments update the same node
|
|
449
|
+
- path identity matters more than declaration order
|
|
450
|
+
|
|
451
|
+
## 6. HTML Tag Inference
|
|
452
|
+
|
|
453
|
+
The compiler resolves each node name through the type symbol table, then infers an HTML tag from the resolved base type.
|
|
454
|
+
|
|
455
|
+
### 6.1 Declared names
|
|
456
|
+
|
|
457
|
+
Example:
|
|
458
|
+
|
|
459
|
+
```web
|
|
460
|
+
Button heroButton;
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
`heroButton` resolves to base type `Button`, so it renders as `<button>`.
|
|
464
|
+
|
|
465
|
+
### 6.2 Undeclared names
|
|
466
|
+
|
|
467
|
+
Undeclared names are allowed.
|
|
468
|
+
|
|
469
|
+
Example:
|
|
470
|
+
|
|
471
|
+
```web
|
|
472
|
+
pageCanvas {
|
|
473
|
+
width = "100%";
|
|
474
|
+
}
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
This still renders, because the compiler treats unknown names as external or built-in types and then runs tag inference on the name itself.
|
|
478
|
+
|
|
479
|
+
Examples:
|
|
480
|
+
|
|
481
|
+
- `pageCanvas` -> `<div>`
|
|
482
|
+
- `heroSection` -> `<section>`
|
|
483
|
+
- `siteHeader` -> `<header>`
|
|
484
|
+
- `primaryButton` -> `<button>`
|
|
485
|
+
|
|
486
|
+
If nothing matches, the fallback is:
|
|
487
|
+
|
|
488
|
+
- `<div>`
|
|
489
|
+
|
|
490
|
+
### 6.3 Built-in tag inference
|
|
491
|
+
|
|
492
|
+
Supported mappings include:
|
|
493
|
+
|
|
494
|
+
- `Heading`, `Heading1`, `H1` -> `<h1>`
|
|
495
|
+
- `Heading2`, `H2` -> `<h2>`
|
|
496
|
+
- `Heading3`, `H3` -> `<h3>`
|
|
497
|
+
- `Heading4`, `H4` -> `<h4>`
|
|
498
|
+
- `Heading5`, `H5` -> `<h5>`
|
|
499
|
+
- `Heading6`, `H6` -> `<h6>`
|
|
500
|
+
- `Button` -> `<button>`
|
|
501
|
+
- `Paragraph`, `Text` -> `<p>`
|
|
502
|
+
- `Span` -> `<span>`
|
|
503
|
+
- `Section` -> `<section>`
|
|
504
|
+
- `Main` -> `<main>`
|
|
505
|
+
- `Header` -> `<header>`
|
|
506
|
+
- `Footer` -> `<footer>`
|
|
507
|
+
- `Nav`, `Navigation` -> `<nav>`
|
|
508
|
+
- `Article` -> `<article>`
|
|
509
|
+
- `Aside` -> `<aside>`
|
|
510
|
+
- `Figure` -> `<figure>`
|
|
511
|
+
- `FigureCaption`, `FigCaption` -> `<figcaption>`
|
|
512
|
+
- `Link`, `Anchor` -> `<a>`
|
|
513
|
+
- `Picture` -> `<picture>`
|
|
514
|
+
- `Source` -> `<source>`
|
|
515
|
+
- `Image`, `Img` -> `<img>`
|
|
516
|
+
- `Video` -> `<video>`
|
|
517
|
+
- `Audio` -> `<audio>`
|
|
518
|
+
- `Form` -> `<form>`
|
|
519
|
+
- `Label` -> `<label>`
|
|
520
|
+
- `Dialog` -> `<dialog>`
|
|
521
|
+
- `Details` -> `<details>`
|
|
522
|
+
- `Summary` -> `<summary>`
|
|
523
|
+
- `Input` -> `<input>`
|
|
524
|
+
- `TextArea` -> `<textarea>`
|
|
525
|
+
- `Select` -> `<select>`
|
|
526
|
+
- `Option` -> `<option>`
|
|
527
|
+
- `List`, `UnorderedList`, `BulletList` -> `<ul>`
|
|
528
|
+
- `OrderedList`, `NumberedList` -> `<ol>`
|
|
529
|
+
- `ListItem` -> `<li>`
|
|
530
|
+
- `Table` -> `<table>`
|
|
531
|
+
- `TableHead` -> `<thead>`
|
|
532
|
+
- `TableBody` -> `<tbody>`
|
|
533
|
+
- `TableFoot` -> `<tfoot>`
|
|
534
|
+
- `TableRow` -> `<tr>`
|
|
535
|
+
- `TableHeaderCell` -> `<th>`
|
|
536
|
+
- `TableCell` -> `<td>`
|
|
537
|
+
- `Code` -> `<code>`
|
|
538
|
+
- `Pre`, `Preformatted` -> `<pre>`
|
|
539
|
+
- `Strong` -> `<strong>`
|
|
540
|
+
- `Em`, `Emphasis` -> `<em>`
|
|
541
|
+
- `LineBreak`, `Br` -> `<br>`
|
|
542
|
+
|
|
543
|
+
## 7. Reserved Properties
|
|
544
|
+
|
|
545
|
+
WEB has three special property names in normal DOM scopes:
|
|
546
|
+
|
|
547
|
+
- `textContent`
|
|
548
|
+
- `innerHTML`
|
|
549
|
+
- `raw`
|
|
550
|
+
|
|
551
|
+
Everything else is treated as a CSS property.
|
|
552
|
+
|
|
553
|
+
### 7.1 `textContent`
|
|
554
|
+
|
|
555
|
+
- inserts escaped text into the node
|
|
556
|
+
- safe for plain content
|
|
557
|
+
|
|
558
|
+
Example:
|
|
559
|
+
|
|
560
|
+
```web
|
|
561
|
+
heroCopy.textContent = "Safe plain text";
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
### 7.2 `innerHTML`
|
|
565
|
+
|
|
566
|
+
- inserts raw HTML into the node
|
|
567
|
+
- may also accept structured fragments returned from `js\`...\``
|
|
568
|
+
|
|
569
|
+
Example:
|
|
570
|
+
|
|
571
|
+
```web
|
|
572
|
+
heroCopy.innerHTML = "Read the <strong>docs</strong>.";
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
### 7.3 `::attrs`
|
|
576
|
+
|
|
577
|
+
Use `::attrs { ... }` when you want real HTML attributes on a normal WEB node.
|
|
578
|
+
|
|
579
|
+
Example:
|
|
580
|
+
|
|
581
|
+
```web
|
|
582
|
+
docsLink {
|
|
583
|
+
textContent = "Docs";
|
|
584
|
+
|
|
585
|
+
::attrs {
|
|
586
|
+
href = "/docs";
|
|
587
|
+
target = "_blank";
|
|
588
|
+
rel = "noreferrer";
|
|
589
|
+
ariaLabel = "Open docs";
|
|
590
|
+
dataTrack = "docs";
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
Behavior:
|
|
596
|
+
|
|
597
|
+
- `::attrs` must be nested directly inside an element scope
|
|
598
|
+
- it is not allowed inside `styles { ... }`, pseudo blocks, or media blocks
|
|
599
|
+
- it only supports direct attribute assignments
|
|
600
|
+
- attribute names are written in camelCase and compile to kebab-case
|
|
601
|
+
- `disabled = true;` emits a bare boolean attribute
|
|
602
|
+
- `open = true;` is useful for `Dialog` and `Details`
|
|
603
|
+
- `false`, `null`, and `undefined` omit the attribute
|
|
604
|
+
- `class` and `style` are not allowed in `::attrs`
|
|
605
|
+
|
|
606
|
+
### 7.4 `raw`
|
|
607
|
+
|
|
608
|
+
- inserts raw CSS associated with the current selector
|
|
609
|
+
- meant as an escape hatch
|
|
610
|
+
|
|
611
|
+
Example:
|
|
612
|
+
|
|
613
|
+
```web
|
|
614
|
+
heroButton.raw = `
|
|
615
|
+
transition: transform 0.2s ease;
|
|
616
|
+
&:hover {
|
|
617
|
+
transform: translateY(-1px);
|
|
618
|
+
}
|
|
619
|
+
`;
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
## 8. CSS Property Model
|
|
623
|
+
|
|
624
|
+
Any non-reserved property assignment becomes CSS.
|
|
625
|
+
|
|
626
|
+
Example:
|
|
627
|
+
|
|
628
|
+
```web
|
|
629
|
+
heroSection {
|
|
630
|
+
display = "grid";
|
|
631
|
+
gap = "18px";
|
|
632
|
+
backgroundColor = "#101522";
|
|
633
|
+
}
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
Compiles to CSS similar to:
|
|
637
|
+
|
|
638
|
+
```css
|
|
639
|
+
.heroSection {
|
|
640
|
+
display: grid;
|
|
641
|
+
gap: 18px;
|
|
642
|
+
background-color: #101522;
|
|
643
|
+
}
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
Rules:
|
|
647
|
+
|
|
648
|
+
- write CSS properties in camelCase
|
|
649
|
+
- compiler converts them to kebab-case
|
|
650
|
+
- later assignments to the same property on the same rule win
|
|
651
|
+
|
|
652
|
+
## 9. Value Types
|
|
653
|
+
|
|
654
|
+
WEB accepts these value kinds:
|
|
655
|
+
|
|
656
|
+
- quoted strings: `"grid"`
|
|
657
|
+
- plain template literals: `` `some text` ``
|
|
658
|
+
- numbers: `1`, `0.95`, `-8`
|
|
659
|
+
- percentages: `50%`
|
|
660
|
+
- bare identifiers: `none`, `auto`, `inherit`
|
|
661
|
+
- variable references: `@pageWidth`
|
|
662
|
+
- JavaScript injections: `` js`...` ``
|
|
663
|
+
|
|
664
|
+
Examples:
|
|
665
|
+
|
|
666
|
+
```web
|
|
667
|
+
heroSection.display = grid;
|
|
668
|
+
heroSection.opacity = 0.95;
|
|
669
|
+
heroSection.width = 100%;
|
|
670
|
+
heroSection.border = @borderRule;
|
|
671
|
+
heroSection.fontFamily = `"Helvetica Neue", Arial, sans-serif`;
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
Notes:
|
|
675
|
+
|
|
676
|
+
- plain template literals are just string-like values, not executed JavaScript
|
|
677
|
+
- object-like values are only possible through `js\`...\``
|
|
678
|
+
|
|
679
|
+
## 9.1 Comments
|
|
680
|
+
|
|
681
|
+
WEB source supports:
|
|
682
|
+
|
|
683
|
+
- line comments with `//`
|
|
684
|
+
- block comments with `/* ... */`
|
|
685
|
+
|
|
686
|
+
## 10. Selector Generation
|
|
687
|
+
|
|
688
|
+
WEB turns paths into descendant class selectors.
|
|
689
|
+
|
|
690
|
+
Example:
|
|
691
|
+
|
|
692
|
+
```web
|
|
693
|
+
pageMain.heroSection.heroButton.background = "#2d5cff";
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
Compiles to:
|
|
697
|
+
|
|
698
|
+
```css
|
|
699
|
+
.pageMain .heroSection .heroButton {
|
|
700
|
+
background: #2d5cff;
|
|
701
|
+
}
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
The generated HTML also uses the node name as the element class.
|
|
705
|
+
|
|
706
|
+
Example:
|
|
707
|
+
|
|
708
|
+
- node name `heroButton`
|
|
709
|
+
- generated HTML class `class="heroButton"`
|
|
710
|
+
|
|
711
|
+
## 11. `styles { ... }` Scope
|
|
712
|
+
|
|
713
|
+
`styles { ... }` creates CSS selectors without creating HTML nodes.
|
|
714
|
+
|
|
715
|
+
Use it when:
|
|
716
|
+
|
|
717
|
+
- `innerHTML = js\`...\`` returns class names that need styling
|
|
718
|
+
- you want descendant CSS rules only
|
|
719
|
+
|
|
720
|
+
Example:
|
|
721
|
+
|
|
722
|
+
```web
|
|
723
|
+
featureGrid {
|
|
724
|
+
innerHTML = js`
|
|
725
|
+
return node("Article", "featureCard", {
|
|
726
|
+
children: [
|
|
727
|
+
node("Heading3", "featureTitle", { textContent: "Readable" }),
|
|
728
|
+
],
|
|
729
|
+
});
|
|
730
|
+
`;
|
|
731
|
+
|
|
732
|
+
styles {
|
|
733
|
+
featureCard {
|
|
734
|
+
padding = "20px";
|
|
735
|
+
borderRadius = "20px";
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
featureTitle {
|
|
739
|
+
color = "#1e2852";
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
Rules:
|
|
746
|
+
|
|
747
|
+
- `styles { ... }` affects CSS only
|
|
748
|
+
- it does not create DOM nodes
|
|
749
|
+
- `textContent` is not allowed inside `styles`
|
|
750
|
+
- `innerHTML` is not allowed inside `styles`
|
|
751
|
+
- `raw` is allowed inside `styles`
|
|
752
|
+
- pseudo blocks are allowed inside `styles`
|
|
753
|
+
|
|
754
|
+
## 12. Pseudo System
|
|
755
|
+
|
|
756
|
+
WEB uses a unified `::...` syntax for:
|
|
757
|
+
|
|
758
|
+
- pseudo-classes
|
|
759
|
+
- pseudo-elements
|
|
760
|
+
- `::attrs`
|
|
761
|
+
- media queries
|
|
762
|
+
- keyframes
|
|
763
|
+
|
|
764
|
+
### 12.1 Pseudo selector blocks
|
|
765
|
+
|
|
766
|
+
Example:
|
|
767
|
+
|
|
768
|
+
```web
|
|
769
|
+
buttonCard {
|
|
770
|
+
background = "blue";
|
|
771
|
+
|
|
772
|
+
::hover {
|
|
773
|
+
background = "red";
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
Compiles to:
|
|
779
|
+
|
|
780
|
+
```css
|
|
781
|
+
.buttonCard {
|
|
782
|
+
background: blue;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
.buttonCard:hover {
|
|
786
|
+
background: red;
|
|
787
|
+
}
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
### 12.2 Mapping rules
|
|
791
|
+
|
|
792
|
+
The compiler normalizes pseudo names like this:
|
|
793
|
+
|
|
794
|
+
- pseudo-classes use a single CSS colon
|
|
795
|
+
- pseudo-elements use a double CSS colon
|
|
796
|
+
- camelCase is converted to kebab-case
|
|
797
|
+
|
|
798
|
+
Examples:
|
|
799
|
+
|
|
800
|
+
- `::hover` -> `:hover`
|
|
801
|
+
- `::focusWithin` -> `:focus-within`
|
|
802
|
+
- `::nthChild(2n + 1)` -> `:nth-child(2n + 1)`
|
|
803
|
+
- `::before` -> `::before`
|
|
804
|
+
- `::firstLetter` -> `::first-letter`
|
|
805
|
+
|
|
806
|
+
### 12.3 Nested pseudo descendants
|
|
807
|
+
|
|
808
|
+
Pseudo blocks attach to the scope where they are declared.
|
|
809
|
+
|
|
810
|
+
Example:
|
|
811
|
+
|
|
812
|
+
```web
|
|
813
|
+
card {
|
|
814
|
+
badge {
|
|
815
|
+
opacity = 0.4;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
::hover {
|
|
819
|
+
badge {
|
|
820
|
+
opacity = 1;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
Compiles conceptually to:
|
|
827
|
+
|
|
828
|
+
```css
|
|
829
|
+
.card .badge {
|
|
830
|
+
opacity: 0.4;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
.card:hover .badge {
|
|
834
|
+
opacity: 1;
|
|
835
|
+
}
|
|
836
|
+
```
|
|
837
|
+
|
|
838
|
+
### 12.4 Placement rule
|
|
839
|
+
|
|
840
|
+
Pseudo selector blocks must be nested inside:
|
|
841
|
+
|
|
842
|
+
- an element scope
|
|
843
|
+
- or a `styles { ... }` scope
|
|
844
|
+
|
|
845
|
+
This is invalid:
|
|
846
|
+
|
|
847
|
+
```web
|
|
848
|
+
::hover {
|
|
849
|
+
background = "red";
|
|
850
|
+
}
|
|
851
|
+
```
|
|
852
|
+
|
|
853
|
+
## 13. `::keyframes`
|
|
854
|
+
|
|
855
|
+
`::keyframes` creates top-level animation blocks.
|
|
856
|
+
|
|
857
|
+
Example:
|
|
858
|
+
|
|
859
|
+
```web
|
|
860
|
+
card {
|
|
861
|
+
animation = "slideIn 0.4s ease";
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
::keyframes slideIn {
|
|
865
|
+
from {
|
|
866
|
+
opacity = 0;
|
|
867
|
+
transform = "translateY(18px)";
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
50% {
|
|
871
|
+
opacity = 1;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
to {
|
|
875
|
+
opacity = 1;
|
|
876
|
+
transform = "translateY(0)";
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
```
|
|
880
|
+
|
|
881
|
+
Rules:
|
|
882
|
+
|
|
883
|
+
- must be top-level
|
|
884
|
+
- must not be inside elements
|
|
885
|
+
- must not be inside `styles { ... }`
|
|
886
|
+
- stages may only be `from`, `to`, or percentages like `50%`
|
|
887
|
+
- keyframe stages accept CSS property assignments only
|
|
888
|
+
- `textContent`, `innerHTML`, and `raw` are not allowed inside keyframe stages
|
|
889
|
+
|
|
890
|
+
## 14. `::media`
|
|
891
|
+
|
|
892
|
+
`::media (...)` creates CSS media query scopes.
|
|
893
|
+
|
|
894
|
+
### 14.1 Nested media
|
|
895
|
+
|
|
896
|
+
```web
|
|
897
|
+
card {
|
|
898
|
+
padding = "20px";
|
|
899
|
+
|
|
900
|
+
::media (max-width: 700px) {
|
|
901
|
+
padding = "14px";
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
```
|
|
905
|
+
|
|
906
|
+
### 14.2 Top-level media
|
|
907
|
+
|
|
908
|
+
```web
|
|
909
|
+
::media (max-width: 700px) {
|
|
910
|
+
pageMain {
|
|
911
|
+
gap = "18px";
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
```
|
|
915
|
+
|
|
916
|
+
### 14.3 Combined media and pseudo
|
|
917
|
+
|
|
918
|
+
```web
|
|
919
|
+
buttonCard {
|
|
920
|
+
::media (max-width: 700px) {
|
|
921
|
+
::hover {
|
|
922
|
+
background = "#2d5cff";
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
```
|
|
927
|
+
|
|
928
|
+
Rules:
|
|
929
|
+
|
|
930
|
+
- write the media condition in normal CSS syntax
|
|
931
|
+
- `::media` can contain selector blocks, style scopes, pseudo blocks, properties, and `raw`
|
|
932
|
+
- `textContent` and `innerHTML` are not allowed inside media scopes
|
|
933
|
+
- type declarations and variables are not allowed inside media scopes
|
|
934
|
+
|
|
935
|
+
## 15. Raw CSS Escape Hatch
|
|
936
|
+
|
|
937
|
+
Use `raw` only when WEB syntax is not expressive enough.
|
|
938
|
+
|
|
939
|
+
Supported raw behaviors:
|
|
940
|
+
|
|
941
|
+
- normal declarations
|
|
942
|
+
- nested selectors with `&`
|
|
943
|
+
- nested selectors beginning with `:` or `[` attach to the parent selector
|
|
944
|
+
- at-rules such as `@supports`
|
|
945
|
+
- passthrough at-rules such as `@keyframes`, `@font-face`, `@property`, `@counter-style`, `@page`
|
|
946
|
+
|
|
947
|
+
Example:
|
|
948
|
+
|
|
949
|
+
```web
|
|
950
|
+
heroButton.raw = `
|
|
951
|
+
transition: transform 0.25s ease;
|
|
952
|
+
&:hover {
|
|
953
|
+
transform: translateY(-2px);
|
|
954
|
+
}
|
|
955
|
+
@supports (backdrop-filter: blur(12px)) {
|
|
956
|
+
backdrop-filter: blur(12px);
|
|
957
|
+
}
|
|
958
|
+
`;
|
|
959
|
+
```
|
|
960
|
+
|
|
961
|
+
Guideline:
|
|
962
|
+
|
|
963
|
+
- prefer normal properties first
|
|
964
|
+
- then `styles { ... }`
|
|
965
|
+
- then `::pseudo`
|
|
966
|
+
- then `::media`
|
|
967
|
+
- use `raw` last
|
|
968
|
+
|
|
969
|
+
## 16. Compile-Time JavaScript
|
|
970
|
+
|
|
971
|
+
`js\`...\`` executes at compile time inside Node via `vm`.
|
|
972
|
+
|
|
973
|
+
It is never emitted as browser JavaScript.
|
|
974
|
+
|
|
975
|
+
Timeout:
|
|
976
|
+
|
|
977
|
+
- 1 second
|
|
978
|
+
|
|
979
|
+
Error behavior:
|
|
980
|
+
|
|
981
|
+
- runtime errors become inline text
|
|
982
|
+
- syntax errors become inline text
|
|
983
|
+
- timeouts become inline text
|
|
984
|
+
- JS failure does not crash compilation
|
|
985
|
+
|
|
986
|
+
### 16.1 Result resolution order
|
|
987
|
+
|
|
988
|
+
The compiler resolves a JS block in this order:
|
|
989
|
+
|
|
990
|
+
1. explicit `return`
|
|
991
|
+
2. output collected through `emit(value)`
|
|
992
|
+
3. the last top-level declared variable in the JS source
|
|
993
|
+
4. empty string
|
|
994
|
+
|
|
995
|
+
Example:
|
|
996
|
+
|
|
997
|
+
```web
|
|
998
|
+
heroCopy.textContent = js`
|
|
999
|
+
emit("Hello ");
|
|
1000
|
+
emit("world");
|
|
1001
|
+
`;
|
|
1002
|
+
```
|
|
1003
|
+
|
|
1004
|
+
### 16.2 How results are converted
|
|
1005
|
+
|
|
1006
|
+
For normal string-like contexts:
|
|
1007
|
+
|
|
1008
|
+
- strings stay strings
|
|
1009
|
+
- numbers become strings
|
|
1010
|
+
- booleans become strings
|
|
1011
|
+
- plain objects are JSON-stringified if possible
|
|
1012
|
+
- `null` and `undefined` become empty strings
|
|
1013
|
+
|
|
1014
|
+
For `innerHTML = js\`...\``:
|
|
1015
|
+
|
|
1016
|
+
- structured fragment values are preserved and rendered as HTML
|
|
1017
|
+
|
|
1018
|
+
For `textContent = js\`...\``:
|
|
1019
|
+
|
|
1020
|
+
- fragment values are not preserved as structure
|
|
1021
|
+
- errors become escaped text
|
|
1022
|
+
|
|
1023
|
+
## 17. JavaScript Injection API
|
|
1024
|
+
|
|
1025
|
+
The compile-time JS sandbox exposes these helpers:
|
|
1026
|
+
|
|
1027
|
+
- `emit(value)`
|
|
1028
|
+
- `node(typeName, className?, options?)`
|
|
1029
|
+
- `el(...)` as an alias for `node(...)`
|
|
1030
|
+
- `fragment(children)`
|
|
1031
|
+
- `text(value)`
|
|
1032
|
+
- `html(value)`
|
|
1033
|
+
|
|
1034
|
+
The sandbox also exposes a muted `console`:
|
|
1035
|
+
|
|
1036
|
+
- `console.log`
|
|
1037
|
+
- `console.info`
|
|
1038
|
+
- `console.warn`
|
|
1039
|
+
- `console.error`
|
|
1040
|
+
|
|
1041
|
+
These do nothing.
|
|
1042
|
+
|
|
1043
|
+
### 17.1 `emit(value)`
|
|
1044
|
+
|
|
1045
|
+
Appends text to an internal output buffer.
|
|
1046
|
+
|
|
1047
|
+
Rules:
|
|
1048
|
+
|
|
1049
|
+
- `emit(undefined)` and `emit(null)` do not append text
|
|
1050
|
+
- returns the current accumulated buffer
|
|
1051
|
+
- only matters if no explicit `return` overrides it
|
|
1052
|
+
|
|
1053
|
+
### 17.2 `node(typeName, className?, options?)`
|
|
1054
|
+
|
|
1055
|
+
Creates a structured fragment node.
|
|
1056
|
+
|
|
1057
|
+
Example:
|
|
1058
|
+
|
|
1059
|
+
```web
|
|
1060
|
+
cards.innerHTML = js`
|
|
1061
|
+
return node("Article", "featureCard", {
|
|
1062
|
+
children: [
|
|
1063
|
+
node("Heading3", "featureTitle", { textContent: "Fast" }),
|
|
1064
|
+
node("Paragraph", "featureCopy", { textContent: "Generated in a loop." }),
|
|
1065
|
+
],
|
|
1066
|
+
});
|
|
1067
|
+
`;
|
|
1068
|
+
```
|
|
1069
|
+
|
|
1070
|
+
Rules:
|
|
1071
|
+
|
|
1072
|
+
- `typeName` must be a non-empty string
|
|
1073
|
+
- `className` may be a string, array, or omitted
|
|
1074
|
+
- `options` must be an object when provided
|
|
1075
|
+
- `typeName` goes through the same HTML tag inference as normal WEB nodes
|
|
1076
|
+
|
|
1077
|
+
### 17.3 `node(...)` options
|
|
1078
|
+
|
|
1079
|
+
Supported options:
|
|
1080
|
+
|
|
1081
|
+
- `className`
|
|
1082
|
+
- `textContent`
|
|
1083
|
+
- `innerHTML`
|
|
1084
|
+
- `children`
|
|
1085
|
+
- `style`
|
|
1086
|
+
- `attributes`
|
|
1087
|
+
|
|
1088
|
+
Details:
|
|
1089
|
+
|
|
1090
|
+
- `textContent` becomes escaped text
|
|
1091
|
+
- `innerHTML` is inserted raw
|
|
1092
|
+
- `children` may be a single child, an array, or nested arrays
|
|
1093
|
+
- `style` may be a string or an object
|
|
1094
|
+
- `attributes` must be an object
|
|
1095
|
+
|
|
1096
|
+
### 17.4 `style` option
|
|
1097
|
+
|
|
1098
|
+
Examples:
|
|
1099
|
+
|
|
1100
|
+
```js
|
|
1101
|
+
style: "padding: 20px; color: #123;"
|
|
1102
|
+
```
|
|
1103
|
+
|
|
1104
|
+
```js
|
|
1105
|
+
style: {
|
|
1106
|
+
padding: "20px",
|
|
1107
|
+
backgroundColor: "#eef2ff",
|
|
1108
|
+
}
|
|
1109
|
+
```
|
|
1110
|
+
|
|
1111
|
+
Behavior:
|
|
1112
|
+
|
|
1113
|
+
- object keys are converted from camelCase to kebab-case
|
|
1114
|
+
- falsey values `undefined`, `null`, and `false` are omitted
|
|
1115
|
+
- resulting styles are emitted inline on the fragment node
|
|
1116
|
+
|
|
1117
|
+
### 17.5 `attributes` option
|
|
1118
|
+
|
|
1119
|
+
Example:
|
|
1120
|
+
|
|
1121
|
+
```js
|
|
1122
|
+
attributes: {
|
|
1123
|
+
href: "/docs",
|
|
1124
|
+
target: "_blank",
|
|
1125
|
+
disabled: true,
|
|
1126
|
+
}
|
|
1127
|
+
```
|
|
1128
|
+
|
|
1129
|
+
Behavior:
|
|
1130
|
+
|
|
1131
|
+
- `undefined`, `null`, and `false` omit the attribute
|
|
1132
|
+
- `true` emits a bare boolean attribute
|
|
1133
|
+
- all other values are stringified and escaped
|
|
1134
|
+
|
|
1135
|
+
Important:
|
|
1136
|
+
|
|
1137
|
+
- normal WEB blocks support real HTML attributes through `::attrs { ... }`
|
|
1138
|
+
- use JS fragment `attributes` when the node itself is being generated inside `js\`...\``
|
|
1139
|
+
|
|
1140
|
+
### 17.6 `fragment(children)`
|
|
1141
|
+
|
|
1142
|
+
Groups children without adding a wrapper element.
|
|
1143
|
+
|
|
1144
|
+
### 17.7 `text(value)`
|
|
1145
|
+
|
|
1146
|
+
Creates an escaped text node.
|
|
1147
|
+
|
|
1148
|
+
### 17.8 `html(value)`
|
|
1149
|
+
|
|
1150
|
+
Creates a raw HTML fragment node.
|
|
1151
|
+
|
|
1152
|
+
Use this sparingly.
|
|
1153
|
+
|
|
1154
|
+
## 18. Void Elements
|
|
1155
|
+
|
|
1156
|
+
Void tags cannot contain content or children.
|
|
1157
|
+
|
|
1158
|
+
Relevant built-ins include:
|
|
1159
|
+
|
|
1160
|
+
- `Image` / `Img`
|
|
1161
|
+
- `Input`
|
|
1162
|
+
- `LineBreak` / `Br`
|
|
1163
|
+
- `Source`
|
|
1164
|
+
|
|
1165
|
+
This rule applies both to:
|
|
1166
|
+
|
|
1167
|
+
- normal WEB nodes
|
|
1168
|
+
- JS fragment nodes
|
|
1169
|
+
|
|
1170
|
+
## 19. What Creates HTML vs What Creates CSS
|
|
1171
|
+
|
|
1172
|
+
This distinction is critical for agents.
|
|
1173
|
+
|
|
1174
|
+
### Creates HTML and CSS
|
|
1175
|
+
|
|
1176
|
+
- normal assignments in DOM scopes
|
|
1177
|
+
- nested blocks under normal element scopes
|
|
1178
|
+
|
|
1179
|
+
### Creates HTML only
|
|
1180
|
+
|
|
1181
|
+
- `::attrs { ... }`
|
|
1182
|
+
- `::head { ... }`
|
|
1183
|
+
- `::script { ... }`
|
|
1184
|
+
|
|
1185
|
+
### Creates CSS only
|
|
1186
|
+
|
|
1187
|
+
- top-level `html { ... }` and `* { ... }`
|
|
1188
|
+
- style inheritance declarations inside `define { ... }` such as `storeCard extends card;`
|
|
1189
|
+
- `styles { ... }`
|
|
1190
|
+
- `::hover`, `::before`, other pseudo blocks
|
|
1191
|
+
- `::media (...)`
|
|
1192
|
+
- `::keyframes`
|
|
1193
|
+
- `raw`
|
|
1194
|
+
|
|
1195
|
+
### Creates compile-time generated HTML
|
|
1196
|
+
|
|
1197
|
+
- `innerHTML = js\`...\`` when the JS returns strings or structured fragments
|
|
1198
|
+
|
|
1199
|
+
## 20. Common Agent Mistakes
|
|
1200
|
+
|
|
1201
|
+
Avoid these:
|
|
1202
|
+
|
|
1203
|
+
### Mistake 1: treating normal properties as HTML attributes
|
|
1204
|
+
|
|
1205
|
+
This does not set a real anchor `href`:
|
|
1206
|
+
|
|
1207
|
+
```web
|
|
1208
|
+
docsLink.href = "/docs";
|
|
1209
|
+
```
|
|
1210
|
+
|
|
1211
|
+
It becomes CSS:
|
|
1212
|
+
|
|
1213
|
+
```css
|
|
1214
|
+
.docsLink {
|
|
1215
|
+
href: /docs;
|
|
1216
|
+
}
|
|
1217
|
+
```
|
|
1218
|
+
|
|
1219
|
+
Use `::attrs { ... }` on normal WEB nodes:
|
|
1220
|
+
|
|
1221
|
+
```web
|
|
1222
|
+
docsLink {
|
|
1223
|
+
::attrs {
|
|
1224
|
+
href = "/docs";
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
```
|
|
1228
|
+
|
|
1229
|
+
Use JS fragment `attributes` only when the node is created inside `js\`...\``.
|
|
1230
|
+
|
|
1231
|
+
### Mistake 2: putting variables after rules
|
|
1232
|
+
|
|
1233
|
+
Invalid:
|
|
1234
|
+
|
|
1235
|
+
```web
|
|
1236
|
+
pageMain.width = "100%";
|
|
1237
|
+
define {
|
|
1238
|
+
@pageWidth = "960px";
|
|
1239
|
+
}
|
|
1240
|
+
```
|
|
1241
|
+
|
|
1242
|
+
### Mistake 3: putting `::keyframes` inside a block
|
|
1243
|
+
|
|
1244
|
+
Invalid:
|
|
1245
|
+
|
|
1246
|
+
```web
|
|
1247
|
+
card {
|
|
1248
|
+
::keyframes fadeIn {
|
|
1249
|
+
from {
|
|
1250
|
+
opacity = 0;
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
```
|
|
1255
|
+
|
|
1256
|
+
### Mistake 4: using `textContent` inside `styles`
|
|
1257
|
+
|
|
1258
|
+
Invalid:
|
|
1259
|
+
|
|
1260
|
+
```web
|
|
1261
|
+
heroSection {
|
|
1262
|
+
styles {
|
|
1263
|
+
title {
|
|
1264
|
+
textContent = "Hello";
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
```
|
|
1269
|
+
|
|
1270
|
+
### Mistake 5: confusing type inheritance with style inheritance
|
|
1271
|
+
|
|
1272
|
+
This is false today.
|
|
1273
|
+
|
|
1274
|
+
Type inheritance affects:
|
|
1275
|
+
|
|
1276
|
+
- tag resolution
|
|
1277
|
+
- semantic naming
|
|
1278
|
+
|
|
1279
|
+
It does not affect CSS sharing.
|
|
1280
|
+
|
|
1281
|
+
Use this for semantic inheritance:
|
|
1282
|
+
|
|
1283
|
+
```web
|
|
1284
|
+
card storeCard;
|
|
1285
|
+
```
|
|
1286
|
+
|
|
1287
|
+
Use this for CSS inheritance:
|
|
1288
|
+
|
|
1289
|
+
```web
|
|
1290
|
+
storeCard extends card;
|
|
1291
|
+
```
|
|
1292
|
+
|
|
1293
|
+
Use both if you want both.
|
|
1294
|
+
|
|
1295
|
+
Type declarations alone do not affect:
|
|
1296
|
+
|
|
1297
|
+
- CSS property inheritance inside the compiler
|
|
1298
|
+
|
|
1299
|
+
### Mistake 6: overusing declarations that are not needed
|
|
1300
|
+
|
|
1301
|
+
If a node name already infers the right tag, you may omit the declaration.
|
|
1302
|
+
|
|
1303
|
+
Example:
|
|
1304
|
+
|
|
1305
|
+
- `heroSection` already infers `<section>`
|
|
1306
|
+
- `siteHeader` already infers `<header>`
|
|
1307
|
+
- `pageCanvas` falls back to `<div>`
|
|
1308
|
+
|
|
1309
|
+
## 21. Recommended Authoring Strategy For Agents
|
|
1310
|
+
|
|
1311
|
+
When generating new WEB code:
|
|
1312
|
+
|
|
1313
|
+
1. declare only the types that improve semantics or clarity
|
|
1314
|
+
2. use undeclared names freely for wrapper `<div>`s
|
|
1315
|
+
3. prefer nested blocks for readability
|
|
1316
|
+
4. put variables, type declarations, and style inheritance inside one `define { ... }` block
|
|
1317
|
+
5. use variables for repeated primitive values
|
|
1318
|
+
6. use normal property assignments before raw CSS
|
|
1319
|
+
7. use `::attrs { ... }` for real HTML attributes on normal WEB nodes
|
|
1320
|
+
8. use `styles { ... }` for classes created by JS fragments
|
|
1321
|
+
9. use `::pseudo` and `::media` before `raw`
|
|
1322
|
+
10. use `js\`...\`` only when loops, conditionals, or fragment generation are genuinely needed
|
|
1323
|
+
|
|
1324
|
+
## 22. Canonical Authoring Template
|
|
1325
|
+
|
|
1326
|
+
```web
|
|
1327
|
+
define {
|
|
1328
|
+
@pageWidth = "min(1120px, calc(100vw - 48px))";
|
|
1329
|
+
@panelRadius = "24px";
|
|
1330
|
+
@borderRule = "1px solid rgba(0, 0, 0, 0.08)";
|
|
1331
|
+
|
|
1332
|
+
Main pageMain;
|
|
1333
|
+
Article card;
|
|
1334
|
+
card storeCard;
|
|
1335
|
+
storeCard extends card;
|
|
1336
|
+
Heading heroTitle;
|
|
1337
|
+
Paragraph heroCopy;
|
|
1338
|
+
Button heroButton;
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
pageMain {
|
|
1342
|
+
width = @pageWidth;
|
|
1343
|
+
display = "grid";
|
|
1344
|
+
gap = "24px";
|
|
1345
|
+
|
|
1346
|
+
heroSection {
|
|
1347
|
+
padding = "32px";
|
|
1348
|
+
border = @borderRule;
|
|
1349
|
+
borderRadius = @panelRadius;
|
|
1350
|
+
|
|
1351
|
+
heroLink {
|
|
1352
|
+
textContent = "Docs";
|
|
1353
|
+
|
|
1354
|
+
::attrs {
|
|
1355
|
+
href = "/docs";
|
|
1356
|
+
target = "_blank";
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
heroTitle {
|
|
1361
|
+
textContent = "Build with WEB";
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
heroCopy {
|
|
1365
|
+
textContent = "One source file, compiled to HTML and CSS.";
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
heroButton {
|
|
1369
|
+
textContent = "Launch";
|
|
1370
|
+
|
|
1371
|
+
::hover {
|
|
1372
|
+
transform = "translateY(-1px)";
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
featureGrid {
|
|
1378
|
+
innerHTML = js`
|
|
1379
|
+
const items = ["Readable", "Composable", "Static"];
|
|
1380
|
+
return items.map((label) =>
|
|
1381
|
+
node("Article", "featureCard", {
|
|
1382
|
+
children: [
|
|
1383
|
+
node("Heading3", "featureTitle", { textContent: label }),
|
|
1384
|
+
],
|
|
1385
|
+
})
|
|
1386
|
+
);
|
|
1387
|
+
`;
|
|
1388
|
+
|
|
1389
|
+
styles {
|
|
1390
|
+
featureCard {
|
|
1391
|
+
padding = "20px";
|
|
1392
|
+
border = @borderRule;
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
::media (max-width: 700px) {
|
|
1399
|
+
pageMain {
|
|
1400
|
+
gap = "16px";
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
```
|
|
1404
|
+
|
|
1405
|
+
## 23. Summary
|
|
1406
|
+
|
|
1407
|
+
WEB is a path-based declarative language with:
|
|
1408
|
+
|
|
1409
|
+
- optional semantic type declarations
|
|
1410
|
+
- one optional top-level `define { ... }` prelude
|
|
1411
|
+
- variables inside `define`
|
|
1412
|
+
- CSS inheritance declarations with `extends` inside `define`
|
|
1413
|
+
- nested or dotted rule syntax
|
|
1414
|
+
- real HTML attributes through `::attrs`
|
|
1415
|
+
- literal head tags through top-level `::head`
|
|
1416
|
+
- literal script tags through top-level `::script`
|
|
1417
|
+
- CSS property generation
|
|
1418
|
+
- CSS-only `styles { ... }` scopes
|
|
1419
|
+
- first-class `::pseudo`
|
|
1420
|
+
- top-level `::keyframes`
|
|
1421
|
+
- first-class `::media`
|
|
1422
|
+
- compile-time `js` with a small fragment API
|
|
1423
|
+
- `raw` as a last-resort CSS escape hatch
|
|
1424
|
+
|
|
1425
|
+
The most important operational truth for agents is:
|
|
1426
|
+
|
|
1427
|
+
- normal scopes create DOM
|
|
1428
|
+
- `::attrs` adds HTML attributes to DOM nodes
|
|
1429
|
+
- `::head` adds literal meta/link tags to the document head
|
|
1430
|
+
- `::script` adds literal script tags to the HTML output
|
|
1431
|
+
- style scopes and pseudo/media scopes create CSS
|
|
1432
|
+
- JS runs only at compile time
|
|
1433
|
+
- JS fragment `attributes` is only needed when the node itself is generated inside `js\`...\``
|