└── readme.md /readme.md: -------------------------------------------------------------------------------- 1 | ## Interoperable Style Transfer Format [DRAFT]. 2 | 3 | The biggest issue at scale we currently have with all CSSinJS implementations is that they are not sharable without the runtime. 4 | 5 | Using regular CSS doesn't support all features we need. CSS notation is designed for humans and is too slow for parsing. 6 | 7 | This format is [blazingly fast](https://esbench.com/bench/592d599e99634800a03483d8) to parse and will support all needs of the current CSSinJS implementations. 8 | 9 | Package maintainers will be able to publish JavaScript files to NPM in this format instead of plain CSS or any variation of objects notation. 10 | 11 | Do not confuse this format with an AST. It is designed purely for interoperability and publishing. Using this format you can build an AST or regular CSS or any other structure. 12 | 13 | ### Glossary 14 | 15 | The following are some terms that are used throughout the spec, or are relevant when discussing the spec. 16 | 17 | - **ISTF array:** Array containing ISTF Rules 18 | - **ISTF rule:** Subset of ISTF array, containing rule selector and declaration 19 | - **ISTF selector:** Subset of ISTF rule, containing only rule selector 20 | - **ISTF declaration:** Subset of ISTF rule, containing only properties and values 21 | - **ISTF property:** Subset of ISTF rule declarations, containing only properties 22 | - **ISTF values:** Subset of ISTF rule declarations, containing only values 23 | - **Markers:** Like nodes in an AST format, these tuples indicate significant literals and values (See [Markers](#markers)) 24 | - **References:** References to primitives in the host language, typically known as "interpolations" in tagged template literals in JavaScript 25 | - **Parsing:** The process of converting the input to an ISTF array 26 | - **Transformation:** The act of changing an ISTF array 27 | - **Runtime:** The user environment wherein ISTF is turned into CSS and rendered 28 | - **Preprocessing:** Transforming ISTF arrays to make it ready for distribution and transformation 29 | - **Postprocessing:** Last transformation steps of ISTF during runtime, like autoprefixing 30 | - **Evaluation:** Evaluating references to be able to put them into the CSS string during stringification 31 | - **Stringification:** Turning ISTF into regular CSS, which might involve evaluating references 32 | - **Rendering:** Using the resulting CSS and displaying the result 33 | 34 | ### Markers 35 | 36 | Examples are using constant names instead of values for readability within the spec. The resulting format will use their values. 37 | 38 | ```js 39 | const RULE_START = 0 40 | const RULE_END = 1 41 | const RULE_NAME = 2 42 | const SELECTOR = 3 43 | const PARENT_SELECTOR = 4 44 | const UNIVERSAL_SELECTOR = 5 45 | const COMPOUND_SELECTOR_START = 6 46 | const COMPOUND_SELECTOR_END = 7 47 | const SPACE_COMBINATOR = 8 48 | const DOUBLED_CHILD_COMBINATOR = 9 49 | const CHILD_COMBINATOR = 10 50 | const NEXT_SIBLING_COMBINATOR = 11 51 | const SUBSEQUENT_SIBLING_COMBINATOR = 12 52 | const PROPERTY = 13 53 | const VALUE = 14 54 | const COMPOUND_VALUE_START = 15 55 | const COMPOUND_VALUE_END = 16 56 | const CONDITION = 17 57 | const FUNCTION_START = 18 58 | const FUNCTION_END = 19 59 | const ANIMATION_NAME = 20 60 | const SELECTOR_REF = 21 61 | const PROPERTY_REF = 22 62 | const VALUE_REF = 23 63 | const PARTIAL_REF = 24 64 | const STRING_START = 25 65 | const STRING_END = 26 66 | ``` 67 | 68 | #### Rule start 69 | 70 | `[RULE_START, ]` 71 | 72 | Marker `RULE_START` specifies the beginning of a rule, has a rule type. 73 | 74 | All rule types from https://wiki.csswg.org/spec/cssom-constants 75 | 76 | ``` 77 | 1 STYLE_RULE CSSOM 78 | 2 CHARSET_RULE CSSOM 79 | 3 IMPORT_RULE CSSOM 80 | 4 MEDIA_RULE CSSOM 81 | 5 FONT_FACE_RULE CSSOM 82 | 6 PAGE_RULE CSSOM 83 | 7 KEYFRAMES_RULE css3-animations 84 | 8 KEYFRAME_RULE css3-animations 85 | 9 MARGIN_RULE CSSOM 86 | 10 NAMESPACE_RULE CSSOM 87 | 11 COUNTER_STYLE_RULE css3-lists 88 | 12 SUPPORTS_RULE css3-conditional 89 | 13 DOCUMENT_RULE css3-conditional 90 | 14 FONT_FEATURE_VALUES_RULE css3-fonts 91 | 15 VIEWPORT_RULE css-device-adapt 92 | 16 REGION_STYLE_RULE proposed for css3-regions 93 | 17 CUSTOM_MEDIA_RULE mediaqueries 94 | ``` 95 | 96 | #### Rule end 97 | 98 | `[RULE_END]` 99 | 100 | Marker `RULE_END` specifies the end of a rule. 101 | 102 | #### Named rule. 103 | 104 | `[RULE_NAME, ]` 105 | 106 | Marker `RULE_NAME` specifies a rule name. 107 | 108 | Instead of using a selector, a rule can be identified by a name. This is a preferred way instead of using hardcoded selectors. Consumer library will generate a selector for the rule and user can access it by name. 109 | We rely on the JavaScript module scope. Name should be unique within that module scope. 110 | 111 | #### Selector 112 | 113 | `[SELECTOR, ]` 114 | 115 | Marker `SELECTOR` specifies a selector. 116 | 117 | Selector e.g. `.foo` => `[SELECTOR, '.foo']` 118 | 119 | Selector list e.g. `.foo, .bar` => `[SELECTOR, '.foo'], [SELECTOR, '.bar']` 120 | 121 | #### Compound selector 122 | 123 | `[COMPOUND_SELECTOR_START], [SELECTOR, ]+, [*COMBINATOR]?, [COMPOUND_SELECTOR_END]` 124 | 125 | [Compound](https://drafts.csswg.org/selectors/#compound) selector consists of simple selectors which might be separated by [combinators](https://drafts.csswg.org/selectors/#combinators). 126 | 127 | Selector compound without combinators e.g. `.foo.bar` => 128 | 129 | ```js 130 | [SELECTOR_COMPOUND_START], 131 | [SELECTOR, '.foo'], 132 | [SELECTOR, '.bar'], 133 | [SELECTOR_COMPOUND_END] 134 | ``` 135 | 136 | Selector compound with space combinator e.g. `.foo .bar` => 137 | 138 | ```js 139 | [SELECTOR_COMPOUND_START], 140 | [SELECTOR, '.foo'], 141 | [SPACE_COMBINATOR], 142 | [SELECTOR, '.bar'], 143 | [SELECTOR_COMPOUND_END] 144 | ``` 145 | 146 | Selector compound with next sibling combinator e.g. `.foo + .bar` => 147 | 148 | ```js 149 | [SELECTOR_COMPOUND_START], 150 | [SELECTOR, '.foo'], 151 | [NEXT_SIBLING_COMBINATOR], 152 | [SELECTOR, '.bar'], 153 | [SELECTOR_COMPOUND_END] 154 | ``` 155 | 156 | #### Parent selector 157 | 158 | `[PARENT_SELECTOR]` 159 | 160 | Marker `PARENT_SELECTOR` specifies a selector, which is a reference to the parent selector, `&`. 161 | Useful for nesting. 162 | 163 | E.g.: `&:hover` => 164 | 165 | ```js 166 | [COMPOUND_SELECTOR_START] 167 | [PARENT_SELECTOR], 168 | [SELECTOR, ':hover'], 169 | [COMPOUND_SELECTOR_END] 170 | ``` 171 | 172 | 173 | #### Universal selector 174 | 175 | `[UNIVERSAL_SELECTOR]` 176 | 177 | Marker `UNIVERSAL_SELECTOR` specifies a selector, which is a reference to the universal selector, `*`. 178 | 179 | E.g.: `*.red` => 180 | 181 | ```js 182 | [COMPOUND_SELECTOR_START] 183 | [UNIVERSAL_SELECTOR], 184 | [SELECTOR, '.red'], 185 | [COMPOUND_SELECTOR_END] 186 | ``` 187 | 188 | #### Property name 189 | 190 | `[PROPERTY, ]` 191 | 192 | Marker `PROPERTY` specifies a property name e.g.: `color` => `[PROPERTY, 'color']`. 193 | 194 | #### Simple value 195 | 196 | `[VALUE, ]` 197 | 198 | Marker `VALUE` specifies a property value e.g.: `red` => `[VALUE, 'red']`. 199 | 200 | #### Values list 201 | 202 | `[VALUE, 'red']+` 203 | 204 | A comma separated list of simple values e.g.: `red, green` => `[VALUE, 'red'], [VALUE, 'green']`. 205 | 206 | 207 | #### Compound value 208 | 209 | `[COMPOUND_VALUE_START], [VALUE, ]+, [COMPOUND_VALUE_END]` 210 | 211 | A value that consists of multiple space separated simple values: e.g.: `10px 20px` => 212 | 213 | ```js 214 | [COMPOUND_VALUE_START], 215 | [VALUE, '10px'], 216 | [VALUE, '20px'], 217 | [COMPOUND_VALUE_END] 218 | ``` 219 | #### Function 220 | 221 | `[FUNCTION_START, ], [SELECTOR|VALUE, ], [FUNCTION_END]` 222 | 223 | Function specifies [functional pseudo class](https://drafts.csswg.org/selectors/#functional-pseudo-class) as well as [functional notation](https://drafts.csswg.org/css-values-4/#functional-notation). 224 | 225 | Functional pseudo class e.g. `.foo:matches(:hover, :focus)` => 226 | 227 | ```js 228 | [SELECTOR_COMPOUND_START], 229 | [SELECTOR, '.foo'], 230 | [FUNCTION_START, ':matches'], 231 | [SELECTOR, ':hover'], 232 | [SELECTOR, ':focus'], 233 | [FUNCTION_END], 234 | [SELECTOR_COMPOUND_END] 235 | ``` 236 | 237 | Functional value notation e.g.: `color: rgb(100, 200, 50)` => 238 | 239 | ```js 240 | [PROPERTY, 'color'], 241 | [FUNCTION_START, 'rgb'], 242 | [VALUE, 100], 243 | [VALUE, 200], 244 | [VALUE, 50], 245 | [FUNCTION_END] 246 | ``` 247 | 248 | ### Condition 249 | 250 | `[CONDITION, ]` 251 | 252 | Marker `CONDITION` specifies a condition for conditional rules. 253 | 254 | E.g. `@media all` => `[RULE_START, 4], [CONDITION, 'all']` 255 | 256 | #### Value reference 257 | 258 | `[VALUE_REF, ref]` 259 | 260 | Variable `ref` can be ISTF values, a string or a function returning any of those. Using ISTF values gives more power to post-processors. Using string value results in better performance. 261 | 262 | `border: red, green` => 263 | 264 | ```js 265 | // Using ISTF values: 266 | [PROPERTY, 'border'], 267 | [VALUE_REF, () => [ 268 | [VALUE, 'red'], 269 | [VALUE, 'green'], 270 | ]] 271 | 272 | // Using a string value: 273 | [VALUE_REF, () => 'red, green'] 274 | ``` 275 | 276 | #### Property reference 277 | 278 | `[PROPERTY_REF, ref]` 279 | 280 | Variable `ref` is a string or a function which returns a string. 281 | 282 | `border: red` => 283 | 284 | ```js 285 | [PROPERTY_REF, () => 'border'], 286 | [VALUE, 'red'] 287 | ``` 288 | 289 | #### Selector reference 290 | 291 | `[SELECTOR_REF, ref]` 292 | 293 | Variable `ref` is a string or a function which returns a string. 294 | 295 | Simple selector: `.foo` => `[SELECTOR_REF, () => '.foo']` 296 | Compound selector: `.foo.bar` => 297 | 298 | ```js 299 | [COMPOUND_SELECTOR_START] 300 | [SELECTOR, '.foo'], 301 | [SELECTOR_REF, () => '.bar'], 302 | [COMPOUND_SELECTOR_END] 303 | ``` 304 | 305 | #### Partial reference 306 | 307 | `[PARTIAL_REF, ref]` 308 | 309 | Variable `ref` is an ISTF array or a function which returns an ISTF array. 310 | 311 | ```css 312 | .foo { 313 | color: red; 314 | } 315 | .partial { 316 | color: green; 317 | } 318 | ``` 319 | ```js 320 | [ 321 | [RULE_START, 1], 322 | [SELECTOR, '.foo'], 323 | [PROPERTY, 'color'], 324 | [VALUE, 'red'], 325 | [RULE_END], 326 | [PARTIAL_REF, () => [ 327 | [RULE_START, 1], 328 | [SELECTOR, '.partial'], 329 | [PROPERTY, 'color'], 330 | [VALUE, 'green'], 331 | [RULE_END], 332 | ]] 333 | ] 334 | ``` 335 | 336 | ## Examples 337 | 338 | ### Global tag selector 339 | 340 | ```css 341 | body { 342 | color: red 343 | } 344 | ``` 345 | 346 | ```js 347 | [ 348 | [RULE_START, 1], 349 | [SELECTOR, 'body'], 350 | [PROPERTY, 'color'], 351 | [VALUE, 'red'] 352 | [RULE_END] 353 | ] 354 | ``` 355 | 356 | ### Selectors list 357 | 358 | ```css 359 | body, .foo { 360 | color: red 361 | } 362 | ``` 363 | 364 | ```js 365 | [ 366 | [RULE_START, 1], 367 | [SELECTOR, 'body'], 368 | [SELECTOR, '.foo'], 369 | [PROPERTY, 'color'], 370 | [VALUE, 'red'] 371 | [RULE_END] 372 | ] 373 | ``` 374 | 375 | ### Multiple values 376 | 377 | ```css 378 | .foo { 379 | border-color: red, green 380 | } 381 | ``` 382 | 383 | ```js 384 | [ 385 | [RULE_START, 1], 386 | [SELECTOR, '.foo'], 387 | [PROPERTY, 'border'], 388 | [VALUE, 'red'], 389 | [VALUE, 'green'], 390 | [RULE_END] 391 | ] 392 | ``` 393 | 394 | ### Fallbacks 395 | 396 | ```css 397 | .foo { 398 | color: red; 399 | color: palevioletred; 400 | } 401 | ``` 402 | 403 | ```js 404 | [ 405 | [RULE_START, 1], 406 | [SELECTOR, '.foo'], 407 | [PROPERTY, 'color'], 408 | [VALUE, 'red'], 409 | [PROPERTY, 'color'], 410 | [VALUE, 'palevioletred'], 411 | [RULE_END] 412 | ] 413 | ``` 414 | 415 | ### Compound strings 416 | 417 | `[STRING_START, ], [VALUE|VALUE_REF, ], [STRING_END]` 418 | 419 | When a string in CSS contains an interpolation, e.g. `"hello, ${name}"`, the string should be split up into its values and 420 | value references. This is because compound values are unsuitable to represent strings, since all values and value references should be joined without a separating whitespace. Also how the references should be escaped changes for strings, due to their quotes. 421 | 422 | ```js 423 | [ 424 | [STRING_START, '"'], 425 | [VALUE, 'hello, '], 426 | [VALUE_REF, name], 427 | [STRING_END] 428 | ] 429 | ``` 430 | 431 | The quotes of the string will not be part of the values, but part of the `STRING_START` marker. When the ISTF is turned back into CSS, the quote will need to be escaped inside the value references. 432 | 433 | ### Conditionals 434 | 435 | ```css 436 | @media all { 437 | .foo { 438 | color: red; 439 | } 440 | } 441 | ``` 442 | 443 | ```js 444 | [ 445 | [RULE_START, 1], 446 | [CONDITION, 'all'], 447 | [RULE_START, 1], 448 | [SELECTOR, '.foo'], 449 | [PROPERTY, 'color'], 450 | [VALUE, 'red'], 451 | [RULE_END], 452 | [RULE_END] 453 | ] 454 | ``` 455 | 456 | ### Nesting 457 | 458 | ```css 459 | .foo { 460 | color: red; 461 | &:hover { 462 | color: green; 463 | } 464 | } 465 | ``` 466 | 467 | ```js 468 | [ 469 | [RULE_START, 1], 470 | [SELECTOR, '.foo'], 471 | [PROPERTY, 'color'], 472 | [VALUE, 'red'], 473 | [RULE_START, 1], 474 | [COMPOUND_SELECTOR_START], 475 | [PARENT_SELECTOR], 476 | [SELECTOR, ':hover'], 477 | [COMPOUND_SELECTOR_END], 478 | [PROPERTY, 'color'], 479 | [VALUE, 'green'], 480 | [RULE_END], 481 | [RULE_END] 482 | ] 483 | ``` 484 | 485 | ### Nesting with a compound selector 486 | 487 | ```css 488 | .foo { 489 | color: red; 490 | &.bar.baz .bla { 491 | color: green; 492 | } 493 | } 494 | ``` 495 | 496 | ```js 497 | [ 498 | [RULE_START, 1], 499 | [SELECTOR, '.foo'], 500 | [PROPERTY, 'color'], 501 | [VALUE, 'red'], 502 | [RULE_START, 1], 503 | [COMPOUND_SELECTOR_START], 504 | [PARENT_SELECTOR], 505 | [SELECTOR, '.bar'], 506 | [SELECTOR, '.baz'], 507 | [SPACE_COMBINATOR], 508 | [SELECTOR, '.bla'], 509 | [COMPOUND_SELECTOR_END], 510 | [PROPERTY, 'color'], 511 | [VALUE, 'green'], 512 | [RULE_END], 513 | [RULE_END] 514 | ] 515 | ``` 516 | 517 | ### Scoping or named rules 518 | 519 | ```css 520 | .foo-123456 { 521 | color: red 522 | } 523 | ``` 524 | 525 | ```js 526 | [ 527 | [RULE_START, 1], 528 | [RULE_NAME, 'foo'], 529 | [PROPERTY, 'color'], 530 | [VALUE, 'red'] 531 | [RULE_END] 532 | ] 533 | ``` 534 | 535 | ### Functional pseudo class 536 | 537 | ```css 538 | .foo:matches(:hover, :focus) { 539 | color: red 540 | } 541 | ``` 542 | 543 | ```js 544 | [ 545 | [RULE_START, 1], 546 | [COMPOUND_SELECTOR_START], 547 | [SELECTOR, '.foo'], 548 | [FUNCTION_START, ':matches'], 549 | [SELECTOR, ':hover'], 550 | [SELECTOR, ':focus'], 551 | [FUNCTION_END], 552 | [COMPOUND_SELECTOR_END], 553 | [PROPERTY, 'color'], 554 | [VALUE, 'red'] 555 | [RULE_END] 556 | ] 557 | ``` 558 | 559 | ### Functional value notation 560 | 561 | ```css 562 | .foo { 563 | background: url(http://www.example.org/image); 564 | color: rgb(100, 200, 50); 565 | content: counter(list-item) ". "; 566 | width: calc(50% - 2em); 567 | } 568 | ``` 569 | 570 | ```js 571 | [ 572 | [RULE_START, 1], 573 | [SELECTOR, '.foo'], 574 | [PROPERTY, 'background'], 575 | [FUNCTION_START, 'url'], 576 | [VALUE, 'http://www.example.org/image'] 577 | [FUNCTION_END], 578 | [PROPERTY, 'color'], 579 | [FUNCTION_START, 'rgb'], 580 | [VALUE, 100], 581 | [VALUE, 200], 582 | [VALUE, 50] 583 | [FUNCTION_END], 584 | [PROPERTY, 'content'], 585 | [COMPOUND_VALUE_START], 586 | [FUNCTION_START, 'counter'], 587 | [VALUE, 'list-item'] 588 | [FUNCTION_END], 589 | [VALUE, '". "'] 590 | [COMPOUND_VALUE_END], 591 | [PROPERTY, 'width'], 592 | [FUNCTION_START, 'calc'], 593 | [VALUE, '50% - 2em'], 594 | [FUNCTION_END], 595 | [RULE_END] 596 | ] 597 | ``` 598 | 599 | #### Value, Selector and Partial using a function 600 | 601 | ```css 602 | .foo.bar { 603 | color: red; 604 | margin: 10px 20px; 605 | border: green, red; 606 | } 607 | .partial { 608 | color: green; 609 | } 610 | ``` 611 | 612 | ```js 613 | [ 614 | [RULE_START, 1], 615 | [COMPOUND_SELECTOR_START], 616 | [SELECTOR, '.foo'], 617 | [SELECTOR_REF, () => '.bar'], 618 | [COMPOUND_SELECTOR_END], 619 | [PROPERTY, 'color'], 620 | [VALUE_REF, () => [ 621 | [VALUE, 'red'] 622 | ]], 623 | [PROPERTY, 'margin'], 624 | [VALUE_REF, () => [ 625 | [COMPOUND_VALUE_START], 626 | [VALUE, '10px'], 627 | [VALUE, '20px'], 628 | [COMPOUND_VALUE_END] 629 | ]], 630 | [PROPERTY, 'border'], 631 | [VALUE_REF, () => [ 632 | [VALUE, 'green'], 633 | [VALUE, 'red'] 634 | ]], 635 | [RULE_END], 636 | [PARTIAL_REF, () => [ 637 | [RULE_START, 1], 638 | [SELECTOR, '.partial'] 639 | [PROPERTY, 'color'], 640 | [VALUE, 'green'], 641 | [RULE_END], 642 | ]] 643 | ] 644 | ``` 645 | 646 | #### Keyframes 647 | 648 | ```css 649 | @keyframes fadeIn { 650 | from { 651 | opacity: 0; 652 | } 653 | to { 654 | opacity: 1; 655 | } 656 | } 657 | ``` 658 | 659 | ```js 660 | [ 661 | [RULE_START, 7], 662 | [ANIMATION_NAME, 'fadeIn'], 663 | [RULE_START, 8], 664 | [RULE_NAME, 'from'], 665 | [PROPERTY, 'opacity'], 666 | [VALUE, 0], 667 | [RULE_END], 668 | [RULE_START, 8], 669 | [RULE_NAME, 'to'], 670 | [PROPERTY, 'opacity'], 671 | [VALUE, 1], 672 | [RULE_END], 673 | [RULE_END] 674 | ] 675 | ``` 676 | 677 | #### Media query 678 | 679 | ```css 680 | @media all { 681 | .foo { 682 | color: red; 683 | } 684 | } 685 | ``` 686 | 687 | ```js 688 | [ 689 | [RULE_START, 4], 690 | [CONDITION, 'all'], 691 | [RULE_START, 1], 692 | [SELECTOR, '.foo'], 693 | [PROPERTY, 'color'], 694 | [VALUE, 'red'], 695 | [RULE_END], 696 | [RULE_END] 697 | ] 698 | ``` 699 | --------------------------------------------------------------------------------