or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

index.md

index.mddocs/

0

# CSS-Inline

1

2

CSS-Inline is a high-performance Rust library for inlining CSS into HTML 'style' attributes. It transforms HTML documents by moving CSS rules from `<style>` and `<link>` tags directly into the style attributes of matching HTML elements, making it ideal for preparing HTML emails or embedding HTML into third-party web pages.

3

4

## Package Information

5

6

- **Package Name**: css-inline

7

- **Package Type**: cargo

8

- **Language**: Rust

9

- **Installation**: `cargo add css-inline` or add `css-inline = "0.17"` to `Cargo.toml`

10

- **Minimum Rust Version**: 1.75

11

12

## Core Imports

13

14

```rust

15

use css_inline::{inline, inline_fragment, CSSInliner, InlineOptions, Result};

16

use std::io::Write; // Required for *_to functions

17

use std::borrow::Cow; // Required for extra_css

18

```

19

20

For URL handling:

21

```rust

22

use css_inline::Url;

23

```

24

25

For error handling:

26

```rust

27

use css_inline::InlineError;

28

```

29

30

For custom resolvers:

31

```rust

32

use css_inline::{StylesheetResolver, DefaultStylesheetResolver};

33

use std::sync::Arc;

34

```

35

36

For caching (requires `stylesheet-cache` feature):

37

```rust

38

use css_inline::StylesheetCache;

39

use std::num::NonZeroUsize;

40

use std::sync::Mutex; // Required for cache configuration

41

```

42

43

## Architecture

44

45

CSS-Inline follows a modular, pipeline-based architecture designed for high performance:

46

47

**Core Components:**

48

- **CSSInliner**: Main entry point providing configured inlining with options

49

- **InlineOptions**: Builder pattern configuration for customizing behavior

50

- **StylesheetResolver**: Trait-based system for loading stylesheets from various sources

51

- **Document Parser**: HTML5 parser that builds an internal DOM representation

52

- **CSS Parser**: Robust CSS3 parser using Mozilla's cssparser crate

53

- **Selector Engine**: CSS selector matching using Mozilla's selectors crate

54

55

**Processing Pipeline:**

56

1. **Parse HTML**: Document parsed into internal representation with pre-allocated capacity

57

2. **Collect Styles**: Gathers CSS from `<style>` tags, `<link>` tags, and external sources

58

3. **Parse CSS**: CSS parsed into rules with specificity calculation

59

4. **Match Selectors**: Rules matched to elements using selector engine with caching

60

5. **Resolve Conflicts**: Higher specificity and `!important` rules take precedence

61

6. **Inline Styles**: Matched styles merged into element `style` attributes

62

7. **Serialize Output**: Final HTML serialized with inlined styles

63

64

**Key Design Patterns:**

65

- **Builder Pattern**: `InlineOptions` for flexible configuration

66

- **Strategy Pattern**: `StylesheetResolver` trait for pluggable stylesheet loading

67

- **Caching**: Optional LRU cache for external stylesheets to avoid redundant requests

68

- **Error Handling**: Comprehensive error types with source error chaining

69

70

## Basic Usage

71

72

### Simple CSS Inlining

73

74

```rust

75

use css_inline::inline;

76

77

fn main() -> css_inline::Result<()> {

78

let html = r#"<html>

79

<head>

80

<style>h1 { color:blue; }</style>

81

</head>

82

<body>

83

<h1>Big Text</h1>

84

</body>

85

</html>"#;

86

87

let inlined = inline(html)?;

88

println!("{}", inlined);

89

Ok(())

90

}

91

```

92

93

### Fragment Inlining

94

95

```rust

96

use css_inline::inline_fragment;

97

98

fn main() -> css_inline::Result<()> {

99

let fragment = r#"<main>

100

<h1>Hello</h1>

101

<p>Content</p>

102

</main>"#;

103

104

let css = r#"

105

p { color: red; }

106

h1 { color: blue; }

107

"#;

108

109

let inlined = inline_fragment(fragment, css)?;

110

println!("{}", inlined);

111

Ok(())

112

}

113

```

114

115

## Capabilities

116

117

### Simple Inlining Functions

118

119

Convenience functions for basic CSS inlining with default configuration.

120

121

```rust { .api }

122

fn inline(html: &str) -> Result<String>;

123

fn inline_to<W: Write>(html: &str, target: &mut W) -> Result<()>;

124

fn inline_fragment(html: &str, css: &str) -> Result<String>;

125

fn inline_fragment_to<W: Write>(html: &str, css: &str, target: &mut W) -> Result<()>;

126

```

