Skip to content

Commit 8ade631

Browse files
committed
feat: A way to customize resolving remote stylesheets
1 parent a92c2f5 commit 8ade631

File tree

21 files changed

+251
-140
lines changed

21 files changed

+251
-140
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## [Unreleased]
44

5+
### Added
6+
7+
- A way to customize resolving remote stylesheets.
8+
59
## [0.12.0] - 2023-12-28
610

711
### Changed

README.md

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

73-
fn main() -> Result<(), css_inline::InlineError> {
73+
fn main() -> css_inline::Result<()> {
7474
let inlined = css_inline::inline(HTML)?;
7575
// Do something with inlined HTML, e.g. send an email
7676
Ok(())
@@ -84,7 +84,7 @@ fn main() -> Result<(), css_inline::InlineError> {
8484
```rust
8585
const HTML: &str = "...";
8686

87-
fn main() -> Result<(), css_inline::InlineError> {
87+
fn main() -> css_inline::Result<()> {
8888
let inliner = css_inline::CSSInliner::options()
8989
.load_remote_stylesheets(false)
9090
.build();
@@ -131,7 +131,7 @@ If you'd like to load stylesheets from your filesystem, use the `file://` scheme
131131
```rust
132132
const HTML: &str = "...";
133133

134-
fn main() -> Result<(), css_inline::InlineError> {
134+
fn main() -> css_inline::Result<()> {
135135
let base_url = css_inline::Url::parse("file://styles/email/").expect("Invalid URL");
136136
let inliner = css_inline::CSSInliner::options()
137137
.base_url(Some(base_url))
@@ -142,6 +142,26 @@ fn main() -> Result<(), css_inline::InlineError> {
142142
}
143143
```
144144

145+
For resolving remote stylesheets it is possible to implement a custom resolver:
146+
147+
```rust
148+
#[derive(Debug, Default)]
149+
pub struct CustomStylesheetResolver;
150+
151+
impl css_inline::StylesheetResolver for CustomStylesheetResolver {
152+
fn retrieve(&self, location: &str) -> css_inline::Result<String> {
153+
Err(self.unsupported("External stylesheets are not supported"))
154+
}
155+
}
156+
157+
fn main() -> css_inline::Result<()> {
158+
let inliner = css_inline::CSSInliner::options()
159+
.resolver(std::sync::Arc::new(CustomStylesheetResolver))
160+
.build();
161+
Ok(())
162+
}
163+
```
164+
145165
## Performance
146166

147167
`css-inline` typically inlines HTML emails within hundreds of microseconds, though results may vary with input complexity.

bindings/c/rustfmt.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
imports_granularity = "Crate"
2+
edition = "2021"

bindings/c/src/lib.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
1-
use css_inline::{CSSInliner, InlineError, InlineOptions, Url};
1+
use css_inline::{CSSInliner, DefaultStylesheetResolver, InlineError, InlineOptions, Url};
22
use libc::{c_char, size_t};
3-
use std::borrow::Cow;
4-
use std::cmp;
5-
use std::ffi::CStr;
6-
use std::io::Write;
7-
use std::ptr;
3+
use std::{borrow::Cow, cmp, ffi::CStr, io::Write, ptr, sync::Arc};
84

95
/// Result of CSS inlining operations
106
#[repr(C)]
@@ -157,6 +153,7 @@ impl TryFrom<&CssInlinerOptions> for InlineOptions<'_> {
157153
load_remote_stylesheets: value.load_remote_stylesheets,
158154
extra_css: extra_css.map(Cow::Borrowed),
159155
preallocate_node_capacity: value.preallocate_node_capacity,
156+
resolver: Arc::new(DefaultStylesheetResolver),
160157
})
161158
}
162159
}

bindings/javascript/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## [Unreleased]
44

5+
### Changed
6+
7+
- Avoid loading additional dependencies for WASM resulting in ~6% module size reduction.
8+
59
## [0.12.0] - 2023-12-28
610

711
- Initial public release

