Skip to content

Commit bf49213

Browse files
committed
feat: Non-blocking stylesheet resolving
1 parent a52171c commit bf49213

File tree

21 files changed

+669
-474
lines changed

21 files changed

+669
-474
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Added
66

77
- A way to customize resolving remote stylesheets.
8+
- Non-blocking stylesheet resolving by default.
89

910
### Changed
1011

README.md

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,21 @@ const HTML: &str = r#"<html>
7070
</body>
7171
</html>"#;
7272

73+
#[tokio::main]
74+
async fn main() -> css_inline::Result<()> {
75+
let inlined = css_inline::inline(HTML).await?;
76+
// Do something with inlined HTML, e.g. send an email
77+
Ok(())
78+
}
79+
```
80+
81+
There is also a "blocking" API for inlining:
82+
83+
```rust
84+
const HTML: &str = "...";
85+
7386
fn main() -> css_inline::Result<()> {
74-
let inlined = css_inline::inline(HTML)?;
87+
let inlined = css_inline::blocking::inline(HTML)?;
7588
// Do something with inlined HTML, e.g. send an email
7689
Ok(())
7790
}
@@ -84,11 +97,12 @@ fn main() -> css_inline::Result<()> {
8497
```rust
8598
const HTML: &str = "...";
8699

87-
fn main() -> css_inline::Result<()> {
100+
#[tokio::main]
101+
async fn main() -> css_inline::Result<()> {
88102
let inliner = css_inline::CSSInliner::options()
89103
.load_remote_stylesheets(false)
90104
.build();
91-
let inlined = inliner.inline(HTML)?;
105+
let inlined = inliner.inline(HTML).await?;
92106
// Do something with inlined HTML, e.g. send an email
93107
Ok(())
94108
}
@@ -131,12 +145,28 @@ If you'd like to load stylesheets from your filesystem, use the `file://` scheme
131145
```rust
132146
const HTML: &str = "...";
133147

134-
fn main() -> css_inline::Result<()> {
148+
#[tokio::main]
149+
async fn main() -> css_inline::Result<()> {
135150
let base_url = css_inline::Url::parse("file://styles/email/").expect("Invalid URL");
136151
let inliner = css_inline::CSSInliner::options()
137152
.base_url(Some(base_url))
138153
.build();
139-
let inlined = inliner.inline(HTML);
154+
let inlined = inliner.inline(HTML).await?;
155+
// Do something with inlined HTML, e.g. send an email
156+
Ok(())
157+
}
158+
```
159+
160+
The blocking version is available as well:
161+
162+
```rust
163+
const HTML: &str = "...";
164+
165+
fn main() -> css_inline::Result<()> {
166+
let inliner = css_inline::blocking::CSSInliner::options()
167+
.load_remote_stylesheets(false)
168+
.build();
169+
let inlined = inliner.inline(HTML)?;
140170
// Do something with inlined HTML, e.g. send an email
141171
Ok(())
142172
}
@@ -149,7 +179,7 @@ For resolving remote stylesheets it is possible to implement a custom resolver:
149179
pub struct CustomStylesheetResolver;
150180

151181
impl css_inline::StylesheetResolver for CustomStylesheetResolver {
152-
fn retrieve(&self, location: &str) -> css_inline::Result<String> {
182+
fn retrieve_blocking(&self, location: &str) -> css_inline::Result<String> {
153183
Err(self.unsupported("External stylesheets are not supported"))
154184
}
155185
}

bindings/c/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ cbindgen = "0.26"
1818
path = "../../css-inline"
1919
version = "*"
2020
default-features = false
21-
features = ["http", "file"]
21+
features = ["http-blocking", "file"]

bindings/c/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use css_inline::{CSSInliner, DefaultStylesheetResolver, InlineError, InlineOptions, Url};
1+
use css_inline::{
2+
blocking::{CSSInliner, InlineOptions},
3+
DefaultStylesheetResolver, InlineError, Url,
4+
};
25
use libc::{c_char, size_t};
36
use std::{borrow::Cow, cmp, ffi::CStr, io::Write, ptr, sync::Arc};
47

bindings/javascript/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ serde = { version = "1", features = ["derive"], default-features = false }
3232
path = "../../css-inline"
3333
version = "*"
3434
default-features = false
35-
features = ["http", "file"]
35+
features = ["http-blocking", "file"]
3636

3737
[target.'cfg(target_arch = "wasm32")'.dependencies.css-inline]
3838
path = "../../css-inline"

bindings/javascript/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,6 @@ export function inline(html: string, options?: InlineOptions): string;
6060
export function version(): string;"#;
6161

6262
fn inline_inner(html: String, options: Options) -> std::result::Result<String, errors::JsError> {
63-
let inliner = css_inline::CSSInliner::new(options.try_into()?);
63+
let inliner = css_inline::blocking::CSSInliner::new(options.try_into()?);
6464
Ok(inliner.inline(&html).map_err(errors::InlineError)?)
6565
}

bindings/javascript/src/options.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,11 @@ pub struct Options {
4242
pub preallocate_node_capacity: Option<u32>,
4343
}
4444

45-
impl TryFrom<Options> for css_inline::InlineOptions<'_> {
45+
impl TryFrom<Options> for css_inline::blocking::InlineOptions<'_> {
4646
type Error = JsError;
4747

4848
fn try_from(value: Options) -> std::result::Result<Self, Self::Error> {
49-
Ok(css_inline::InlineOptions {
49+
Ok(css_inline::blocking::InlineOptions {
5050
inline_style_tags: value.inline_style_tags.unwrap_or(true),
5151
keep_style_tags: value.keep_style_tags.unwrap_or(false),
5252
keep_link_tags: value.keep_link_tags.unwrap_or(false),

bindings/python/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ url = "2"
2323
path = "../../css-inline"
2424
version = "*"
2525
default-features = false
26-
features = ["http", "file"]
26+
features = ["http-blocking", "file"]
2727

2828
[profile.release]
2929
codegen-units = 1

bindings/python/src/lib.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,12 @@ fn parse_url(url: Option<String>) -> PyResult<Option<rust_inline::Url>> {
7474
/// Customizable CSS inliner.
7575
#[pyclass]
7676
struct CSSInliner {
77-
inner: rust_inline::CSSInliner<'static>,
77+
inner: rust_inline::blocking::CSSInliner<'static>,
7878
}
7979

8080
macro_rules! inliner {
8181
($inline_style_tags:expr, $keep_style_tags:expr, $keep_link_tags:expr, $base_url:expr, $load_remote_stylesheets:expr, $extra_css:expr, $preallocate_node_capacity:expr) => {{
82-
let options = rust_inline::InlineOptions {
82+
rust_inline::blocking::InlineOptions {
8383
inline_style_tags: $inline_style_tags.unwrap_or(true),
8484
keep_style_tags: $keep_style_tags.unwrap_or(false),
8585
keep_link_tags: $keep_link_tags.unwrap_or(false),
@@ -88,8 +88,8 @@ macro_rules! inliner {
8888
extra_css: $extra_css.map(Into::into),
8989
preallocate_node_capacity: $preallocate_node_capacity.unwrap_or(32),
9090
resolver: std::sync::Arc::new(rust_inline::DefaultStylesheetResolver),
91-
};
92-
rust_inline::CSSInliner::new(options)
91+
}
92+
.build()
9393
}};
9494
}
9595

@@ -198,7 +198,7 @@ fn inline_many(
198198
}
199199

200200
fn inline_many_impl(
201-
inliner: &rust_inline::CSSInliner<'_>,
201+
inliner: &rust_inline::blocking::CSSInliner<'_>,
202202
htmls: &PyList,
203203
) -> PyResult<Vec<String>> {
204204
// Extract strings from the list. It will fail if there is any non-string value

bindings/ruby/ext/css_inline/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,4 @@ rayon = "1"
2323
path = "../../../../css-inline"
2424
version = "*"
2525
default-features = false
26-
features = ["http", "file"]
26+
features = ["http-blocking", "file"]

bindings/ruby/ext/css_inline/src/lib.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ type RubyResult<T> = Result<T, magnus::Error>;
3939

4040
fn parse_options<Req>(
4141
args: &Args<Req, (), (), (), RHash, ()>,
42-
) -> RubyResult<rust_inline::InlineOptions<'static>> {
42+
) -> RubyResult<rust_inline::blocking::InlineOptions<'static>> {
4343
let kwargs = get_kwargs::<
4444
_,
4545
(),
@@ -67,7 +67,7 @@ fn parse_options<Req>(
6767
],
6868
)?;
6969
let kwargs = kwargs.optional;
70-
Ok(rust_inline::InlineOptions {
70+
Ok(rust_inline::blocking::InlineOptions {
7171
inline_style_tags: kwargs.0.unwrap_or(true),
7272
keep_style_tags: kwargs.1.unwrap_or(false),
7373
keep_link_tags: kwargs.2.unwrap_or(false),
@@ -81,7 +81,7 @@ fn parse_options<Req>(
8181

8282
#[magnus::wrap(class = "CSSInline::CSSInliner")]
8383
struct CSSInliner {
84-
inner: rust_inline::CSSInliner<'static>,
84+
inner: rust_inline::blocking::CSSInliner<'static>,
8585
}
8686

8787
struct InlineErrorWrapper(rust_inline::InlineError);
@@ -133,7 +133,7 @@ impl CSSInliner {
133133
let args = scan_args::<(), _, _, _, _, _>(args)?;
134134
let options = parse_options(&args)?;
135135
Ok(CSSInliner {
136-
inner: rust_inline::CSSInliner::new(options),
136+
inner: rust_inline::blocking::CSSInliner::new(options),
137137
})
138138
}
139139

@@ -152,21 +152,21 @@ fn inline(args: &[Value]) -> RubyResult<String> {
152152
let args = scan_args::<(String,), _, _, _, _, _>(args)?;
153153
let options = parse_options(&args)?;
154154
let html = args.required.0;
155-
Ok(rust_inline::CSSInliner::new(options)
155+
Ok(rust_inline::blocking::CSSInliner::new(options)
156156
.inline(&html)
157157
.map_err(InlineErrorWrapper)?)
158158
}
159159

160160
fn inline_many(args: &[Value]) -> RubyResult<Vec<String>> {
161161
let args = scan_args::<(Vec<String>,), _, _, _, _, _>(args)?;
162162
let options = parse_options(&args)?;
163-
let inliner = rust_inline::CSSInliner::new(options);
163+
let inliner = rust_inline::blocking::CSSInliner::new(options);
164164
inline_many_impl(&args.required.0, &inliner)
165165
}
166166

167167
fn inline_many_impl(
168168
htmls: &[String],
169-
inliner: &rust_inline::CSSInliner<'static>,
169+
inliner: &rust_inline::blocking::CSSInliner<'static>,
170170
) -> RubyResult<Vec<String>> {
171171
let output: Result<Vec<_>, _> = htmls.par_iter().map(|html| inliner.inline(html)).collect();
172172
Ok(output.map_err(InlineErrorWrapper)?)

css-inline/Cargo.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,20 @@ rust-version = "1.65"
2222
name = "css-inline"
2323

2424
[features]
25-
default = ["cli", "http", "file"]
25+
default = ["cli", "http", "http-blocking", "file"]
2626
cli = ["pico-args", "rayon"]
2727
http = ["reqwest"]
28+
http-blocking = ["reqwest/blocking"]
2829
file = []
2930

3031
[dependencies]
3132
cssparser = "0.31.2"
33+
futures-util = "0.3.30"
3234
html5ever = "0.26.0"
3335
indexmap = "2.1"
3436
pico-args = { version = "0.3", optional = true }
3537
rayon = { version = "1.7", optional = true }
36-
reqwest = { version = "0.11.23", optional = true, default-features = false, features = ["rustls-tls", "blocking"] }
38+
reqwest = { version = "0.11.23", optional = true, default-features = false, features = ["rustls-tls"] }
3739
rustc-hash = "1.1.0"
3840
selectors = "0.25.0"
3941
smallvec = "1"
@@ -46,6 +48,7 @@ criterion = { version = "0.5.1", features = [], default-features = false }
4648
serde = { version = "1", features = ["derive"] }
4749
serde_json = "1"
4850
test-case = "3.3"
51+
tokio = { version = "1.32", features = ["macros", "rt"] }
4952

5053
[[bench]]
5154
name = "inliner"

css-inline/benches/inliner.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use codspeed_criterion_compat::{black_box, criterion_group, criterion_main, Criterion};
2-
use css_inline::inline_to;
2+
use css_inline::blocking::inline_to;
33
use std::fs;
44

55
#[derive(serde::Deserialize, Debug)]

css-inline/src/error.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ pub enum InlineError {
2020
/// from the filesystem.
2121
IO(io::Error),
2222
/// Network-related problem. E.g. resource is not available.
23-
#[cfg(feature = "http")]
23+
#[cfg(any(feature = "http", feature = "http-blocking"))]
2424
Network {
2525
/// Original network error.
2626
error: reqwest::Error,
@@ -41,7 +41,7 @@ impl Error for InlineError {
4141
fn source(&self) -> Option<&(dyn Error + 'static)> {
4242
match self {
4343
InlineError::IO(error) => Some(error),
44-
#[cfg(feature = "http")]
44+
#[cfg(any(feature = "http", feature = "http-blocking"))]
4545
InlineError::Network { error, .. } => Some(error),
4646
InlineError::MissingStyleSheet { .. } | InlineError::ParseError(_) => None,
4747
}
@@ -52,7 +52,7 @@ impl Display for InlineError {
5252
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
5353
match self {
5454
Self::IO(error) => error.fmt(f),
55-
#[cfg(feature = "http")]
55+
#[cfg(any(feature = "http", feature = "http-blocking"))]
5656
Self::Network { error, location } => f.write_fmt(format_args!("{error}: {location}")),
5757
Self::ParseError(error) => f.write_str(error),
5858
Self::MissingStyleSheet { path } => {

0 commit comments

Comments
 (0)