-
Notifications
You must be signed in to change notification settings - Fork 5
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
First draft #17
Conversation
53e5f69
to
d44662a
Compare
readme.md
Outdated
```js | ||
[ | ||
[RULE_START], | ||
[RULE_TYPE, 1], |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
readme.md
Outdated
[PROPERTY, 'color'], | ||
[VALUE, 'red'], | ||
[RULE_START], | ||
[SELECTOR, REF, ':hover'], |
There was a problem hiding this comment.
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"]]
.
There was a problem hiding this comment.
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']
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
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!
readme.md
Outdated
[ | ||
[RULE_START], | ||
[RULE_TYPE, 1], | ||
[SELECTOR, SCOPED, '.foo'], |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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']
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah good point!
There was a problem hiding this comment.
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']
readme.md
Outdated
[SELECTOR, '.foo'], | ||
[PROPERTY, 'border'], | ||
[VALUE, '1px solid red'], | ||
[VALUE, '1px solid green'], |
There was a problem hiding this comment.
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+.
There was a problem hiding this comment.
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.
readme.md
Outdated
[ | ||
[RULE_START], | ||
[RULE_TYPE, 1], | ||
[SELECTOR, SCOPED, '.foo'], |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
Updated the spec. |
- No separate RULE_TYPE - NAME - REF=>PARENT_SELECTOR - SPACE
readme.md
Outdated
const PROPERTY = 4 | ||
const VALUE = 5 | ||
const PARENT_SELECTOR = 6 | ||
const SPACE = 7 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're gonna want more than SPACE
if you're going this way - you want all five of the combinators, at least. Are you expecting this to be extensible by libraries, or always limited to the set of official CSS ones?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, maybe we need a COMBINATOR
which can have these values: ' ', '>', '>>', '+', '~']
Better ideas?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or otherwise separate constant for each combinator.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As the combinators amount is limited to 5, I would probably go for separate constants, because this will make the result more compact.
readme.md
Outdated
|
||
Selector compound e.g. `.foo.bar` => `[SELECTOR, '.foo', '.bar']` | ||
|
||
Multiple classes e.g. `.foo .bar` => `[SELECTOR, '.foo'], [SELECTOR, '.bar']` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't right; the description down below is that you generate multiple [SELECTOR ...]
tokens to represent comma-separated selectors, like .foo, .bar
, which evaluate independently.
(The terms you want, btw, are "compound selector" for things like .foo.bar
, "complex selector" when combinators are involved like .foo .bar
, and "X selector list" when commas are involved, like .foo, .bar
.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, so this example should be
Selector list e.g. .foo, .bar
=> [SELECTOR, '.foo'], [SELECTOR, '.bar']
readme.md
Outdated
|
||
Denotes a selector or selector compound. | ||
|
||
Selector compound e.g. `.foo.bar` => `[SELECTOR, '.foo', '.bar']` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do plan to handle pseudo-classes with arguments, particularly ones that take selectors as arguments like :matches(.foo, .bar)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Idea: FUNCTION constant followed by a function name and arguments
[FUNCTION, ':matches', '.foo', '.bar']
When combined with another selector:
.bla:matches(:hover, :active)
=> [SELECTOR, '.bla', [FUNCTION, ':matches', ':hover', ':active']]
Updated the spec |
@tabatkins btw. this format could be also fixing the interoperability between less, sass and cssinjs! |
@tabatkins @geelen do you have more thoughts about this PR? |
This is starting to shape up! I noticed one thing missing—dynamic values. For styled components, we need the ability for parts of the CSS to be injected based on props or theme variables, and if the intermediate format doesn't support that then we'd not be able to publish cross-compatible components—we'd have to publish full Styled Components. So, I think it's worth considering areas where dynamic injections might occur, and a way to invert control so that injections can be evaluated as a snippet is executed. It could be as simple as something like: .foo-123456 {
color: ${ props => props.red ? 'red' : 'blue' };
margin: 0.25em;
${ props => props.big && css`
font-size: 1.25em;
margin: 0.5em;
}
} [
[RULE_START, 1],
[RULE_NAME, 'foo'],
[PROPERTY, 'color'],
[VALUE, [ DYNAMIC, 0 ]],
[DYNAMIC, 1],
[RULE_END]
] Then the specification also defines evaluation contexts something like: function toCSS( rulesToSelectorsFn, ...dynamicFns ) {} By default |
Might be good to get @philpl's thoughts here now too |
Definitely, I wanted just to cover basics first. |
Then my only feedback is whether any rules should have any nested values (i.e. [
[RULE_START, 1],
[COMPOUND_SELECTOR_START],
[SELECTOR, '.foo'],
[FUNCTION_SELECTOR, ':matches'],
[SELECTOR, ':hover'],
[SELECTOR, ':focus'],
[FUNCTION_SELECTOR_END],
[COMPOUND_SELECTOR_END],
[PROPERTY, 'color'],
[VALUE, 'red']
[RULE_END]
] That way any |
readme.md
Outdated
[PROPERTY, 'color'], | ||
[VALUE, 'red'], | ||
[RULE_START, 1], | ||
[PARENT_SELECTOR, '.bar', '.baz', SPACE_COMBINATOR, '.bla'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could also be replaced with the idea of a COMPOUND_SELECTOR
By css flattener, I mean sth that potentially takes this css last and replaces nested rules with flattened ones. If nested rules will actually be parsed by a reference implementation? If not, it'll probably come down to a separate implementation that outputs this format/ast. As to the second point: this is exactly what I'll start working on. One library which can preprocess and extract css bits in libraries like SC, glamorous and Glam. |
We got nesting into this spec already, meaning RULE_START can happen inside of another rule and to reference the parent selector we got PARENT_SELECTOR. Or maybe I don't quite understand what you mean and need an example.
Sounds great! |
@kof that sounds like what I mean :) maybe I'll start with writing a minimal parser and we can think about a reference implementation later ;) |
added COMPOUND_SELECTOR_START added COMPOUND_SELECTOR_END added COMPOUND_VALUE_START added COMPOUND_VALUE_END added FUNCTION_START added FUNCTION_END
Spec updated: removed nesting, array tuples length are now max 2. So now we could actually apply further optimizations, please vote with thumbs up and down to the next comments. |
Should we go for single array? As consequence we will only be able to express this:
Right now it seems to work nicely. [
[RULE_START, 1],
[SELECTOR, 'body'],
[PROPERTY, 'color'],
[VALUE, 'red'],
[RULE_END]
] will become [
RULE_START, 1,
SELECTOR, 'body',
PROPERTY, 'color',
VALUE, 'red',
RULE_END
] |
Should we go for all string values? Benchmark shows a considerable perf benefit in v8, if it is not flowed: https://esbench.com/bench/592d599e99634800a03483d8 [
1, 1,
2, 'body',
3, 'color',
4, 'red',
5
] will become [
'1', '1',
'2', 'body',
'3', 'color',
'4', 'red',
'5'
]
|
Btw. we potentially have not only fn refs, but any other refs, objects, imported primitives, whatever. If static analyzer is not able to completely follow the import which might be also a result of a complex function invocation, we will need to inline the ref in place or push all refs into a separate array. Right now the only reason to maintain separate array is the perf benefit in v8 and 20% storage savings. |
@kof the perf will likely not be worse in any other engine due to a homogeneous array. So I'd strongly advise it. For the refs we can just create a bucket. Probably a WeakMap, where it's available? Since we know the type from the AST, we don't need to separate the map for separate types |
not worse, just no benefit
I don't see how a WeakMap can be useful here. |
@kof Yea, actually a normal Map would suffice, and a WeakMap might actually not be working correctly for our needs ^^" It was more of an example. The point is, we can create a bucket and hold all of our referenced values in there |
Don't know whats a bucket in js. I guess a refs array would do + position as a pointer in the main array. But still not sure if its worth it vs. just referencing in place. |
Yeah I wouldn't chase down perf ideas until we're sure this format can express everything it needs to. I think the idea that everything can be 2-tuples is useful because it's a design constraint that potentially speeds things up, but I'd just use nested, non-homogenous arrays to represent it. That way, if we find something that can't be done easily, we can go to 3-tuples or real references etc etc. The performance criteria we should be judging this on is what's fastest to download + parse + convert into real CSS. 2-tuples might save us some branching in a tight loop, but it might require more structure as we're processing it. Potentially, there may be no need to deconstruct things into to |
I think we need to deconstruct things heavily, otherwise you want be able to for e.g. vendor-prefix props/values upon consumption efficiently. I was even thinking about deconstructing numeric values and its units. |
Btw. even in a flatten array we still can have longer than 2 value pairs, we basically can't have dynamic amount of members, but rather a statically predefined for a certain marker. |
Ok, lets flatten the format and try other optimizations like homogeneous array after we have one reference implementation and see what else is missing, I will create for them separate issues. |
I think we can merge this PR and create new one for the next things. Thumbs up? |
merged 🎉 |
This is mainly to discuss the details of it and raise all concerns. cc @geelen