-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcss_inline.ex
More file actions
129 lines (108 loc) · 5.1 KB
/
css_inline.ex
File metadata and controls
129 lines (108 loc) · 5.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
defmodule CSSInline do
@moduledoc """
High-performance CSS inlining for HTML documents.
This library uses a Rust NIF powered by the [css-inline](https://crates.io/crates/css-inline)
crate to inline CSS from `<style>` tags into element `style` attributes. This is particularly
useful for preparing HTML emails, which require inline styles for maximum compatibility
across email clients.
## Usage
iex> html = \"\"\"
...> <html>
...> <head>
...> <style>p { color: red; }</style>
...> </head>
...> <body><p>Hello</p></body>
...> </html>
...> \"\"\"
iex> {:ok, result} = CSSInline.inline(html)
iex> result =~ "color"
true
## Options
The following options can be passed to `inline/2` and `inline!/2`:
* `:inline_style_tags` - Whether to inline CSS from `<style>` tags. Defaults to `true`.
* `:keep_style_tags` - Whether to keep `<style>` tags after inlining. Defaults to `false`.
* `:keep_link_tags` - Whether to keep `<link>` tags after processing. Defaults to `false`.
* `:load_remote_stylesheets` - Whether to load remote stylesheets referenced in `<link>` tags.
Defaults to `true`. Set to `false` to skip external stylesheets.
* `:minify_css` - Whether to minify the inlined CSS. Defaults to `true`.
* `:check_depth` - Whether to check HTML nesting depth before inlining. Defaults to `true`.
* `:max_depth` - Maximum allowed HTML nesting depth. Documents exceeding this return
`{:error, :nesting_depth_exceeded}`. Only applies when `:check_depth` is `true`. Defaults to `128`.
## Performance
The Rust NIF runs on a dirty CPU scheduler to avoid blocking the BEAM's main schedulers,
as CSS inlining can take several milliseconds for large documents. The implementation
uses zero-copy input handling and direct buffer writes for optimal performance.
"""
defmodule Options do
@moduledoc """
Options struct for CSS inlining configuration.
"""
defstruct inline_style_tags: true,
keep_style_tags: false,
keep_link_tags: false,
load_remote_stylesheets: true,
minify_css: true,
check_depth: true,
max_depth: 128
@type t :: %__MODULE__{
inline_style_tags: boolean(),
keep_style_tags: boolean(),
keep_link_tags: boolean(),
load_remote_stylesheets: boolean(),
minify_css: boolean(),
check_depth: boolean(),
max_depth: pos_integer()
}
end
@type option ::
{:inline_style_tags, boolean()}
| {:keep_style_tags, boolean()}
| {:keep_link_tags, boolean()}
| {:load_remote_stylesheets, boolean()}
| {:minify_css, boolean()}
| {:check_depth, boolean()}
| {:max_depth, pos_integer()}
@doc """
Inlines CSS from `<style>` tags into element `style` attributes.
Returns `{:ok, inlined_html}` on success, or `{:error, reason}` on failure.
## Options
* `:inline_style_tags` - Whether to inline CSS from `<style>` tags. Defaults to `true`.
* `:keep_style_tags` - Whether to keep `<style>` tags after inlining. Defaults to `false`.
* `:keep_link_tags` - Whether to keep `<link>` tags after processing. Defaults to `false`.
* `:load_remote_stylesheets` - Whether to load remote stylesheets. Defaults to `true`.
* `:minify_css` - Whether to minify the inlined CSS. Defaults to `true`.
* `:check_depth` - Whether to check HTML nesting depth before inlining. Defaults to `true`.
* `:max_depth` - Maximum allowed HTML nesting depth. Defaults to `128`.
"""
@spec inline(String.t(), [option()]) :: {:ok, String.t()} | {:error, term()}
def inline(html, opts \\ []) when is_binary(html) and is_list(opts) do
options = struct(Options, opts)
case CSSInline.Native.inline_css(html, options) do
result when is_binary(result) or is_list(result) ->
{:ok, IO.iodata_to_binary(result)}
{:error, reason} ->
{:error, reason}
end
rescue
e -> {:error, {e, __STACKTRACE__}}
end
@doc """
Inlines CSS from `<style>` tags into element `style` attributes.
Returns the inlined HTML on success, or raises an exception on failure.
## Options
* `:inline_style_tags` - Whether to inline CSS from `<style>` tags. Defaults to `true`.
* `:keep_style_tags` - Whether to keep `<style>` tags after inlining. Defaults to `false`.
* `:keep_link_tags` - Whether to keep `<link>` tags after processing. Defaults to `false`.
* `:load_remote_stylesheets` - Whether to load remote stylesheets. Defaults to `true`.
* `:minify_css` - Whether to minify the inlined CSS. Defaults to `true`.
* `:check_depth` - Whether to check HTML nesting depth before inlining. Defaults to `true`.
* `:max_depth` - Maximum allowed HTML nesting depth. Defaults to `128`.
"""
@spec inline!(String.t(), [option()]) :: String.t()
def inline!(html, opts \\ []) when is_binary(html) and is_list(opts) do
case inline(html, opts) do
{:ok, result} -> result
{:error, reason} -> raise "CSS inlining failed: #{inspect(reason)}"
end
end
end