127

128

**Parameters:**

129

- `html: &str` - HTML document or fragment to process

130

- `css: &str` - CSS to inline (for fragment functions)

131

- `target: &mut W` - Writer to output the result to

132

133

**Returns:**

134

- `Result<String>` - Inlined HTML as string

135

- `Result<()>` - Success/failure for writer functions

136

137

### Configuration Options

138

139

The `InlineOptions` struct provides a builder pattern for configuring CSS inlining behavior.

140

141

```rust { .api }

142

impl InlineOptions<'a> {

143

fn inline_style_tags(self, inline_style_tags: bool) -> Self;

144

fn keep_style_tags(self, keep_style_tags: bool) -> Self;

145

fn keep_link_tags(self, keep_link_tags: bool) -> Self;

146

fn keep_at_rules(self, keep_at_rules: bool) -> Self;

147

fn base_url(self, base_url: Option<Url>) -> Self;

148

fn load_remote_stylesheets(self, load_remote_stylesheets: bool) -> Self;

149

fn extra_css(self, extra_css: Option<Cow<'a, str>>) -> Self;

150

fn preallocate_node_capacity(self, preallocate_node_capacity: usize) -> Self;

151

fn resolver(self, resolver: Arc<dyn StylesheetResolver>) -> Self;

152

fn build(self) -> CSSInliner<'a>;

153

}

154

```

155

156

**Configuration Options:**

157

- `inline_style_tags: bool` - Whether to inline CSS from "style" tags (default: `true`)

158

- `keep_style_tags: bool` - Keep "style" tags after inlining (default: `false`)

159

- `keep_link_tags: bool` - Keep "link" tags after inlining (default: `false`)

160

- `keep_at_rules: bool` - Keep "at-rules" after inlining (default: `false`)

161

- `base_url: Option<Url>` - Base URL for resolving relative URLs (default: `None`)

162

- `load_remote_stylesheets: bool` - Whether remote stylesheets should be loaded (default: `true`)

163

- `extra_css: Option<Cow<'a, str>>` - Additional CSS to inline (default: `None`)

164

- `preallocate_node_capacity: usize` - Pre-allocate capacity for HTML nodes (default: `32`)

165

- `resolver: Arc<dyn StylesheetResolver>` - Custom stylesheet resolver (default: `DefaultStylesheetResolver`)

166

167

#### Feature-Gated Configuration (requires `stylesheet-cache` feature)

168

169

```rust { .api }

170

impl InlineOptions<'a> {

171

fn cache(self, cache: impl Into<Option<StylesheetCache>>) -> Self;

172

}

173

```

174

175

- `cache: Option<StylesheetCache>` - External stylesheet cache (default: `None`)

176

177

### Configurable CSS Inliner

178

179

The main `CSSInliner` struct provides the core inlining functionality with customizable options.

180

181

```rust { .api }

182

impl CSSInliner<'a> {

183

fn new(options: InlineOptions<'a>) -> Self;

184

fn options() -> InlineOptions<'a>;

185

fn inline(&self, html: &str) -> Result<String>;

186

fn inline_to<W: Write>(&self, html: &str, target: &mut W) -> Result<()>;

187

fn inline_fragment(&self, html: &str, css: &str) -> Result<String>;

188

fn inline_fragment_to<W: Write>(&self, html: &str, css: &str, target: &mut W) -> Result<()>;

189

}

190

```

191

192

**Usage Example:**

193

```rust

194

use css_inline::{CSSInliner, Url};

195

196

let inliner = CSSInliner::options()

197

.load_remote_stylesheets(false)

198

.keep_style_tags(true)

199

.base_url(Some(Url::parse("https://example.com")?))

200

.build();

201

202

let result = inliner.inline(html)?;

203

```

204

205

### Custom Stylesheet Resolvers

206

207

Implement custom logic for loading stylesheets from various sources.

208

209

```rust { .api }

210

trait StylesheetResolver: Send + Sync {

211

fn retrieve(&self, location: &str) -> Result<String>;

212

fn retrieve_from_url(&self, url: &str) -> Result<String>;

213

fn retrieve_from_path(&self, path: &str) -> Result<String>;

214

fn unsupported(&self, reason: &str) -> InlineError;

215

}

216

```

