-
-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Internal refactor, introduce AtRule
#14802
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
Conversation
24fe915 to
23bbcf4
Compare
|
Nice! Curious to benchmark this change, expecting it to be basically identical but secretly hoping it's a tiny bit faster. |
|
@adamwathan haha that was my next thing on the list to try. I'm hoping for the same results you mentioned. |
|
Running some benchmarks on the Tailwind UI codebase. Before ( After ( It looks like this PR is a tiny bit slower right now. Thinking about it, it makes sense from a CSS parsing perspective because we are doing a bit more when we see at-rules. We also re-parse every time we see an at-rule in the variants used, I think this is where we can do some speedups. Let me try some things, because it's easier to work with style rules and at rules separately, but not at the cost of perfomance. |
4a8f3bc to
76b5e1f
Compare
|
Few improvements later, these are the results: Before ( After ( |
324afae to
49da19c
Compare
adamwathan
left a comment
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.
Nice! I think this is a great improvement.
The only high level thing I'm half wondering about is whether we should include the @ sign as part of AtRule.name for greppability even if it's not necessary? Don't have a strong opinion on it but does feel like something we're losing with this change. Could easily be convinced not to worry about it.
|
|
||
| const AT_SIGN = 0x40 | ||
|
|
||
| export type StyleRule = { |
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.
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 the same, but @thecrypticace pointed me to the spec where they use Style rules and At rules. I think just Rule is very common because of PostCSS.
I'm fine with either. Biggest benefit for separating is that we have a rule() abstraction that returns a style rule via styleRule() or an at rule via atRule() right now depending on the @ being present.
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.
Yeah the spec explicitly calls them style rules.
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.
Yeah I'm fine with style rule then if that's considered more technically correct 👍
|
Hmm, I do like the grepability of looking for Fun fact, during printing, I forgot to add an Any strong feelings about this @thecrypticace? |
Nah, I say go for it. |
cdd1447 to
99c1d27
Compare
This follows the CSS spec naming, which is useful if we have `StyleRule` and `AtRule`.
This keeps the codebase in a similar state as before, but this just creates one of the follow nodes: `AtRule | StyleRule`
Keeps the diff smaller. Might drop this commit before merging.
This keeps the diff smaller
We can use `rule(…)` here as well, and it will make the diff smaller. However, that requires us to jump through an additional function where we verify that we don't start with `@`. We already know this, so let's skip the indirection.
This is a tiny optimization, but the moment we call `parseAtRule` we know 2 things: 1. It starts with an `@` because it's an at-rule, therefore we can skip this character. 2. You need at least 4 characters (excluding `@`) because `@page` is the shortest valid at-rule. This means that we can start looking past `@page`, which means that we can start at character 6. List of valid CSS at-rules (currently) ordered by length: - `@page` - `@layer` - `@media` - `@scope` - `@import` - `@charset` - `@property` - `@supports` - `@container` - `@font-face` - `@keyframes` - `@namespace` - `@custom-media` - `@position-try` - `@color-profile` - `@counter-style` - `@starting-style` - `@view-transition` - `@font-feature-values` - `@font-palette-values`
No need to create a new rule and mutate the original. We can just push the new rule directly and share the original keyframesRule object.
In theory, people could write their own at-rules (e.g.: if you sandwich `@tailwindcss/postcss` between 2 PostCSS plugins that handle custom at-rules). In that scenario, it is possible to have `@x` at-rules.
Changed my mind again. There are a lot of conditions necessary to use shorter at-rules. This means that we can improve the parsing by using the more common case of skipping 5 characters by default. We can adjust this number if it's actually causing issues.
This allows us to just search for `@media` in the codebase and find wherever we used the at-rules. This is purely a development QoL improvement.
99c1d27 to
cb5222b
Compare
adamwathan
left a comment
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.
Glad we decided to include the @ seeing the diff now, I think the code reads a lot better with it being part of the string 👍

This PR introduces an internal refactor where we introduce the
AtRuleCSS Node in our AST.The motivation for this is that in a lot of places we need to differentiate between a
Ruleand anAtRule. We often do this with code that looks like this:Another issue we have is that we often need to check for
'@media 'including the space, because we don't want to match@mediafoobarif somebody has this in their CSS. Alternatively, if you CSS is minified then it could be that you have a rule that looks like@media(width>=100px), in this case we also have to check for@media(.Here is a snippet of code that we have in our codebase:
Which will now be replaced with a much simpler version:
Checking for all the cases from the first snippet is not the end of the world, but it is error prone. It's easy to miss a case.
A direct comparison is also faster than comparing via the
startsWith(…)function.Note: this is only a refactor without changing other code unless it was required to make the tests pass. The tests themselves are all passing and none of them changed (because the behavior should be the same).
The one exception is the tests where we check the parsed AST, which now includes
at-rulenodes instead ofrulenodes when we have an at-rule.