bindings/javascript/Cargo.toml

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,30 @@ include = ["src/*.rs", "LICENSE", "README.md", "CHANGELOG.md"]
1515
[lib]
1616
crate-type = ["cdylib"]
1717

18+
[target.'cfg(all(not(all(target_os = "linux", target_arch = "aarch64", target_env = "musl")), not(all(target_os = "windows", target_arch = "aarch64")), not(target_arch = "wasm32")))'.dependencies]
19+
mimalloc-rust = { version = "0.2" }
20+
1821
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
1922
napi = { version = "2.14.1", default-features = false, features = ["napi4"] }
2023
napi-derive = "2.14.4"
2124

22-
[target.'cfg(all(not(all(target_os = "linux", target_arch = "aarch64", target_env = "musl")), not(all(target_os = "windows", target_arch = "aarch64")), not(target_arch = "wasm32")))'.dependencies]
23-
mimalloc-rust = { version = "0.2" }
24-
2525
[target.'cfg(target_arch = "wasm32")'.dependencies]
26-
attohttpc = { version = "0.24", default-features = false }
2726
wasm-bindgen = "0.2.87"
2827
serde-wasm-bindgen = "0.6"
2928
getrandom = { version = "0.2", features = ["js"] }
3029
serde = { version = "1", features = ["derive"], default-features = false }
3130

32-
[dependencies.css-inline]
31+
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.css-inline]
3332
path = "../../css-inline"
3433
version = "*"
3534
default-features = false
3635
features = ["http", "file"]
3736

37+
[target.'cfg(target_arch = "wasm32")'.dependencies.css-inline]
38+
path = "../../css-inline"
39+
version = "*"
40+
default-features = false
41+
3842
[build-dependencies]
3943
napi-build = "2.1.0"
4044

bindings/javascript/__test__/wasm.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ test("invalid href", (t) => {
6060
},
6161
{ any: true },
6262
);
63-
t.is(error, "Invalid base URL: http:");
63+
t.is(error, "Loading remote stylesheets is not supported on WASM: http:");
6464
});
6565

6666
test("invalid style", (t) => {

bindings/javascript/src/errors.rs

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -44,22 +44,6 @@ impl From<InlineError> for JsValue {
4444
{
4545
JsValue::from_str(io_error.to_string().as_str())
4646
}
47-
css_inline::InlineError::Network {
48-
error: network_error,
49-
location,
50-
} => {
51-
if let attohttpc::ErrorKind::Io(io_error) = network_error.kind() {
52-
if io_error.kind() == std::io::ErrorKind::Unsupported {
53-
return JsValue::from_str(
54-
format!(
55-
"Loading remote stylesheets is not supported on WASM: {location}"
56-
)
57-
.as_str(),
58-
);
59-
}
60-
}
61-
JsValue::from_str(error.0.to_string().as_str())
62-
}
6347
_ => JsValue::from_str(error.0.to_string().as_str()),
6448
}
6549
}

bindings/javascript/src/options.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::errors::{JsError, UrlError};
22
#[cfg(not(target_arch = "wasm32"))]
33
use napi_derive::napi;
4-
use std::borrow::Cow;
4+
use std::{borrow::Cow, sync::Arc};
55
#[cfg(target_arch = "wasm32")]
66
use wasm_bindgen::prelude::JsValue;
77

@@ -68,6 +68,31 @@ impl TryFrom<Options> for css_inline::InlineOptions<'_> {
6868
} else {
6969
32
7070
},
71+
resolver: {
72+
#[cfg(target_arch = "wasm32")]
73+
{
74+
#[derive(Debug, Default)]
75+
pub struct UnsupportedResolver;
76+
77+
impl css_inline::StylesheetResolver for UnsupportedResolver {
78+
fn retrieve(&self, location: &str) -> css_inline::Result<String> {
79+
let message = if location.starts_with("https")
80+
| location.starts_with("http")
81+
{
82+
format!("Loading remote stylesheets is not supported on WASM: {location}")
83+
} else {
84+
format!("Loading local files is not supported on WASM: {location}")
85+
};
86+
Err(self.unsupported(&message))
87+
}
88+
}
89+
Arc::new(UnsupportedResolver)
90+
}
91+
#[cfg(not(target_arch = "wasm32"))]
92+
{
93+
Arc::new(css_inline::DefaultStylesheetResolver)
94+
}
95+
},
7196
})
7297
}
7398
}