217

218

**Default Implementation:**

219

```rust { .api }

220

struct DefaultStylesheetResolver;

221

```

222

223

**Usage Example:**

224

```rust

225

use css_inline::{StylesheetResolver, Result, InlineError};

226

use std::sync::Arc;

227

228

#[derive(Debug, Default)]

229

struct CustomResolver;

230

231

impl StylesheetResolver for CustomResolver {

232

fn retrieve(&self, location: &str) -> Result<String> {

233

// Custom implementation

234

if location.starts_with("custom://") {

235

Ok("/* custom CSS */".to_string())

236

} else {

237

Err(self.unsupported("Only custom:// URLs supported"))

238

}

239

}

240

}

241

242

let inliner = css_inline::CSSInliner::options()

243

.resolver(Arc::new(CustomResolver))

244

.build();

245

```

246

247

### Caching (Feature-Gated)

248

249

External stylesheet caching to avoid redundant network requests (requires `stylesheet-cache` feature).

250

251

```rust { .api }

252

type StylesheetCache<S = DefaultHasher> = LruCache<String, String, S>;

253

```

254

255

**Usage Example:**

256

```rust

257

use std::num::NonZeroUsize;

258

259

#[cfg(feature = "stylesheet-cache")]

260

let inliner = css_inline::CSSInliner::options()

261

.cache(css_inline::StylesheetCache::new(

262

NonZeroUsize::new(10).unwrap()

263

))

264

.build();

265

```

266

267

## Types

268

269

### Result Type

270

271

```rust { .api }

272

type Result<T> = std::result::Result<T, InlineError>;

273

```

274

275

### Error Types

276

277

```rust { .api }

278

enum InlineError {

279

MissingStyleSheet { path: String },

280

IO(std::io::Error),

281

#[cfg(feature = "http")]

282

Network { error: reqwest::Error, location: String },

283

ParseError(Cow<'static, str>),

284

}

285

```

286

287

**Error Variants:**

288

- `MissingStyleSheet` - Stylesheet file not found

289

- `IO` - File system or I/O error

290

- `Network` - Network-related error (requires `http` feature)

291

- `ParseError` - CSS parsing or selector error

292

293

### Configuration Structures

294

295

```rust { .api }

296

struct InlineOptions<'a> {

297

pub inline_style_tags: bool,

298

pub keep_style_tags: bool,

299

pub keep_link_tags: bool,

300

pub keep_at_rules: bool,

301

pub base_url: Option<Url>,

302

pub load_remote_stylesheets: bool,

303

#[cfg(feature = "stylesheet-cache")]

304

pub cache: Option<Mutex<StylesheetCache>>,

305

pub extra_css: Option<Cow<'a, str>>,

306

pub preallocate_node_capacity: usize,

307

pub resolver: Arc<dyn StylesheetResolver>,

308

}

309

310

struct CSSInliner<'a> {

311

options: InlineOptions<'a>,

312

}

313

314

// Both implement Default

315

impl Default for InlineOptions<'_>;

316

impl Default for CSSInliner<'_>;

317

```

318

319

### Re-exported Types

320

321

```rust { .api }

322

// From url crate

323

struct Url;

324

enum ParseError;

325

```

326

327

## HTML Attributes

328

329

CSS-Inline recognizes special HTML attributes for controlling inlining behavior:

330

331

- `data-css-inline="ignore"` - Skip CSS inlining for this element or skip processing of `<style>`/`<link>` tags

332

- `data-css-inline="keep"` - Keep `<style>` tags even when `keep_style_tags` is `false`

333

334

**Example:**

335

```html

336

<style data-css-inline="ignore">h1 { color: blue; }</style>

337

<h1 data-css-inline="ignore">Not styled</h1>

338

<style data-css-inline="keep">@media (max-width: 600px) { h1 { font-size: 18px; } }</style>

339

```

340

341

## Feature Flags

342

343

CSS-Inline provides optional feature flags:

344

345

- `http` (default) - Enables remote stylesheet loading via HTTP/HTTPS

346

- `file` (default) - Enables local file stylesheet loading

347

- `stylesheet-cache` (default) - Enables external stylesheet caching

348

- `cli` (default) - Enables command-line interface

349

- `default` - Includes all above features

350

351

**Usage in Cargo.toml:**

352

```toml

353

[dependencies]

354

css-inline = { version = "0.17", default-features = false, features = ["http"] }

355

```