Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First draft #17

Merged
merged 10 commits into from
Jun 21, 2017
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 202 additions & 4 deletions readme.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,205 @@
## An attempt to find an interoperable CSSinJS format.
## Interoperable CSSinJS format.

The biggest issue at scale we currently have with all the CSSinJS implementations is that they are not sharable without the runtime.
The biggest issue at scale we currently have with all CSSinJS implementations is that they are not sharable without the runtime.

If we could find a format that is fast an easy to parse and supports all needs of current CSSinJS implementations, it could become a de facto standard for exported CSSinJS.
We can define a format that is easy to parse and supports all the needs of the current CSSinJS implementations on top of CSS.

It could be similar to how CommonJS and ES3 are currently the standard for sharing JavaScript modules on npm.
Package maintainers will be able to publish JavaScript files to NPM with this format inside instead of plain CSS.

## First draft

Format is optimized for parsing in JavaScript, not for human readability.

### Constants

#### Markers

Examples are using constant names instead of values for readability. The end format will use the values.

```js
const RULE_START = 0
const RULE_TYPE = 1
const RULE_END = 2
const SELECTOR = 3
const SCOPED = 4
const PROPERTY = 5
const VALUE = 6
const REF = 7
const CONDITION = 8
```

#### Rule types

Copied from https://wiki.csswg.org/spec/cssom-constants

```
1 STYLE_RULE CSSOM
2 CHARSET_RULE CSSOM
3 IMPORT_RULE CSSOM
4 MEDIA_RULE CSSOM
5 FONT_FACE_RULE CSSOM
6 PAGE_RULE CSSOM
7 KEYFRAMES_RULE css3-animations
8 KEYFRAME_RULE css3-animations
9 MARGIN_RULE CSSOM
10 NAMESPACE_RULE CSSOM
11 COUNTER_STYLE_RULE css3-lists
12 SUPPORTS_RULE css3-conditional
13 DOCUMENT_RULE css3-conditional
14 FONT_FEATURE_VALUES_RULE css3-fonts
15 VIEWPORT_RULE css-device-adapt
16 REGION_STYLE_RULE proposed for css3-regions
17 CUSTOM_MEDIA_RULE mediaqueries
```

### Global tag selector

```css
body {
color: red
}
```

```js
[
[RULE_START],
[RULE_TYPE, 1],

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merge this value into the previous token ([RULE_START 1]); no need to wait until the second token of the rule to find out what type of rule it is. Then you can drop RULE_TYPE entirely.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, thought about that too.

[SELECTOR, 'body'],
[PROPERTY, 'color'],
[VALUE, 'red']
[RULE_END]
]
```

### Multiple classes in one selector

```css
body, .foo {
color: red
}
```

```js
[
[RULE_START],
[RULE_TYPE, 1],
[SELECTOR, 'body'],
[SELECTOR, '.foo'],
[PROPERTY, 'color'],
[VALUE, 'red']
[RULE_END]
]
```

### Multiple values

```css
.foo {
border: 1px solid red, 1px solid green
}
```

```js
[
[RULE_START],
[RULE_TYPE, 1],
[SELECTOR, '.foo'],
[PROPERTY, 'border'],
[VALUE, '1px solid red'],
[VALUE, '1px solid green'],

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the recognition of list-valued properties here, and the representation as distinct values. A+.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I personally dislike is the use of shortcuts. I think we need to come up with a format completely without shortcuts.

[RULE_END]
]
```

### Fallbacks

```css
.foo {
color: red;
color: linear-gradient(to right, red 0%, green 100%);
}
```

```js
[
[RULE_START],
[RULE_TYPE, 1],
[SELECTOR, '.foo'],
[PROPERTY, 'color'],
[VALUE, 'red'],
[PROPERTY, 'color'],
[VALUE, 'linear-gradient(to right, red 0%, green 100%)'],
[RULE_END]
]
```

### Media rules

```css
@media all {
.foo {
color: red;
}
}
```