bindings/javascript/wasm/index.js

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,18 @@ heap.push(void 0, null, true, false);
3333
function getObject(idx) {
3434
return heap[idx];
3535
}
36+
var heap_next = heap.length;
37+
function dropObject(idx) {
38+
if (idx < 132)
39+
return;
40+
heap[idx] = heap_next;
41+
heap_next = idx;
42+
}
43+
function takeObject(idx) {
44+
const ret = getObject(idx);
45+
dropObject(idx);
46+
return ret;
47+
}
3648
var WASM_VECTOR_LEN = 0;
3749
var cachedUint8Memory0 = null;
3850
function getUint8Memory0() {
@@ -104,7 +116,6 @@ function getStringFromWasm0(ptr, len) {
104116
ptr = ptr >>> 0;
105117
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
106118
}
107-
var heap_next = heap.length;
108119
function addHeapObject(obj) {
109120
if (heap_next === heap.length)
110121
heap.push(heap.length + 1);
@@ -113,17 +124,6 @@ function addHeapObject(obj) {
113124
heap[idx] = obj;
114125
return idx;
115126
}
116-
function dropObject(idx) {
117-
if (idx < 132)
118-
return;
119-
heap[idx] = heap_next;
120-
heap_next = idx;
121-
}
122-
function takeObject(idx) {
123-
const ret = getObject(idx);
124-
dropObject(idx);
125-
return ret;
126-
}
127127
var cachedFloat64Memory0 = null;
128128
function getFloat64Memory0() {
129129
if (cachedFloat64Memory0 === null || cachedFloat64Memory0.byteLength === 0) {
@@ -261,6 +261,14 @@ function __wbg_get_imports() {
261261
const ret = getObject(arg0) === void 0;
262262
return ret;
263263
};
264+
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
265+
takeObject(arg0);
266+
};
267+
imports.wbg.__wbindgen_boolean_get = function(arg0) {
268+
const v = getObject(arg0);
269+
const ret = typeof v === "boolean" ? v ? 1 : 0 : 2;
270+
return ret;
271+
};
264272
imports.wbg.__wbindgen_string_get = function(arg0, arg1) {
265273
const obj = getObject(arg1);
266274
const ret = typeof obj === "string" ? obj : void 0;
@@ -269,20 +277,15 @@ function __wbg_get_imports() {
269277
getInt32Memory0()[arg0 / 4 + 1] = len1;
270278
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
271279
};
272-
imports.wbg.__wbindgen_boolean_get = function(arg0) {
273-
const v = getObject(arg0);
274-
const ret = typeof v === "boolean" ? v ? 1 : 0 : 2;
280+
imports.wbg.__wbindgen_is_object = function(arg0) {
281+
const val = getObject(arg0);
282+
const ret = typeof val === "object" && val !== null;
275283
return ret;
276284
};
277285
imports.wbg.__wbindgen_string_new = function(arg0, arg1) {
278286
const ret = getStringFromWasm0(arg0, arg1);
279287
return addHeapObject(ret);
280288
};
281-
imports.wbg.__wbindgen_is_object = function(arg0) {
282-
const val = getObject(arg0);
283-
const ret = typeof val === "object" && val !== null;
284-
return ret;
285-
};
286289
imports.wbg.__wbindgen_object_clone_ref = function(arg0) {
287290
const ret = getObject(arg0);
288291
return addHeapObject(ret);
@@ -303,9 +306,6 @@ function __wbg_get_imports() {
303306
const ret = +getObject(arg0);
304307
return ret;
305308
};
306-
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
307-
takeObject(arg0);
308-
};
309309
imports.wbg.__wbg_length_1d25fa9e4ac21ce7 = function(arg0) {
310310
const ret = getObject(arg0).length;
311311
return ret;

0 commit comments

Comments
 (0)