css_inline 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +80 -15
- data/ext/css_inline/Cargo.toml +2 -2
- data/ext/css_inline/src/lib.rs +97 -5
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dcb73434f736f0fbe7a516bdbeb8064091ac0408910ff3af705ca8e3579368f0
|
|
4
|
+
data.tar.gz: f52c04d176780cca6c3f758cb8fbcd6123b693ef80696e60993e4d846258584e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 33061093a87721b1cc61215c63124ea2e8669a3e4a88304499abee70a4b8443aabc7da5284dcc41cc95d313f8477a84a75624ab86d737dec5ae6d35c3c13067a
|
|
7
|
+
data.tar.gz: 057d4df4c802feb3cbebb3c69d7e8369a76114d47423cab1b077f1a57c254ae93ce869a99e13f09c22a0df8e661c6e4c9c53ed6d674e92ba9f060eddb6142ae9
|
data/README.md
CHANGED
|
@@ -37,9 +37,11 @@ into:
|
|
|
37
37
|
- Inlines CSS from `style` and `link` tags
|
|
38
38
|
- Removes `style` and `link` tags
|
|
39
39
|
- Resolves external stylesheets (including local files)
|
|
40
|
+
- Optionally caches external stylesheets
|
|
40
41
|
- Can process multiple documents in parallel
|
|
41
42
|
- Works on Linux, Windows, and macOS
|
|
42
43
|
- Supports HTML5 & CSS3
|
|
44
|
+
- Tested on Ruby 2.7 and 3.2.
|
|
43
45
|
|
|
44
46
|
## Playground
|
|
45
47
|
|
|
@@ -67,7 +69,45 @@ puts inlined
|
|
|
67
69
|
# Outputs: "<html><head></head><body><h1 style=\"color:blue;\">Big Text</h1></body></html>"
|
|
68
70
|
```
|
|
69
71
|
|
|
70
|
-
|
|
72
|
+
Note that `css-inline` automatically adds missing `html` and `body` tags, so the output is a valid HTML document.
|
|
73
|
+
|
|
74
|
+
Alternatively, you can inline CSS into an HTML fragment, preserving the original structure:
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
require 'css_inline'
|
|
78
|
+
|
|
79
|
+
fragment = """
|
|
80
|
+
<main>
|
|
81
|
+
<h1>Hello</h1>
|
|
82
|
+
<section>
|
|
83
|
+
<p>who am i</p>
|
|
84
|
+
</section>
|
|
85
|
+
</main>
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
css = """
|
|
89
|
+
p {
|
|
90
|
+
color: red;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
h1 {
|
|
94
|
+
color: blue;
|
|
95
|
+
}
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
inlined = CSSInline.inline_fragment(fragment, css)
|
|
99
|
+
|
|
100
|
+
puts inlined
|
|
101
|
+
# HTML becomes this:
|
|
102
|
+
# <main>
|
|
103
|
+
# <h1 style="color: blue;">Hello</h1>
|
|
104
|
+
# <section>
|
|
105
|
+
# <p style="color: red;">who am i</p>
|
|
106
|
+
# </section>
|
|
107
|
+
# </main>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
When there is a need to inline multiple HTML documents simultaneously, `css_inline` offers `inline_many` and `inline_many_fragments` functions.
|
|
71
111
|
This feature allows for concurrent processing of several inputs, significantly improving performance when dealing with a large number of documents.
|
|
72
112
|
|
|
73
113
|
```ruby
|
|
@@ -92,11 +132,12 @@ inliner = CSSInline::CSSInliner.new(keep_style_tags: true)
|
|
|
92
132
|
inliner.inline("...")
|
|
93
133
|
```
|
|
94
134
|
|
|
95
|
-
- `inline_style_tags`. Specifies whether to inline CSS from "style" tags. Default: `
|
|
96
|
-
- `keep_style_tags`. Specifies whether to keep "style" tags after inlining. Default: `
|
|
97
|
-
- `keep_link_tags`. Specifies whether to keep "link" tags after inlining. Default: `
|
|
135
|
+
- `inline_style_tags`. Specifies whether to inline CSS from "style" tags. Default: `true`
|
|
136
|
+
- `keep_style_tags`. Specifies whether to keep "style" tags after inlining. Default: `false`
|
|
137
|
+
- `keep_link_tags`. Specifies whether to keep "link" tags after inlining. Default: `false`
|
|
98
138
|
- `base_url`. The base URL used to resolve relative URLs. If you'd like to load stylesheets from your filesystem, use the `file://` scheme. Default: `nil`
|
|
99
|
-
- `load_remote_stylesheets`. Specifies whether remote stylesheets should be loaded. Default: `
|
|
139
|
+
- `load_remote_stylesheets`. Specifies whether remote stylesheets should be loaded. Default: `true`
|
|
140
|
+
- `cache`. Specifies caching options for external stylesheets (for example, `StylesheetCache(size: 5)`). Default: `nil`
|
|
100
141
|
- `extra_css`. Extra CSS to be inlined. Default: `nil`
|
|
101
142
|
- `preallocate_node_capacity`. **Advanced**. Preallocates capacity for HTML nodes during parsing. This can improve performance when you have an estimate of the number of nodes in your HTML document. Default: `32`
|
|
102
143
|
|
|
@@ -124,6 +165,21 @@ The `data-css-inline="ignore"` attribute also allows you to skip `link` and `sty
|
|
|
124
165
|
</body>
|
|
125
166
|
```
|
|
126
167
|
|
|
168
|
+
Alternatively, you may keep `style` from being removed by using the `data-css-inline="keep"` attribute.
|
|
169
|
+
This is useful if you want to keep `@media` queries for responsive emails in separate `style` tags:
|
|
170
|
+
|
|
171
|
+
```html
|
|
172
|
+
<head>
|
|
173
|
+
<!-- Styles below are not removed -->
|
|
174
|
+
<style data-css-inline="keep">h1 { color:blue; }</style>
|
|
175
|
+
</head>
|
|
176
|
+
<body>
|
|
177
|
+
<h1>Big Text</h1>
|
|
178
|
+
</body>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Such tags will be kept in the resulting HTML even if the `keep_style_tags` option is set to `false`.
|
|
182
|
+
|
|
127
183
|
If you'd like to load stylesheets from your filesystem, use the `file://` scheme:
|
|
128
184
|
|
|
129
185
|
```ruby
|
|
@@ -134,6 +190,19 @@ inliner = CSSInline::CSSInliner.new(base_url: "file://styles/email/")
|
|
|
134
190
|
inliner.inline("...")
|
|
135
191
|
```
|
|
136
192
|
|
|
193
|
+
You can also cache external stylesheets to avoid excessive network requests:
|
|
194
|
+
|
|
195
|
+
```ruby
|
|
196
|
+
require 'css_inline'
|
|
197
|
+
|
|
198
|
+
inliner = CSSInline::CSSInliner.new(
|
|
199
|
+
cache: CSSInline::StylesheetCache.new(size: 5)
|
|
200
|
+
)
|
|
201
|
+
inliner.inline("...")
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Caching is disabled by default.
|
|
205
|
+
|
|
137
206
|
## Performance
|
|
138
207
|
|
|
139
208
|
Leveraging efficient tools from Mozilla's Servo project, this library delivers superior performance.
|
|
@@ -141,19 +210,15 @@ It consistently outperforms `premailer`, offering speed increases often exceedin
|
|
|
141
210
|
|
|
142
211
|
The table below provides a detailed comparison between `css_inline` and `premailer` when inlining CSS into an HTML document (like in the Usage section above):
|
|
143
212
|
|
|
144
|
-
| | Size | `css_inline 0.
|
|
213
|
+
| | Size | `css_inline 0.14.0` | `premailer 1.21.0 with Nokogiri 1.15.2` | Difference |
|
|
145
214
|
|-------------------|---------|---------------------|------------------------------------------------|------------|
|
|
146
|
-
| Basic usage | 230 B | 8.
|
|
147
|
-
| Realistic email 1 | 8.58 KB |
|
|
148
|
-
| Realistic email 2 | 4.3 KB |
|
|
149
|
-
| GitHub Page | 1.81 MB |
|
|
215
|
+
| Basic usage | 230 B | 8.82 µs | 417.84 µs | **47.37x** |
|
|
216
|
+
| Realistic email 1 | 8.58 KB | 160.37 µs | 10.15 ms | **63.32x** |
|
|
217
|
+
| Realistic email 2 | 4.3 KB | 102.88 µs | Error: Cannot parse 0 calc((100% - 500px) / 2) | - |
|
|
218
|
+
| GitHub Page | 1.81 MB | 237.03 ms | 3.02 s | **12.78x** |
|
|
150
219
|
|
|
151
220
|
Please refer to the `test/bench.rb` file to review the benchmark code.
|
|
152
|
-
The results displayed above were measured using stable `rustc 1.
|
|
153
|
-
|
|
154
|
-
## Ruby support
|
|
155
|
-
|
|
156
|
-
`css_inline` supports Ruby 2.7 and 3.2.
|
|
221
|
+
The results displayed above were measured using stable `rustc 1.77.1` on Ruby `3.2.2`.
|
|
157
222
|
|
|
158
223
|
## Further reading
|
|
159
224
|
|
data/ext/css_inline/Cargo.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "css-inline-ruby"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.14.0"
|
|
4
4
|
authors = ["Dmitry Dygalo <dmitry@dygalo.dev>"]
|
|
5
5
|
edition = "2021"
|
|
6
6
|
readme = "README.rdoc"
|
|
@@ -23,4 +23,4 @@ rayon = "1"
|
|
|
23
23
|
path = "../../../../css-inline"
|
|
24
24
|
version = "*"
|
|
25
25
|
default-features = false
|
|
26
|
-
features = ["http", "file"]
|
|
26
|
+
features = ["http", "file", "stylesheet-cache"]
|
data/ext/css_inline/src/lib.rs
CHANGED
|
@@ -25,16 +25,20 @@
|
|
|
25
25
|
rust_2018_compatibility,
|
|
26
26
|
rust_2021_compatibility
|
|
27
27
|
)]
|
|
28
|
-
#[allow(clippy::module_name_repetitions)]
|
|
29
28
|
use css_inline as rust_inline;
|
|
30
29
|
use magnus::{
|
|
31
30
|
class, define_module, function, method,
|
|
32
31
|
prelude::*,
|
|
33
32
|
scan_args::{get_kwargs, scan_args, Args},
|
|
34
|
-
|
|
33
|
+
typed_data::Obj,
|
|
34
|
+
DataTypeFunctions, RHash, TypedData, Value,
|
|
35
35
|
};
|
|
36
36
|
use rayon::prelude::*;
|
|
37
|
-
use std::
|
|
37
|
+
use std::{
|
|
38
|
+
borrow::Cow,
|
|
39
|
+
num::NonZeroUsize,
|
|
40
|
+
sync::{Arc, Mutex},
|
|
41
|
+
};
|
|
38
42
|
|
|
39
43
|
type RubyResult<T> = Result<T, magnus::Error>;
|
|
40
44
|
|
|
@@ -50,6 +54,7 @@ fn parse_options<Req>(
|
|
|
50
54
|
Option<bool>,
|
|
51
55
|
Option<String>,
|
|
52
56
|
Option<bool>,
|
|
57
|
+
Option<Obj<StylesheetCache>>,
|
|
53
58
|
Option<String>,
|
|
54
59
|
Option<usize>,
|
|
55
60
|
),
|
|
@@ -63,6 +68,7 @@ fn parse_options<Req>(
|
|
|
63
68
|
"keep_link_tags",
|
|
64
69
|
"base_url",
|
|
65
70
|
"load_remote_stylesheets",
|
|
71
|
+
"cache",
|
|
66
72
|
"extra_css",
|
|
67
73
|
"preallocate_node_capacity",
|
|
68
74
|
],
|
|
@@ -74,11 +80,38 @@ fn parse_options<Req>(
|
|
|
74
80
|
keep_link_tags: kwargs.2.unwrap_or(false),
|
|
75
81
|
base_url: parse_url(kwargs.3)?,
|
|
76
82
|
load_remote_stylesheets: kwargs.4.unwrap_or(true),
|
|
77
|
-
|
|
78
|
-
|
|
83
|
+
cache: kwargs
|
|
84
|
+
.5
|
|
85
|
+
.map(|cache| Mutex::new(rust_inline::StylesheetCache::new(cache.size))),
|
|
86
|
+
extra_css: kwargs.6.map(Cow::Owned),
|
|
87
|
+
preallocate_node_capacity: kwargs.7.unwrap_or(32),
|
|
88
|
+
resolver: Arc::new(rust_inline::DefaultStylesheetResolver),
|
|
79
89
|
})
|
|
80
90
|
}
|
|
81
91
|
|
|
92
|
+
#[derive(DataTypeFunctions, TypedData)]
|
|
93
|
+
#[magnus(class = "CSSInline::StylesheetCache")]
|
|
94
|
+
struct StylesheetCache {
|
|
95
|
+
size: NonZeroUsize,
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
impl StylesheetCache {
|
|
99
|
+
fn new(args: &[Value]) -> RubyResult<StylesheetCache> {
|
|
100
|
+
fn error() -> magnus::Error {
|
|
101
|
+
magnus::Error::new(
|
|
102
|
+
magnus::exception::arg_error(),
|
|
103
|
+
"Cache size must be an integer greater than zero",
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let args: Args<(), (), (), (), RHash, ()> = scan_args::<(), _, _, _, RHash, _>(args)?;
|
|
108
|
+
let kwargs = get_kwargs::<_, (), (Option<usize>,), ()>(args.keywords, &[], &["size"])
|
|
109
|
+
.map_err(|_| error())?;
|
|
110
|
+
let size = NonZeroUsize::new(kwargs.optional.0.unwrap_or(8)).ok_or_else(error)?;
|
|
111
|
+
Ok(StylesheetCache { size })
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
82
115
|
#[magnus::wrap(class = "CSSInline::CSSInliner")]
|
|
83
116
|
struct CSSInliner {
|
|
84
117
|
inner: rust_inline::CSSInliner<'static>,
|
|
@@ -142,10 +175,27 @@ impl CSSInliner {
|
|
|
142
175
|
Ok(self.inner.inline(&html).map_err(InlineErrorWrapper)?)
|
|
143
176
|
}
|
|
144
177
|
|
|
178
|
+
#[allow(clippy::needless_pass_by_value)]
|
|
179
|
+
fn inline_fragment(&self, html: String, css: String) -> RubyResult<String> {
|
|
180
|
+
Ok(self
|
|
181
|
+
.inner
|
|
182
|
+
.inline_fragment(&html, &css)
|
|
183
|
+
.map_err(InlineErrorWrapper)?)
|
|
184
|
+
}
|
|
185
|
+
|
|
145
186
|
#[allow(clippy::needless_pass_by_value)]
|
|
146
187
|
fn inline_many(&self, html: Vec<String>) -> RubyResult<Vec<String>> {
|
|
147
188
|
inline_many_impl(&html, &self.inner)
|
|
148
189
|
}
|
|
190
|
+
|
|
191
|
+
#[allow(clippy::needless_pass_by_value)]
|
|
192
|
+
fn inline_many_fragments(
|
|
193
|
+
&self,
|
|
194
|
+
html: Vec<String>,
|
|
195
|
+
css: Vec<String>,
|
|
196
|
+
) -> RubyResult<Vec<String>> {
|
|
197
|
+
inline_many_fragments_impl(&html, &css, &self.inner)
|
|
198
|
+
}
|
|
149
199
|
}
|
|
150
200
|
|
|
151
201
|
fn inline(args: &[Value]) -> RubyResult<String> {
|
|
@@ -157,6 +207,16 @@ fn inline(args: &[Value]) -> RubyResult<String> {
|
|
|
157
207
|
.map_err(InlineErrorWrapper)?)
|
|
158
208
|
}
|
|
159
209
|
|
|
210
|
+
fn inline_fragment(args: &[Value]) -> RubyResult<String> {
|
|
211
|
+
let args = scan_args::<(String, String), _, _, _, _, _>(args)?;
|
|
212
|
+
let options = parse_options(&args)?;
|
|
213
|
+
let html = args.required.0;
|
|
214
|
+
let css = args.required.1;
|
|
215
|
+
Ok(rust_inline::CSSInliner::new(options)
|
|
216
|
+
.inline_fragment(&html, &css)
|
|
217
|
+
.map_err(InlineErrorWrapper)?)
|
|
218
|
+
}
|
|
219
|
+
|
|
160
220
|
fn inline_many(args: &[Value]) -> RubyResult<Vec<String>> {
|
|
161
221
|
let args = scan_args::<(Vec<String>,), _, _, _, _, _>(args)?;
|
|
162
222
|
let options = parse_options(&args)?;
|
|
@@ -172,17 +232,49 @@ fn inline_many_impl(
|
|
|
172
232
|
Ok(output.map_err(InlineErrorWrapper)?)
|
|
173
233
|
}
|
|
174
234
|
|
|
235
|
+
fn inline_many_fragments(args: &[Value]) -> RubyResult<Vec<String>> {
|
|
236
|
+
let args = scan_args::<(Vec<String>, Vec<String>), _, _, _, _, _>(args)?;
|
|
237
|
+
let options = parse_options(&args)?;
|
|
238
|
+
let inliner = rust_inline::CSSInliner::new(options);
|
|
239
|
+
inline_many_fragments_impl(&args.required.0, &args.required.1, &inliner)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
fn inline_many_fragments_impl(
|
|
243
|
+
htmls: &[String],
|
|
244
|
+
css: &[String],
|
|
245
|
+
inliner: &rust_inline::CSSInliner<'static>,
|
|
246
|
+
) -> RubyResult<Vec<String>> {
|
|
247
|
+
let output: Result<Vec<_>, _> = htmls
|
|
248
|
+
.par_iter()
|
|
249
|
+
.zip(css)
|
|
250
|
+
.map(|(html, css)| inliner.inline_fragment(html, css))
|
|
251
|
+
.collect();
|
|
252
|
+
Ok(output.map_err(InlineErrorWrapper)?)
|
|
253
|
+
}
|
|
254
|
+
|
|
175
255
|
#[magnus::init(name = "css_inline")]
|
|
176
256
|
fn init() -> RubyResult<()> {
|
|
177
257
|
let module = define_module("CSSInline")?;
|
|
178
258
|
|
|
179
259
|
module.define_module_function("inline", function!(inline, -1))?;
|
|
260
|
+
module.define_module_function("inline_fragment", function!(inline_fragment, -1))?;
|
|
180
261
|
module.define_module_function("inline_many", function!(inline_many, -1))?;
|
|
262
|
+
module.define_module_function(
|
|
263
|
+
"inline_many_fragments",
|
|
264
|
+
function!(inline_many_fragments, -1),
|
|
265
|
+
)?;
|
|
181
266
|
|
|
182
267
|
let class = module.define_class("CSSInliner", class::object())?;
|
|
183
268
|
class.define_singleton_method("new", function!(CSSInliner::new, -1))?;
|
|
184
269
|
class.define_method("inline", method!(CSSInliner::inline, 1))?;
|
|
270
|
+
class.define_method("inline_fragment", method!(CSSInliner::inline_fragment, 2))?;
|
|
185
271
|
class.define_method("inline_many", method!(CSSInliner::inline_many, 1))?;
|
|
272
|
+
class.define_method(
|
|
273
|
+
"inline_many_fragments",
|
|
274
|
+
method!(CSSInliner::inline_many_fragments, 2),
|
|
275
|
+
)?;
|
|
186
276
|
|
|
277
|
+
let class = module.define_class("StylesheetCache", class::object())?;
|
|
278
|
+
class.define_singleton_method("new", function!(StylesheetCache::new, -1))?;
|
|
187
279
|
Ok(())
|
|
188
280
|
}
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: css_inline
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.14.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dmitry Dygalo
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2024-04-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rake-compiler
|
|
@@ -124,7 +124,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
124
124
|
version: 3.3.26
|
|
125
125
|
requirements:
|
|
126
126
|
- Rust >= 1.65
|
|
127
|
-
rubygems_version: 3.4.
|
|
127
|
+
rubygems_version: 3.4.19
|
|
128
128
|
signing_key:
|
|
129
129
|
specification_version: 4
|
|
130
130
|
summary: High-performance library for inlining CSS into HTML 'style' attributes
|