```js
[
[RULE_START],
[RULE_TYPE, 4],
[CONDITION, 'all'],
[RULE_START],
[RULE_TYPE, 1],
[SELECTOR, '.foo'],
[PROPERTY, 'color'],
[VALUE, 'red'],
[RULE_END],
[RULE_END]
]
```

### Nesting

```css
.foo {
color: red;
&:hover {
color: green;
}
}
```

```js
[
[RULE_START],
[RULE_TYPE, 1],
[SELECTOR, '.foo'],
[PROPERTY, 'color'],
[VALUE, 'red'],
[RULE_START],
[SELECTOR, REF, ':hover'],

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This structure doesn't seem to let you distinguish between & :hover and &:hover. I suggest the SELECTOR rule should actually break down the selector structurally.

Maybe after the SELECTOR, an alternating sequence of compound selectors and combinators, where each compound selector is an array of simple selectors, which are strings?

That is, &:hover would produce [SELECTOR ["&", ":hover"]], while & :hover would produce [SELECTOR ["&"] " " [":hover"]].

Copy link
Member Author

@kof kof May 30, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

true that, maybe a spacer marker?

'& :hover' -> [SELECTOR, REF, SPACE, ':hover']
'&:hover' -> [SELECTOR, REF, ':hover']

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would the selector &.foo.bar .baz be encoded?

(I'm trying to push you to a consistent, extensible selector representation that minimizes the amount of hand-parsing end-users have to do, which maximizes the likelihood that they'll parse things correctly.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

&.foo.bar .baz -> [SELECTOR, REF, '.foo', '.bar', SPACE, '.baz']

bad idea?

I'm trying to push you to a consistent, extensible selector representation

Totally appreciate that!

[PROPERTY, 'color'],
[VALUE, 'red'],
[RULE_END],
[RULE_END]
]
```

### Scoped class name

```css
.foo-123456 {
color: red
}
```

```js
[
[RULE_START],
[RULE_TYPE, 1],
[SELECTOR, SCOPED, '.foo'],

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, is there some restriction of one-class-per-selector in effect here? Otherwise, you can't tell what's supposed to be scoped; or are all the classes meant to be? Less sure what the right answer would look like here, tho.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking that we can only have one class here, there is an example with multiple classes in one selector.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so if we need multiple classes in one selector it would looke like this:

[SELECTOR, SCOPED, '.foo']
[SELECTOR, SCOPED, '.bar']

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That indicates a selector like .foo-XXX, .bar-XXX, right? I was talking about a selector like .foo-XXX.bar-XXX, where they're both in the same compound selector.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah good point!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about this?

All scoped: .foo.bar -> [SELECTOR, SCOPED, '.foo', '.bar']
Some scoped: .foo.bar -> [SELECTOR, [SCOPED, '.foo'], '.bar']

Copy link
Member Author

@kof kof May 30, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another thought in regard of SCOPED. Actually if we want to let the consumers generate selectors, this thing is just a NAME.

We need a concept of a named rule: foo {color: red}, where foo is any user defined name.

Not scoped selector: .foo -> [SELECTOR, '.foo']
Named: foo -> [NAME, 'foo']

A rule may have just one name, but may have multiple selectors and a name.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that makes sense. So a "name" is gonna be auto-generated with some prefix, to help ensure there are no accidental name clashes, right? That's why "scoped" rules only need a single class - that's basically by definition. In that case, yeah, a separate command that is morally equivalent to a selector, but should only appear at most once per rule, sounds best.

Copy link
Member Author

@kof kof May 30, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"name" is gonna be auto-generated with some prefix

This is one possibility, though I was thinking to let consumers generate them. Generating prefixes at build time on the publisher side would mean we need to ensure the uniqueness for the entire world. Leaving it up to consumer in a specific project will reduce the probability for clashes to the project context. Also there are multiple ways to generate an id, counter based is the most efficient, hashes - not so much.

[PROPERTY, 'color'],
[VALUE, 'red']
[RULE_END]
]
```