feat: Add keep_at_rules option#485
Conversation
155e7b6 to
64161f7
Compare
CodSpeed Performance ReportMerging #485 will not alter performanceComparing Summary
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #485 +/- ##
==========================================
+ Coverage 88.43% 89.11% +0.68%
==========================================
Files 18 18
Lines 1919 2040 +121
==========================================
+ Hits 1697 1818 +121
Misses 222 222 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Stranger6667
left a comment
There was a problem hiding this comment.
Great start! Definitely on the right track :) I left a few comments
css-inline/src/html/serializer.rs
Outdated
|
|
||
| serializer.start_elem(&element.name, &element.attributes, style_node_id)?; | ||
|
|
||
| // TODO this part is the one that I don't like the most |
There was a problem hiding this comment.
I am not 100% sure about this one, even though it is a sound approach.
The first thought that I have is that the least surprising thing would be to keep @ rules in their original places, instead of pushing everything into a single node. This won't work for external stylesheets, so we need to store them somewhere anyway.
On the other hand, this approach is better for performance, and probably it would not be super surprising for the user anyway if we put everything under one <style> tag.
So, in the end, I am leaning towards this approach.
A few other notes:
- We can reduce the performance hit of checking each node name by having two versions of
serialize- the first one will work as before this PR. The new one will check forheadand once it is found, it will switch to the other version ofserializethat won't check the tag name. At the top level we check ifat_rulesare present and then call one of these two versions - I think it should minimize the impact asheadis usually in the very beginning of the nodes vector. - Are there cases when
headis not present? Maybe in HTML fragments? seeinline_fragment
There was a problem hiding this comment.
btw, if the user chose to keep style tags - I think we can only extract @ rule from link, as the old style tags will be around anyway
There was a problem hiding this comment.
That's something I had no time to look closer into yet
There was a problem hiding this comment.
Given the current benchmark results, the performance hit is negligible, so we can skip this. For fragments, likely we can't really do it, as there is no regular HTML structure generally. Maybe we can return an error when the user sets an option to keep at rules, but uses inline_fragment?
|
Hi @kamilzych Let me know if you need any assistance or if you plan to continue working on this PR. I'd be happy to help or take over in case if you don't plan to continue this PR. |
ffe46ee to
c4adfc7
Compare
|
@Stranger6667 Hi! Apologies for lack of activity, I simply had no time, life happened I've just pushed a commit which addresses some of your comments. I'd love to finish the whole PR but I'm not sure when I have enough time to move this forward. |
|
Awesome! Thank you so much! :) I’ll be on vacation till the end of next week, but I’ll review the changes in any event :) Let’s see how it goes |
|
Amazing to see that we already have a working version! There are a few minor things left; there is no time pressure, so I'm just noting what is left to do, and maybe I'll push some changes to it in the next few evenings:
|
c4adfc7 to
fdfd7c8
Compare
|
As for the next steps, I think most of them are about adding new config option to bindings, so they compile. This flag is the same as others in terms of behavior, so it should be more or less straightforward. about the error, we can add an extra check to inline_fragment |
css-inline/src/main.rs
Outdated
|
|
||
| --keep-link-tags | ||
| Keep "link" tags after inlining. | ||
|
|
There was a problem hiding this comment.
There is a small place above where this flag should be added too. The handle_boolean_flag function
|
@Stranger6667 Hi again! I should have some time tomorrow to work on this again. Updating CHANGELOG, README and other docs should be easy. I can also take a look into
and perhaps add a test(s) for it and other cases. The only question I have right now is about bindings. Do you have any docs, guidance, or previous PR that could show me the ropes? |
|
Hi! I’ll have access to my laptop in a few hours and will provide you with more details :) |
|
For bindings, all the TODO entries are similar. The core of them is:
Depending on the language, it requires a bit different annotations, but you can just copy them from For Ruby, extend the For Python, you need to add For C, you need to extend For Java, extract a new boolean from the environment: let keep_at_rules = env.get_bool_field(&cfg, "keepAtRules")?;And add it to the options builder. For JavaScript, extend Let me know if I missed anything, will be happy to elaborate :) |
5ea0ba8 to
6156284
Compare
6156284 to
67574d3
Compare
|
One minor detail about Ruby: bindings use the latest release from crates.io to avoid packaging issues. It is suboptimal, but I am fine with having this PR point to the local crate, so we see everything passes. Then I'll update it before the release, and maybe add a better solution to avoid breaking releases for Ruby bindings. |
css-inline/tests/test_inlining.rs
Outdated
| </body> | ||
| </html>"#; | ||
| let inlined = inliner.inline(html).unwrap(); | ||
| let expected = "<html><head><style>@media (max-width: 600px) { h1 { font-size: 18px; } }</style>\n\n</head>\n<body>\n<h1 style=\"color: blue;\">Hello</h1><p style=\"margin: 10px;\">World</p>\n\n</body></html>"; |
There was a problem hiding this comment.
I see there is a double space - (max-width: 600px) {. I guess we can keep what is in the original string without adding extra space - it should be a bit cleaner + a bit faster
There was a problem hiding this comment.
Unless it breaks some corner cases
There was a problem hiding this comment.
Had to add it back at the end though be2c653 😅 Without it there's no space between multiple @media declarations, like this
@media (max-width: 600px) { ... }@media (max-width: 400px) { ... }
|
@Stranger6667 Regarding error when inlining fragments. I've just checked and it seems that here's the test I used for testing #[test]
fn inline_fragment_keep_style_tags() {
let inliner = CSSInliner::options().keep_style_tags(true).build();
const HTML: &str = r#"<main>
<h1>Hello</h1>
<section>
<p>who am i</p>
</section>
</main>"#;
const CSS: &str = r#"
p {
color: red;
}
h1 {
color: blue;
}
"#;
let inlined = inliner.inline_fragment(HTML, CSS).unwrap();
assert_eq!(inlined, "<main>\n<h1 style=\"color: blue;\">Hello</h1>\n<section>\n<p style=\"color: red;\">who am i</p>\n</section>\n</main>");
} |
|
@kamilzych I completely agree with you! lets handle that part separately |
733dc23 to
104de47
Compare
104de47 to
0a77f50
Compare
|
Awesome! The last failure with Ruby bindings could be resolved by running |
Unfortunately, this is where I failed 🥲 After changing running If I keep version |
|
No worries! I will take a look :) I think at this point everything should be resolved and the feature is ready :) thank you so much for working on it! I plan to merge it if there are no unresolved issues and will sort out the ruby part later |
|
Thank you too for all the patience, guidance and help 🙏 I had a lot of fun when working on it! In the meantime, I created #514 I will also take a look at unit tests, might add more. |
|
@Stranger6667 I can already see some issues. Some are simply incorrect behaviour which I will try to fix, but for one I have a question. Let's consider such test: let html = html!("@media (max-width: 767px) { padding: 0;} h1 {background-color: blue;}", "<h1>Hello world!</h1>");
let options = InlineOptions {
inline_style_tags: false,
keep_style_tags: false,
keep_at_rules: true,
..Default::default()
};
let inliner = CSSInliner::new(options);
let result = inliner.inline(&html).unwrap();
assert_eq!(
result,
"???"
)What should we expect for such options setup? Right now, the result is |
I am glad to hear it! :) Re: config options combo. From a use case point of view, I think the user might want to remove everything except at-rules, so I'd expect a As P.S., adding an unused private field could be a good idea to avoid a public API to construct this without |
|
Yeah, with
Not sure if I follow, where this private field should be added? 🤔 |
|
Thanks! :) I think adding The idea is that having this private field allows for fewer breaking changes. Right now, dependencies could build this struct directly with or without using |
be2c653 to
cc509a2
Compare
|
Ah, I see. Makes sense, although I'm not sure if it should be a part of this PR 🤔 FYI, I've just committed a failing test, fixing it seems to be a bit harder than I thought and I have to run. I hope to find some time next week to work on it again. Thanks again! 🙏 |
|
Sure thing! The private field thing is definitely for later! Thank you again for working on this :) |
Signed-off-by: Dmitry Dygalo <dmitry@dygalo.dev>
Signed-off-by: Dmitry Dygalo <dmitry@dygalo.dev>
Signed-off-by: Dmitry Dygalo <dmitry@dygalo.dev>
|
I think we are ready to go! :) I am so happy to see this feature implemented! :) Great work! I will deal with Ruby separately |
|
Awesome, glad to hear it! Perhaps I'll add more unit tests for corner cases (e.g. |
|
Great! Also, feel free to add that HTML as a benchmark here (if it could be shared publicly). The structure there could be extended so we can benchmark the new config option too (e.g. add optional |
Rel #265 (comment)