@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
|
@@ -0,0 +1,945 @@
|
|
|
1
|
+
# WEB Language Guide
|
|
2
|
+
|
|
3
|
+
WEB is a small declarative UI language that compiles `.web` source into HTML and CSS.
|
|
4
|
+
|
|
5
|
+
Run the compiler with:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
node compiler.js
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
That legacy script mode reads `layout.web` from the current directory and writes `index.html` plus `styles.css`.
|
|
12
|
+
|
|
13
|
+
You can also use the CLI to compile any `.web` file next to its source:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
web home.web
|
|
17
|
+
web home
|
|
18
|
+
web .
|
|
19
|
+
web ./code/*
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
CLI mode writes matching outputs such as `home.html` and `home.css`.
|
|
23
|
+
|
|
24
|
+
## Recommended Build Order
|
|
25
|
+
|
|
26
|
+
WEB is easiest to use in this sequence:
|
|
27
|
+
|
|
28
|
+
1. Define the HTML structure.
|
|
29
|
+
2. Add a `define { ... }` block for variables, semantic types, and shared style inheritance.
|
|
30
|
+
3. Add HTML attributes plus any top-level `::head` and `::script` blocks.
|
|
31
|
+
4. Add states and animations with `::pseudo` blocks.
|
|
32
|
+
5. Add responsive rules with `::media`.
|
|
33
|
+
6. Use `raw` only for CSS that WEB does not express yet.
|
|
34
|
+
|
|
35
|
+
## 1. Structure and HTML Output
|
|
36
|
+
|
|
37
|
+
Start by setting up one optional `define { ... }` block for shared declarations.
|
|
38
|
+
|
|
39
|
+
### Define block
|
|
40
|
+
|
|
41
|
+
All variables, semantic type declarations, and CSS inheritance declarations must live inside a single top-level `define { ... }` block.
|
|
42
|
+
|
|
43
|
+
```web
|
|
44
|
+
define {
|
|
45
|
+
@pageWidth = "960px";
|
|
46
|
+
Button actionButton;
|
|
47
|
+
actionButton primaryButton;
|
|
48
|
+
primaryButton extends actionButton;
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Rules:
|
|
53
|
+
|
|
54
|
+
- `define { ... }` is optional, but required if you use variables, type declarations, or style inheritance
|
|
55
|
+
- it must appear before rules
|
|
56
|
+
- only one `define { ... }` block is allowed
|
|
57
|
+
- only variables, type declarations, and style inheritance are allowed inside it
|
|
58
|
+
|
|
59
|
+
### Type declarations
|
|
60
|
+
|
|
61
|
+
Use this form:
|
|
62
|
+
|
|
63
|
+
```web
|
|
64
|
+
define {
|
|
65
|
+
BaseType NewType;
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Example:
|
|
70
|
+
|
|
71
|
+
```web
|
|
72
|
+
define {
|
|
73
|
+
Button actionButton;
|
|
74
|
+
actionButton primaryButton;
|
|
75
|
+
Main pageMain;
|
|
76
|
+
Section heroSection;
|
|
77
|
+
Paragraph heroCopy;
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Rules:
|
|
82
|
+
|
|
83
|
+
- declarations live inside `define { ... }`
|
|
84
|
+
- types can inherit from other declared types
|
|
85
|
+
- the resolved base type decides which HTML tag is generated
|
|
86
|
+
|
|
87
|
+
Type declarations do not copy CSS. If you want one WEB object or class to inherit another object's styles, use `extends` inside `define { ... }`.
|
|
88
|
+
|
|
89
|
+
### Supported HTML mapping
|
|
90
|
+
|
|
91
|
+
WEB does not let you write raw HTML tags directly. It infers them from the resolved base type.
|
|
92
|
+
|
|
93
|
+
Common built-in mappings:
|
|
94
|
+
|
|
95
|
+
- `Button` -> `<button>`
|
|
96
|
+
- `Heading`, `Heading1`, `H1` -> `<h1>`
|
|
97
|
+
- `Heading2`, `H2` -> `<h2>`
|
|
98
|
+
- `Heading3`, `H3` -> `<h3>`
|
|
99
|
+
- `Heading4`, `H4` -> `<h4>`
|
|
100
|
+
- `Heading5`, `H5` -> `<h5>`
|
|
101
|
+
- `Heading6`, `H6` -> `<h6>`
|
|
102
|
+
- `Paragraph`, `Text` -> `<p>`
|
|
103
|
+
- `Span` -> `<span>`
|
|
104
|
+
- `Section` -> `<section>`
|
|
105
|
+
- `Main` -> `<main>`
|
|
106
|
+
- `Header` -> `<header>`
|
|
107
|
+
- `Footer` -> `<footer>`
|
|
108
|
+
- `Nav`, `Navigation` -> `<nav>`
|
|
109
|
+
- `Article` -> `<article>`
|
|
110
|
+
- `Aside` -> `<aside>`
|
|
111
|
+
- `Figure` -> `<figure>`
|
|
112
|
+
- `FigureCaption`, `FigCaption` -> `<figcaption>`
|
|
113
|
+
- `Link`, `Anchor` -> `<a>`
|
|
114
|
+
- `Picture` -> `<picture>`
|
|
115
|
+
- `Source` -> `<source>`
|
|
116
|
+
- `Image`, `Img` -> `<img>`
|
|
117
|
+
- `Video` -> `<video>`
|
|
118
|
+
- `Audio` -> `<audio>`
|
|
119
|
+
- `Form` -> `<form>`
|
|
120
|
+
- `Label` -> `<label>`
|
|
121
|
+
- `Dialog` -> `<dialog>`
|
|
122
|
+
- `Details` -> `<details>`
|
|
123
|
+
- `Summary` -> `<summary>`
|
|
124
|
+
- `Input` -> `<input>`
|
|
125
|
+
- `TextArea` -> `<textarea>`
|
|
126
|
+
- `Select` -> `<select>`
|
|
127
|
+
- `Option` -> `<option>`
|
|
128
|
+
- `List`, `UnorderedList`, `BulletList` -> `<ul>`
|
|
129
|
+
- `OrderedList`, `NumberedList` -> `<ol>`
|
|
130
|
+
- `ListItem` -> `<li>`
|
|
131
|
+
- `Table` -> `<table>`
|
|
132
|
+
- `TableHead` -> `<thead>`
|
|
133
|
+
- `TableBody` -> `<tbody>`
|
|
134
|
+
- `TableFoot` -> `<tfoot>`
|
|
135
|
+
- `TableRow` -> `<tr>`
|
|
136
|
+
- `TableHeaderCell` -> `<th>`
|
|
137
|
+
- `TableCell` -> `<td>`
|
|
138
|
+
- `Code` -> `<code>`
|
|
139
|
+
- `Pre`, `Preformatted` -> `<pre>`
|
|
140
|
+
- `Strong` -> `<strong>`
|
|
141
|
+
- `Em`, `Emphasis` -> `<em>`
|
|
142
|
+
- `LineBreak`, `Br` -> `<br>`
|
|
143
|
+
- anything else -> `<div>`
|
|
144
|
+
|
|
145
|
+
Void elements such as `Image`, `Input`, `LineBreak`, and `Source` cannot contain children or content.
|
|
146
|
+
|
|
147
|
+
### Dot notation
|
|
148
|
+
|
|
149
|
+
Dot notation builds hierarchy directly from the path.
|
|
150
|
+
|
|
151
|
+
```web
|
|
152
|
+
heroSection.primaryButton.textContent = "Launch";
|
|
153
|
+
heroSection.primaryButton.padding = "14px 18px";
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
That produces a `primaryButton` inside `heroSection`.
|
|
157
|
+
|
|
158
|
+
### Nested blocks
|
|
159
|
+
|
|
160
|
+
Nested blocks are syntax sugar for the same structure.
|
|
161
|
+
|
|
162
|
+
```web
|
|
163
|
+
pageMain {
|
|
164
|
+
heroSection {
|
|
165
|
+
primaryButton {
|
|
166
|
+
textContent = "Launch";
|
|
167
|
+
padding = "14px 18px";
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
These forms are equivalent:
|
|
174
|
+
|
|
175
|
+
```web
|
|
176
|
+
pageMain.heroSection.primaryButton.textContent = "Launch";
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
```web
|
|
180
|
+
pageMain {
|
|
181
|
+
heroSection {
|
|
182
|
+
primaryButton {
|
|
183
|
+
textContent = "Launch";
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Dot notation blocks are also supported:
|
|
190
|
+
|
|
191
|
+
```web
|
|
192
|
+
pageMain.heroSection {
|
|
193
|
+
primaryButton {
|
|
194
|
+
textContent = "Launch";
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Global style blocks
|
|
200
|
+
|
|
201
|
+
Two special top-level blocks map directly to global CSS selectors:
|
|
202
|
+
|
|
203
|
+
```web
|
|
204
|
+
* {
|
|
205
|
+
boxSizing = "border-box";
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
html {
|
|
209
|
+
background = "#101522";
|
|
210
|
+
color = "#f7f6ff";
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
That compiles to:
|
|
215
|
+
|
|
216
|
+
```css
|
|
217
|
+
* {
|
|
218
|
+
box-sizing: border-box;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
html {
|
|
222
|
+
background: #101522;
|
|
223
|
+
color: #f7f6ff;
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Rules:
|
|
228
|
+
|
|
229
|
+
- `* { ... }` and `html { ... }` are CSS-only global selector blocks
|
|
230
|
+
- they do not create HTML nodes
|
|
231
|
+
- they must be declared at the top level
|
|
232
|
+
- they are also allowed inside a top-level `::media` block
|
|
233
|
+
- nested `html { ... }` inside an element block is not allowed
|
|
234
|
+
|
|
235
|
+
### Content properties
|
|
236
|
+
|
|
237
|
+
WEB has three reserved properties:
|
|
238
|
+
|
|
239
|
+
- `textContent`
|
|
240
|
+
- `innerHTML`
|
|
241
|
+
- `raw`
|
|
242
|
+
|
|
243
|
+
`textContent` inserts escaped text:
|
|
244
|
+
|
|
245
|
+
```web
|
|
246
|
+
heroSection.heroCopy.textContent = "Safe plain text";
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
`innerHTML` inserts raw HTML:
|
|
250
|
+
|
|
251
|
+
```web
|
|
252
|
+
heroSection.heroCopy.innerHTML = "Read the <strong>docs</strong>.";
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### HTML attributes with `::attrs`
|
|
256
|
+
|
|
257
|
+
Use `::attrs { ... }` when you want real HTML attributes on a normal WEB node.
|
|
258
|
+
|
|
259
|
+
```web
|
|
260
|
+
docsLink {
|
|
261
|
+
textContent = "Docs";
|
|
262
|
+
|
|
263
|
+
::attrs {
|
|
264
|
+
href = "/docs";
|
|
265
|
+
target = "_blank";
|
|
266
|
+
rel = "noreferrer";
|
|
267
|
+
ariaLabel = "Open docs";
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Rules:
|
|
273
|
+
|
|
274
|
+
- `::attrs` must be nested directly inside an element block
|
|
275
|
+
- attribute names use camelCase in WEB and compile to kebab-case in HTML
|
|
276
|
+
- `disabled = true;` emits a bare boolean attribute
|
|
277
|
+
- `open = true;` is useful for tags like `Dialog` and `Details`
|
|
278
|
+
- `false`, `null`, and `undefined` omit the attribute
|
|
279
|
+
- `class` and `style` are not allowed in `::attrs`
|
|
280
|
+
- `::attrs` is not allowed inside `styles { ... }`, pseudo blocks, or media blocks
|
|
281
|
+
|
|
282
|
+
### Head tags with `::head`
|
|
283
|
+
|
|
284
|
+
Use `::head { ... }` when you want to add `meta` and `link` tags directly to the document `<head>`.
|
|
285
|
+
|
|
286
|
+
```web
|
|
287
|
+
::head {
|
|
288
|
+
meta {
|
|
289
|
+
name = "description";
|
|
290
|
+
content = "Landing page built in WEB.";
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
link {
|
|
294
|
+
rel = "help";
|
|
295
|
+
href = "compiler.md";
|
|
296
|
+
type = "text/markdown";
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
That compiles to entries like:
|
|
302
|
+
|
|
303
|
+
```html
|
|
304
|
+
<meta name="description" content="Landing page built in WEB.">
|
|
305
|
+
<link rel="help" href="compiler.md" type="text/markdown">
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Rules:
|
|
309
|
+
|
|
310
|
+
- `::head` must be declared at the top level
|
|
311
|
+
- it does not participate in CSS generation
|
|
312
|
+
- it only supports `meta { ... }` and `link { ... }` entries
|
|
313
|
+
- `meta` and `link` entries only support direct HTML attribute assignments
|
|
314
|
+
- attribute names use camelCase in WEB and compile to kebab-case in HTML
|
|
315
|
+
- custom head entries are inserted into `<head>` after the compiler’s default charset, viewport, title, and stylesheet tags
|
|
316
|
+
|
|
317
|
+
### Script tags with `::script`
|
|
318
|
+
|
|
319
|
+
Use `::script { ... }` when you want the compiler to emit a literal `<script></script>` tag.
|
|
320
|
+
|
|
321
|
+
```web
|
|
322
|
+
::script {
|
|
323
|
+
src = "/assets/app.js";
|
|
324
|
+
defer = "true";
|
|
325
|
+
|
|
326
|
+
code {
|
|
327
|
+
function helloWorld () {
|
|
328
|
+
alert("hello world");
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
That compiles to:
|
|
335
|
+
|
|
336
|
+
```html
|
|
337
|
+
<script src="/assets/app.js" defer="true">
|
|
338
|
+
function helloWorld () {
|
|
339
|
+
alert("hello world");
|
|
340
|
+
}
|
|
341
|
+
</script>
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Rules:
|
|
345
|
+
|
|
346
|
+
- `::script` must be declared at the top level
|
|
347
|
+
- it does not participate in CSS generation
|
|
348
|
+
- it supports direct HTML attribute assignments like `src = "/app.js";`
|
|
349
|
+
- it supports one optional `code { ... }` block for inline script content
|
|
350
|
+
- the `code { ... }` body is copied into the output script tag as raw text
|
|
351
|
+
- script blocks render near the end of `<body>` after normal WEB nodes
|
|
352
|
+
- `defer = true;` emits a bare boolean attribute, while `defer = "true";` emits `defer="true"`
|
|
353
|
+
|
|
354
|
+
### JavaScript injection
|
|
355
|
+
|
|
356
|
+
WEB supports compile-time JavaScript values with:
|
|
357
|
+
|
|
358
|
+
```web
|
|
359
|
+
heroSection.heroCopy.textContent = js`
|
|
360
|
+
let count = 3;
|
|
361
|
+
let output = "";
|
|
362
|
+
|
|
363
|
+
for (let i = 0; i < count; i += 1) {
|
|
364
|
+
output += `Hello ${i} `;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return output;
|
|
368
|
+
`;
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
How it works:
|
|
372
|
+
|
|
373
|
+
- the compiler wraps the contents in a function and executes it at compile time
|
|
374
|
+
- the final result is inserted into the generated output
|
|
375
|
+
- errors are rendered inline as text instead of crashing the compiler
|
|
376
|
+
|
|
377
|
+
### JavaScript result order
|
|
378
|
+
|
|
379
|
+
WEB resolves a `js` block in this order:
|
|
380
|
+
|
|
381
|
+
1. an explicit `return`
|
|
382
|
+
2. text accumulated through `emit(value)`
|
|
383
|
+
3. the last top-level declared variable inside the `js` block
|
|
384
|
+
4. an empty string
|
|
385
|
+
|
|
386
|
+
Example:
|
|
387
|
+
|
|
388
|
+
```web
|
|
389
|
+
heroSection.heroCopy.textContent = js`
|
|
390
|
+
emit("Hello ");
|
|
391
|
+
emit("world");
|
|
392
|
+
`;
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### Structured fragment output
|
|
396
|
+
|
|
397
|
+
When `innerHTML` is used with `js`, the `js` block may return structured fragment nodes instead of raw tag strings.
|
|
398
|
+
|
|
399
|
+
Helpers available inside `js` blocks:
|
|
400
|
+
|
|
401
|
+
- `node(typeName, className?, options?)`
|
|
402
|
+
- `el(...)` as an alias for `node(...)`
|
|
403
|
+
- `fragment(children)`
|
|
404
|
+
- `text(value)`
|
|
405
|
+
- `html(value)`
|
|
406
|
+
|
|
407
|
+
Supported `node(...)` options:
|
|
408
|
+
|
|
409
|
+
- `textContent`
|
|
410
|
+
- `innerHTML`
|
|
411
|
+
- `children`
|
|
412
|
+
- `style`
|
|
413
|
+
- `attributes`
|
|
414
|
+
|
|
415
|
+
Use `::attrs { ... }` for normal WEB nodes. Use `node(..., { attributes: ... })` only for nodes generated inside `js`.
|
|
416
|
+
|
|
417
|
+
Example:
|
|
418
|
+
|
|
419
|
+
```web
|
|
420
|
+
featureGrid.innerHTML = js`
|
|
421
|
+
const cards = [
|
|
422
|
+
{ title: "Readable", copy: "Less tag-heavy JS output." },
|
|
423
|
+
{ title: "Loop-friendly", copy: "Still generated at compile time." },
|
|
424
|
+
];
|
|
425
|
+
|
|
426
|
+
return cards.map((card) =>
|
|
427
|
+
node("Article", "featureCard", {
|
|
428
|
+
children: [
|
|
429
|
+
node("Heading3", "featureTitle", { textContent: card.title }),
|
|
430
|
+
node("Paragraph", "featureCopy", { textContent: card.copy }),
|
|
431
|
+
],
|
|
432
|
+
})
|
|
433
|
+
);
|
|
434
|
+
`;
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
This keeps loops and conditional logic in JavaScript while still avoiding explicit raw HTML tags in the source.
|
|
438
|
+
|
|
439
|
+
## 2. Basic Styling and Positioning
|
|
440
|
+
|
|
441
|
+
Once the structure exists, add normal CSS properties directly in WEB.
|
|
442
|
+
|
|
443
|
+
### Property assignments
|
|
444
|
+
|
|
445
|
+
Any non-reserved property becomes CSS.
|
|
446
|
+
|
|
447
|
+
```web
|
|
448
|
+
heroSection {
|
|
449
|
+
display = "grid";
|
|
450
|
+
gap = "18px";
|
|
451
|
+
padding = "32px";
|
|
452
|
+
backgroundColor = "#101522";
|
|
453
|
+
}
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
Compiles to:
|
|
457
|
+
|
|
458
|
+
```css
|
|
459
|
+
.heroSection {
|
|
460
|
+
display: grid;
|
|
461
|
+
gap: 18px;
|
|
462
|
+
padding: 32px;
|
|
463
|
+
background-color: #101522;
|
|
464
|
+
}
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
Rules:
|
|
468
|
+
|
|
469
|
+
- CSS property names should be written in camelCase
|
|
470
|
+
- the compiler converts them to kebab-case in `styles.css`
|
|
471
|
+
- later assignments for the same property on the same rule win
|
|
472
|
+
|
|
473
|
+
### Values
|
|
474
|
+
|
|
475
|
+
WEB accepts these value types:
|
|
476
|
+
|
|
477
|
+
- quoted strings: `"grid"`
|
|
478
|
+
- numbers: `1`, `0.95`, `-10`
|
|
479
|
+
- percentages: `50%`
|
|
480
|
+
- bare identifiers: `none`, `auto`, `inherit`
|
|
481
|
+
- variable references: `@pageWidth`
|
|
482
|
+
- template literals: `` `...` ``
|
|
483
|
+
- JavaScript inject blocks: `` js`...` ``
|
|
484
|
+
|
|
485
|
+
Examples:
|
|
486
|
+
|
|
487
|
+
```web
|
|
488
|
+
heroSection.opacity = 0.95;
|
|
489
|
+
heroSection.width = 100%;
|
|
490
|
+
heroSection.display = grid;
|
|
491
|
+
heroSection.border = @borderRule;
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
### Variables
|
|
495
|
+
|
|
496
|
+
Use variables inside `define { ... }` to avoid repeating primitive values.
|
|
497
|
+
|
|
498
|
+
```web
|
|
499
|
+
define {
|
|
500
|
+
@pageWidth = "960px";
|
|
501
|
+
@panelRadius = "24px";
|
|
502
|
+
@borderRule = "1px solid #dde5ee";
|
|
503
|
+
}
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
Then reuse them:
|
|
507
|
+
|
|
508
|
+
```web
|
|
509
|
+
pageMain.width = @pageWidth;
|
|
510
|
+
heroSection.border = @borderRule;
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
Rules:
|
|
514
|
+
|
|
515
|
+
- variable names must start with `@`
|
|
516
|
+
- variables must be declared inside `define { ... }`
|
|
517
|
+
- variables can reference other variables
|
|
518
|
+
- variables only support simple values, not `js` blocks
|
|
519
|
+
|
|
520
|
+
### Style inheritance
|
|
521
|
+
|
|
522
|
+
Use `extends` inside `define { ... }` when you want one WEB name to inherit another name's CSS.
|
|
523
|
+
|
|
524
|
+
Syntax:
|
|
525
|
+
|
|
526
|
+
```web
|
|
527
|
+
define {
|
|
528
|
+
storeCard extends card;
|
|
529
|
+
}
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
Example:
|
|
533
|
+
|
|
534
|
+
```web
|
|
535
|
+
define {
|
|
536
|
+
Article card;
|
|
537
|
+
card storeCard;
|
|
538
|
+
storeCard extends card;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
card {
|
|
542
|
+
padding = "20px";
|
|
543
|
+
borderRadius = "20px";
|
|
544
|
+
background = "#eef3ff";
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
storeCard {
|
|
548
|
+
background = "#ffffff";
|
|
549
|
+
}
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
This means:
|
|
553
|
+
|
|
554
|
+
- `storeCard` inherits the CSS generated for `card`
|
|
555
|
+
- `storeCard` keeps its own explicit styles too
|
|
556
|
+
- explicit styles on `storeCard` win over inherited styles from `card`
|
|
557
|
+
|
|
558
|
+
Rules:
|
|
559
|
+
|
|
560
|
+
- style inheritance declarations must be declared inside `define { ... }`
|
|
561
|
+
- style inheritance is CSS-only and does not change HTML tag inference
|
|
562
|
+
- style inheritance is transitive
|
|
563
|
+
- circular style inheritance is not allowed
|
|
564
|
+
- closer ancestors win over farther ancestors
|
|
565
|
+
- explicit styles on the derived name win over inherited styles, even if the base rule appears later in the file
|
|
566
|
+
|
|
567
|
+
What gets inherited:
|
|
568
|
+
|
|
569
|
+
- normal property assignments
|
|
570
|
+
- nested descendant rules
|
|
571
|
+
- `styles { ... }` selectors
|
|
572
|
+
- pseudo blocks
|
|
573
|
+
- media-query blocks
|
|
574
|
+
- `raw` blocks anchored to the inherited selector
|
|
575
|
+
|
|
576
|
+
If you also want the derived object to share the same semantic HTML tag, combine type inheritance and style inheritance:
|
|
577
|
+
|
|
578
|
+
```web
|
|
579
|
+
define {
|
|
580
|
+
Article card;
|
|
581
|
+
card storeCard;
|
|
582
|
+
storeCard extends card;
|
|
583
|
+
}
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
### Style scopes
|
|
587
|
+
|
|
588
|
+
Use `styles { ... }` when you want CSS selectors without creating matching HTML nodes.
|
|
589
|
+
|
|
590
|
+
This is especially useful for classes returned from `innerHTML = js\`...\``.
|
|
591
|
+
|
|
592
|
+
```web
|
|
593
|
+
featureGrid {
|
|
594
|
+
innerHTML = js`
|
|
595
|
+
return node("Article", ["featureCard", "featureCardCool"], {
|
|
596
|
+
children: [
|
|
597
|
+
node("Heading3", ["featureTitle", "featureTitleCool"], {
|
|
598
|
+
textContent: "Readable",
|
|
599
|
+
}),
|
|
600
|
+
node("Paragraph", "featureCopy", {
|
|
601
|
+
textContent: "Styled from outer WEB syntax.",
|
|
602
|
+
}),
|
|
603
|
+
],
|
|
604
|
+
});
|
|
605
|
+
`;
|
|
606
|
+
|
|
607
|
+
styles {
|
|
608
|
+
featureCard {
|
|
609
|
+
padding = "20px";
|
|
610
|
+
borderRadius = "20px";
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
featureCardCool {
|
|
614
|
+
background = "linear-gradient(180deg, #ffffff 0%, #eef6ff 100%)";
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
featureTitleCool {
|
|
618
|
+
color = "#225fbe";
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
featureCopy {
|
|
622
|
+
color = "#596780";
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
Rules:
|
|
629
|
+
|
|
630
|
+
- `styles { ... }` emits descendant CSS selectors
|
|
631
|
+
- it does not create matching HTML nodes
|
|
632
|
+
- `textContent` and `innerHTML` are not allowed inside `styles`
|
|
633
|
+
- `raw` is still allowed inside `styles`
|
|
634
|
+
|
|
635
|
+
### Selector generation
|
|
636
|
+
|
|
637
|
+
WEB maps paths to descendant class selectors.
|
|
638
|
+
|
|
639
|
+
```web
|
|
640
|
+
pageMain.heroSection.primaryButton.backgroundColor = "#ff7070";
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
Compiles to:
|
|
644
|
+
|
|
645
|
+
```css
|
|
646
|
+
.pageMain .heroSection .primaryButton {
|
|
647
|
+
background-color: #ff7070;
|
|
648
|
+
}
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
## 3. Pseudo Selectors and Animations
|
|
652
|
+
|
|
653
|
+
WEB now has a first-class pseudo system.
|
|
654
|
+
|
|
655
|
+
### Pseudo selector syntax
|
|
656
|
+
|
|
657
|
+
All pseudo selectors are written with a `::` prefix in WEB.
|
|
658
|
+
|
|
659
|
+
Example:
|
|
660
|
+
|
|
661
|
+
```web
|
|
662
|
+
benefitCard {
|
|
663
|
+
background = "blue";
|
|
664
|
+
|
|
665
|
+
::hover {
|
|
666
|
+
background = "red";
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
Compiles to:
|
|
672
|
+
|
|
673
|
+
```css
|
|
674
|
+
.benefitCard {
|
|
675
|
+
background: blue;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
.benefitCard:hover {
|
|
679
|
+
background: red;
|
|
680
|
+
}
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
### Mapping rules
|
|
684
|
+
|
|
685
|
+
WEB normalizes pseudo names like this:
|
|
686
|
+
|
|
687
|
+
- pseudo-classes compile to a single colon in CSS
|
|
688
|
+
- pseudo-elements compile to a double colon in CSS
|
|
689
|
+
- camelCase pseudo names are converted to kebab-case
|
|
690
|
+
|
|
691
|
+
Examples:
|
|
692
|
+
|
|
693
|
+
- `::hover` -> `:hover`
|
|
694
|
+
- `::focusWithin` -> `:focus-within`
|
|
695
|
+
- `::nthChild(2n + 1)` -> `:nth-child(2n + 1)`
|
|
696
|
+
- `::before` -> `::before`
|
|
697
|
+
- `::firstLetter` -> `::first-letter`
|
|
698
|
+
|
|
699
|
+
### Nested descendant rules
|
|
700
|
+
|
|
701
|
+
Pseudo blocks stay attached to the scope where they are declared.
|
|
702
|
+
|
|
703
|
+
```web
|
|
704
|
+
benefitCard {
|
|
705
|
+
icon {
|
|
706
|
+
opacity = 0.4;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
::hover {
|
|
710
|
+
icon {
|
|
711
|
+
opacity = 1;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
Compiles to:
|
|
718
|
+
|
|
719
|
+
```css
|
|
720
|
+
.benefitCard .icon {
|
|
721
|
+
opacity: 0.4;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
.benefitCard:hover .icon {
|
|
725
|
+
opacity: 1;
|
|
726
|
+
}
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
### Pseudo selectors inside style scopes
|
|
730
|
+
|
|
731
|
+
Pseudo blocks also work inside `styles { ... }`.
|
|
732
|
+
|
|
733
|
+
```web
|
|
734
|
+
fragmentMount {
|
|
735
|
+
styles {
|
|
736
|
+
card {
|
|
737
|
+
padding = "12px";
|
|
738
|
+
|
|
739
|
+
::hover {
|
|
740
|
+
background = "#dbe7ff";
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
### `::keyframes`
|
|
748
|
+
|
|
749
|
+
Animations use a top-level `::keyframes` block.
|
|
750
|
+
|
|
751
|
+
Example:
|
|
752
|
+
|
|
753
|
+
```web
|
|
754
|
+
card {
|
|
755
|
+
animation = "slideIn 3s";
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
::keyframes slideIn {
|
|
759
|
+
from {
|
|
760
|
+
opacity = 0;
|
|
761
|
+
transform = "translateY(16px)";
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
60% {
|
|
765
|
+
opacity = 1;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
to {
|
|
769
|
+
opacity = 1;
|
|
770
|
+
transform = "translateY(0)";
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
Compiles to:
|
|
776
|
+
|
|
777
|
+
```css
|
|
778
|
+
.card {
|
|
779
|
+
animation: slideIn 3s;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
@keyframes slideIn {
|
|
783
|
+
from {
|
|
784
|
+
opacity: 0;
|
|
785
|
+
transform: translateY(16px);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
60% {
|
|
789
|
+
opacity: 1;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
to {
|
|
793
|
+
opacity: 1;
|
|
794
|
+
transform: translateY(0);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
Rules:
|
|
800
|
+
|
|
801
|
+
- `::keyframes` must be declared at the top level
|
|
802
|
+
- `::keyframes` cannot be nested inside element blocks or `styles { ... }`
|
|
803
|
+
- keyframe stages support `from`, `to`, and percentages like `50%`
|
|
804
|
+
- keyframe stages accept normal CSS property assignments
|
|
805
|
+
|
|
806
|
+
Invalid:
|
|
807
|
+
|
|
808
|
+
```web
|
|
809
|
+
card {
|
|
810
|
+
::keyframes slideIn {
|
|
811
|
+
from {
|
|
812
|
+
opacity = 0;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
## 4. Media Queries
|
|
819
|
+
|
|
820
|
+
Media queries use the same `::` syntax.
|
|
821
|
+
|
|
822
|
+
### Nested media rules
|
|
823
|
+
|
|
824
|
+
```web
|
|
825
|
+
benefitCard {
|
|
826
|
+
padding = "20px";
|
|
827
|
+
|
|
828
|
+
::media (max-width: 700px) {
|
|
829
|
+
padding = "14px";
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
Compiles to:
|
|
835
|
+
|
|
836
|
+
```css
|
|
837
|
+
.benefitCard {
|
|
838
|
+
padding: 20px;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
@media (max-width: 700px) {
|
|
842
|
+
.benefitCard {
|
|
843
|
+
padding: 14px;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
```
|
|
847
|
+
|
|
848
|
+
### Top-level media blocks
|
|
849
|
+
|
|
850
|
+
You can also wrap whole rule groups:
|
|
851
|
+
|
|
852
|
+
```web
|
|
853
|
+
::media (max-width: 700px) {
|
|
854
|
+
pageMain {
|
|
855
|
+
gap = "18px";
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
pageMain.heroSection {
|
|
859
|
+
padding = "20px";
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
```
|
|
863
|
+
|
|
864
|
+
### Combining media and pseudo selectors
|
|
865
|
+
|
|
866
|
+
Media blocks and pseudo blocks can be combined.
|
|
867
|
+
|
|
868
|
+
```web
|
|
869
|
+
button {
|
|
870
|
+
::media (max-width: 700px) {
|
|
871
|
+
::hover {
|
|
872
|
+
background = "#2d5cff";
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
This compiles to:
|
|
879
|
+
|
|
880
|
+
```css
|
|
881
|
+
@media (max-width: 700px) {
|
|
882
|
+
.button:hover {
|
|
883
|
+
background: #2d5cff;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
Rules:
|
|
889
|
+
|
|
890
|
+
- `::media` accepts normal CSS media query text after the keyword
|
|
891
|
+
- media query conditions should be written in normal CSS syntax such as `max-width`, not camelCase
|
|
892
|
+
- media blocks only support CSS properties, selector blocks, pseudo blocks, `styles { ... }`, and `raw`
|
|
893
|
+
- `textContent`, `innerHTML`, type declarations, and variable declarations are not allowed inside media blocks
|
|
894
|
+
|
|
895
|
+
## 5. Raw CSS Escape Hatch
|
|
896
|
+
|
|
897
|
+
`raw` still exists for CSS that WEB does not express yet.
|
|
898
|
+
|
|
899
|
+
Example:
|
|
900
|
+
|
|
901
|
+
```web
|
|
902
|
+
heroSection.primaryButton.raw = `
|
|
903
|
+
transition: transform 0.3s ease;
|
|
904
|
+
&:hover {
|
|
905
|
+
transform: scale(1.05);
|
|
906
|
+
}
|
|
907
|
+
`;
|
|
908
|
+
```
|
|
909
|
+
|
|
910
|
+
Use `raw` when you need:
|
|
911
|
+
|
|
912
|
+
- CSS selectors that WEB does not model yet
|
|
913
|
+
- at-rules other than `::media` and `::keyframes`
|
|
914
|
+
- complex CSS text that is easier to paste directly
|
|
915
|
+
|
|
916
|
+
`raw` supports:
|
|
917
|
+
|
|
918
|
+
- normal declarations
|
|
919
|
+
- nested selectors using `&`
|
|
920
|
+
- at-rules such as `@supports`
|
|
921
|
+
|
|
922
|
+
For new code, prefer:
|
|
923
|
+
|
|
924
|
+
- normal property assignments first
|
|
925
|
+
- `styles { ... }` for selector-only styling
|
|
926
|
+
- `::pseudo` blocks for states and pseudo-elements
|
|
927
|
+
- `::media` for responsive rules
|
|
928
|
+
|
|
929
|
+
## Comments
|
|
930
|
+
|
|
931
|
+
WEB supports:
|
|
932
|
+
|
|
933
|
+
- line comments with `//`
|
|
934
|
+
- block comments with `/* ... */`
|
|
935
|
+
|
|
936
|
+
## JavaScript Safety
|
|
937
|
+
|
|
938
|
+
JavaScript injection is designed not to crash the compiler.
|
|
939
|
+
|
|
940
|
+
- runtime errors are rendered inline as text
|
|
941
|
+
- syntax errors are rendered inline as text
|
|
942
|
+
- timed-out scripts are rendered inline as text
|
|
943
|
+
- when `innerHTML` generation fails, the error is still escaped as text rather than inserted as raw HTML
|
|
944
|
+
|
|
945
|
+
JavaScript blocks currently run with a 1-second timeout.
|