stranger6667 / css-inline Goto Github PK
View Code? Open in Web Editor NEWHigh-performance library for inlining CSS into HTML 'style' attributes
Home Page: https://css-inline.org/
License: MIT License
High-performance library for inlining CSS into HTML 'style' attributes
Home Page: https://css-inline.org/
License: MIT License
Currently it always creates a string, but it might write to a file instead. One of the ways - accept output: W
where W: Write
. In this case it will be more efficient for writing to a file - there will be no need to allocate a vec and a string.
Also, there could be a built-in shortcut that will work as the current version, but will be using the new, generic one internally
Something like https://github.com/roverdotcom/django-inlinecss (template tag) and https://github.com/Dino16m/mailcomposer (CLI command) but simpler
While working on integrating your tool into our project, we, with a colleague, noticed that the "css-inline" behaves unexpectedly in some situations in part of rules priority ("Specificity" in terms of css).
I'm very grateful to @midezz for the provided examples and help with investigating the issue.
There are a couple of demos.
Source HTML
<html>
<head>
<style>
a {
color: #17bebb;
}
.test-class {
color: #ffffff;
}
</style>
</head>
<body>
<a class="test-class" href="https://example.com">Test</a>
</body>
</html>
Inlined HTML
<html><head>
<style>
a {
color: #17bebb;
}
.test-class {
color: #ffffff;
}
</style>
</head>
<body>
<a class="test-class" href="https://example.com" style="color: #ffffff;">Test</a>
</body></html>
<html>
<head>
<style>
.test-class {
color: #ffffff;
}
a {
color: #17bebb;
}
</style>
</head>
<body>
<a class="test-class" href="https://example.com">Test</a>
</body>
</html>
<html><head>
<style>
.test-class {
color: #ffffff;
}
a {
color: #17bebb;
}
</style>
</head>
<body>
<a class="test-class" href="https://example.com" style="color: #17bebb;">Test</a>
</body></html>
As we see, the order of css-rules declaration affects the rules application priority. That seems wrong, as we expect "class rules" to be of higher priority than "tag rules".
Ref: https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity
If I'm not mistaken, it seems to be a problem with the merge_styles
function, if I'm not mistaken (https://github.com/Stranger6667/css-inline/blob/master/css-inline/src/lib.rs#L397).
I didn't have a chance to check the behaviour with an "!important" rule, but it should be quite an important case here too :)
Add description + usage examples
Currently we can't due to dependency on openssl, but switching to rustls will solve this issue
Instead of this:
let options = css_inline::InlineOptions {
load_remote_stylesheets: false,
..Default::default()
};
let inliner = css_inline::CSSInliner::new(options);
We can do this:
let inliner = css_inline::CSSInliner::options()
.load_remote_stylesheets(false)
.build();
Bench candidates:
inline
merge_styles
Test data:
Either do html.to_string()
or return Cow
to avoid allocations
As far as I see String::from_utf8_unchecked
can be used instead of String:from_utf8_lossy
because the output is always valid utf8.
&str
which is unicode;String
which is also a valid utf8Unless there are some raw bytes manipulations inside serialize
that lead to a non-utf8 output, then it should be safe. As far as I see the serializer - all bytes are from ascii symbols or &str::as_bytes()
which is utf-8. Need to check better
The current size: 1492887 bytes
How much we can cut off:
wasm-opt = ['-Os']
- 6629 byteswasm-opt = ['-Oz']
- 6763 bytesopt-level = "s"
- 338471 bytesopt-level = "z"
- 442795 bytesSo, the best I could get with the options above combined is a reduction by 447844 bytes in cost of some performance
Options I didn't try on my machine yet:
There are multiple expect
calls, that can be rewritten with returning Result
https://github.com/cloudflare/lol-html
I have a PoC that is kinda working for a simple case but need some benchmarking, CSS parsing code is the same
In this case, it is possible to avoid allocating String
and more compact Tendril
instances will be used instead
https://rustwasm.github.io/wasm-bindgen/wasm-bindgen-test/usage.html
+ Integration with GitHub actions
Conditional comments like <!--[if mso]>Hello outlook<![endif]-->
and <!--[if !mso]><!--><h1>Hello not outlook</h1><!--<![endif]-->
are used to either hide things from outlook or only show things to outlook, it looks like those are currently being stripped out
I have a failing test written on this branch)
Not sure where the problem is; I see code in kuchiki that references comment nodes; this repo seems to be using the newest version, is it possibly a bug on kuchiki?
PyO3
+ python tests + python benchmarks against premailer
& pynliner
Now we have some extra spaces that are not needed. ;
in the end, is also optional
Inlining itself could be also optimized if we could just join all loaded CSS into a single string. Worth researching, however, I am not sure how common it is to have emails with multiple style / link tags
When an element has both a class
attribute and a style
attribute, styles from the stylesheet are merged into the destination style
attribute. However, existing values in the element's style
are overwritten by the values from the stylesheet whereas existing style
attribute values should override any values in the stylesheet. This issue results in incorrect styles being applied on elements.
import { inline } from "css-inline";
describe("css-inline", () => {
it.only("correctly allows style tag to override stylesheet", () => {
const document =
`<html>
<head><style>p.MsoNormal {margin-left:0in;font-size:14px;font-family:Arial;}</style></head>
<body><p class=MsoNormal style='margin-left:.25in;font-family:Calibri;'>Some text goes here</p></body>
</html>`;
const expected =
`<html><head></head>
<body><p class="MsoNormal" style="margin-left:.25in;font-family:Calibri;font-size:14px;">Some text goes here</p></body>
</html>`;
expect(inline(document, { inline_style_tags: true, remove_style_tags: true })).toEqual(expected);
});
});
margin-left
should stay as .25in
and font-family
should stay as Calibri
):<html><head></head>
<body><p class="MsoNormal" style="margin-left:.25in;font-family:Calibri;font-size:14px;">Some text goes here</p></body>
</html>
style
attribute):<html><head></head>
<body><p class="MsoNormal" style="margin-left:0in;font-family:Arial;font-size:14px;">Some text goes here</p>
</body>
</html>
E.g. all
/ screen
(from premailer
)
I need an npm account
https://developer.mozilla.org/en-US/docs/WebAssembly/Rust_to_wasm
I.e. return an iterator instead of Vec
, since we still iterate over it on the next step. Currently, we allocate space for a vector
It could be on a specific tag, basically cargo publish
As the second argument to inline + implement Default
for it
Since everything is inlined, classes are useless and likely won't bring any benefits but occupy some space in the output. Hence I believe we can safely remove them.
keep_classes
configuration option (everywhere, including CLI and all bindings). The default should be false
.Sometimes they are static strings
๐ Hi there :)
Have you considered allowing css to inline be passed as an additional parameter?
(This might be related to #10 , but not quite the same)
The use case I'm thinking of is to possibly replace juice in mjml (I'm not affiliated, just a lib user), where css specified in <mj-style inline="inline">
is collected as a separate string that's currently passed into juice as extraCss
, and inlining of other css is disabled (which makes sense because sometimes you do want to tell clients that respect style sheets to do something different from outlook)
As juice
does
It is only relevant for CLI, with the library itself, the end-user can load css from a file manually.
inline
should return its own error type
Dearest Maintainer,
Neat project. I hope to use it soon. I was playing with the command line tool and was confused by the error messages.
time ./target/release/css-inline --remove-style-tags --load-remote-stylesheets ~/trash/1MB.html
/home/becker/trash/1MB.html: FAILURE (No such file or directory (os error 2))
My file is there. what is not there is inlined./home/becker/trash/1MB.html. If you move to postfix it will work for absolute and relative paths.
Dictated but not reviewed.
Becker
Hello!
There is a problem when we use css_inline with the option remove_style_tags=True
. This problem appears when there are a few <style>
tags in HTML.
HTML
<html>
<head>
<style>
body {
margin: 0 auto !important;
padding: 0 !important;
height: 100% !important;
width: 100% !important;
background: #f1f1f1;
}
a {
text-decoration: none;
}
</style>
<style>
.test-class {
color: #ffffff;
}
a {
color: #17bebb;
}
</style>
</head>
<body>
<a class="test-class" href="https://example.com">Test</a>
</body>
</html>
Use css_inline with remove_style_tags=True
inliner = css_inline.CSSInliner(remove_style_tags=True)
html_without_style = inliner(html)
Result
<html>
<head>
<style>
.test-class {
color: #ffffff;
}
a {
color: #17bebb;
}
</style>
</head>
<body style="height: 100% !important;margin: 0 auto !important;padding: 0 !important;width: 100% !important;background: #f1f1f1;">
<a class="test-class" href="https://example.com" style="text-decoration: none;">Test</a>
</body>
</html>
The result is not correct. As you can see, not all styles apply to tag <a>
, and not all tags <style></style>
are removed.
Use css_inline without any options.
html_with_style = css_inline.inline(html)
Result
<html>
<head>
<style>
body {
margin: 0 auto !important;
padding: 0 !important;
height: 100% !important;
width: 100% !important;
background: #f1f1f1;
}
a {
text-decoration: none;
}
</style>
<style>
.test-class {
color: #ffffff;
}
a {
color: #17bebb;
}
</style>
</head>
<body style="margin: 0 auto !important;width: 100% !important;height: 100% !important;background: #f1f1f1;padding: 0 !important;">
<a class="test-class" href="https://example.com" style="color: #ffffff;text-decoration: none;">Test</a>
</body>
</html>
The result is correct.
โ pip install css-inline --no-binary :all:
Collecting css-inline
Downloading css_inline-0.6.1.tar.gz (5.5 kB)
Installing build dependencies ... done
Getting requirements to build wheel ... done
Preparing wheel metadata ... done
Building wheels for collected packages: css-inline
Building wheel for css-inline (PEP 517) ... error
ERROR: Command errored out with exit status 1:
...
error: failed to get `css-inline` as a dependency of package `css-inline-python v0.6.1 (/tmp/pip-install-71d_ro91/css-inline_d9ee84badfa04fe3b7c66254f7a0c899)`
Caused by:
failed to load source for dependency `css-inline`
Caused by:
Unable to update /tmp/pip-install-71d_ro91
Caused by:
failed to read `/tmp/pip-install-71d_ro91/Cargo.toml`
Caused by:
No such file or directory (os error 2)
...
subprocess.CalledProcessError: Command '['cargo', 'metadata', '--manifest-path', 'Cargo.toml', '--format-version', '1']' returned non-zero exit status 101.
----------------------------------------
ERROR: Failed building wheel for css-inline```
Links:
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: aarch64-apple-darwin
profile: minimal
override: true
E.g. with link[rel~=stylesheet]
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.