Skip to content

feat: Add keep_at_rules option #485

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jul 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased]

### Added

- `keep_at_rules` option [#265](https://github.com/Stranger6667/css-inline/issues/265)

## [0.16.0] - 2025-07-16

### Added
Expand Down
19 changes: 17 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ fn main() -> css_inline::Result<()> {
- `inline_style_tags`. Specifies whether to inline CSS from "style" tags. Default: `true`
- `keep_style_tags`. Specifies whether to keep "style" tags after inlining. Default: `false`
- `keep_link_tags`. Specifies whether to keep "link" tags after inlining. Default: `false`
- `keep_at_rules`. Specifies whether to keep "at-rules" (starting with `@`) after inlining. Default: `false`
- `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: `None`
- `load_remote_stylesheets`. Specifies whether remote stylesheets should be loaded. Default: `true`
- `cache`. Specifies cache for external stylesheets. Default: `None`
Expand Down Expand Up @@ -157,7 +158,8 @@ The `data-css-inline="ignore"` attribute also allows you to skip `link` and `sty
```

Alternatively, you may keep `style` from being removed by using the `data-css-inline="keep"` attribute.
This is useful if you want to keep `@media` queries for responsive emails in separate `style` tags:
This is useful if you want to keep `@media` queries for responsive emails in separate `style` tags.
Such tags will be kept in the resulting HTML even if the `keep_style_tags` option is set to `false`.

```html
<head>
Expand All @@ -169,7 +171,20 @@ This is useful if you want to keep `@media` queries for responsive emails in sep
</body>
```

Such tags will be kept in the resulting HTML even if the `keep_style_tags` option is set to `false`.
Another possibility is to set `keep_at_rules` option to `true`. At-rules cannot be inlined into HTML therefore they
get removed by default. This is useful if you want to keep at-rules, e.g. `@media` queries for responsive emails in
separate `style` tags but inline any styles which can be inlined.
Such tags will be kept in the resulting HTML even if the `keep_style_tags` option is explicitly set to `false`.

```html
<head>
<!-- With keep_at_rules=true "color:blue" will get inlined into <h1> but @media will be kept in <style> -->
<style>h1 { color: blue; } @media (max-width: 600px) { h1 { font-size: 18px; } }</style>
</head>
<body>
<h1>Big Text</h1>
</body>
```

If you'd like to load stylesheets from your filesystem, use the `file://` scheme:

Expand Down
4 changes: 4 additions & 0 deletions bindings/c/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased]

### Added

- `keep_at_rules` option [#265](https://github.com/Stranger6667/css-inline/issues/265)

## [0.16.0] - 2025-07-16

### Changed
Expand Down
19 changes: 17 additions & 2 deletions bindings/c/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ Possible configurations:
- `inline_style_tags`. Specifies whether to inline CSS from "style" tags. Default: `true`
- `keep_style_tags`. Specifies whether to keep "style" tags after inlining. Default: `false`
- `keep_link_tags`. Specifies whether to keep "link" tags after inlining. Default: `false`
- `keep_at_rules`. Specifies whether to keep "at-rules" (starting with `@`) after inlining. Default: `false`
- `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: `NULL`
- `load_remote_stylesheets`. Specifies whether remote stylesheets should be loaded. Default: `true`
- `cache`. Specifies caching options for external stylesheets. Default: `NULL`
Expand Down Expand Up @@ -186,7 +187,8 @@ The `data-css-inline="ignore"` attribute also allows you to skip `link` and `sty
```

Alternatively, you may keep `style` from being removed by using the `data-css-inline="keep"` attribute.
This is useful if you want to keep `@media` queries for responsive emails in separate `style` tags:
This is useful if you want to keep `@media` queries for responsive emails in separate `style` tags.
Such tags will be kept in the resulting HTML even if the `keep_style_tags` option is set to `false`.

```html
<head>
Expand All @@ -198,7 +200,20 @@ This is useful if you want to keep `@media` queries for responsive emails in sep
</body>
```

Such tags will be kept in the resulting HTML even if the `keep_style_tags` option is set to `false`.
Another possibility is to set `keep_at_rules` option to `true`. At-rules cannot be inlined into HTML therefore they
get removed by default. This is useful if you want to keep at-rules, e.g. `@media` queries for responsive emails in
separate `style` tags but inline any styles which can be inlined.
Such tags will be kept in the resulting HTML even if the `keep_style_tags` option is explicitly set to `false`.

```html
<head>
<!-- With keep_at_rules=true "color:blue" will get inlined into <h1> but @media will be kept in <style> -->
<style>h1 { color: blue; } @media (max-width: 600px) { h1 { font-size: 18px; } }</style>
</head>
<body>
<h1>Big Text</h1>
</body>
```

You can also cache external stylesheets to avoid excessive network requests:

Expand Down
4 changes: 4 additions & 0 deletions bindings/c/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ pub struct CssInlinerOptions {
pub keep_style_tags: bool,
/// Keep "link" tags after inlining.
pub keep_link_tags: bool,
/// Keep "at-rules" after inlining.
pub keep_at_rules: bool,
/// Whether remote stylesheets should be loaded or not.
pub load_remote_stylesheets: bool,
/// Cache for external stylesheets.
Expand Down Expand Up @@ -183,6 +185,7 @@ pub extern "C" fn css_inliner_default_options() -> CssInlinerOptions {
inline_style_tags: true,
keep_style_tags: false,
keep_link_tags: false,
keep_at_rules: false,
base_url: ptr::null(),
load_remote_stylesheets: true,
cache: std::ptr::null(),
Expand Down Expand Up @@ -225,6 +228,7 @@ impl TryFrom<&CssInlinerOptions> for InlineOptions<'_> {
inline_style_tags: value.inline_style_tags,
keep_style_tags: value.keep_style_tags,
keep_link_tags: value.keep_link_tags,
keep_at_rules: value.keep_at_rules,
base_url: match base_url {
Some(url) => Some(Url::parse(url).map_err(|_| InlineOptionsError::InvalidUrl)?),
None => None,
Expand Down
4 changes: 4 additions & 0 deletions bindings/java/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased]

### Added

- `keep_at_rules` option [#265](https://github.com/Stranger6667/css-inline/issues/265)

## [0.16.0] - 2025-07-16

### Changed
Expand Down
19 changes: 17 additions & 2 deletions bindings/java/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ public class ConfigExample {
- **`setInlineStyleTags(boolean)`** - Inline CSS from `<style>` tags (default: `true`)
- **`setKeepStyleTags(boolean)`** - Keep `<style>` tags after inlining (default: `false`)
- **`setKeepLinkTags(boolean)`** - Keep `<link>` tags after inlining (default: `false`)
- **`setKeepAtRules(boolean`** - Keep `at-rules` (starting with `@`) after inlining (default: `false`)
- **`setBaseUrl(String)`** - Base URL for resolving relative URLs (default: `null`)
- **`setLoadRemoteStylesheets(boolean)`** - Load external stylesheets (default: `true`)
- **`setExtraCss(String)`** - Additional CSS to inline (default: `null`)
Expand Down Expand Up @@ -215,7 +216,8 @@ The `data-css-inline="ignore"` attribute also allows you to skip `link` and `sty
```

Alternatively, you may keep `style` from being removed by using the `data-css-inline="keep"` attribute.
This is useful if you want to keep `@media` queries for responsive emails in separate `style` tags:
This is useful if you want to keep `@media` queries for responsive emails in separate `style` tags.
Such tags will be kept in the resulting HTML even if the `keep_style_tags` option is set to `false`.

```html
<head>
Expand All @@ -227,7 +229,20 @@ This is useful if you want to keep `@media` queries for responsive emails in sep
</body>
```

Such tags will be kept in the resulting HTML even if the `keepStyleTags` option is set to `false`.
Another possibility is to set `keep_at_rules` option to `true`. At-rules cannot be inlined into HTML therefore they
get removed by default. This is useful if you want to keep at-rules, e.g. `@media` queries for responsive emails in
separate `style` tags but inline any styles which can be inlined.
Such tags will be kept in the resulting HTML even if the `keep_style_tags` option is explicitly set to `false`.

```html
<head>
<!-- With keep_at_rules=true "color:blue" will get inlined into <h1> but @media will be kept in <style> -->
<style>h1 { color: blue; } @media (max-width: 600px) { h1 { font-size: 18px; } }</style>
</head>
<body>
<h1>Big Text</h1>
</body>
```

## Performance

Expand Down
20 changes: 18 additions & 2 deletions bindings/java/src/main/java/org/cssinline/CssInlineConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ public class CssInlineConfig {
/** Keep "link" tags after inlining. */
public final boolean keepLinkTags;

/** Keep "at-rules" after inlining. */
public final boolean keepAtRules;

/** Whether remote stylesheets should be loaded or not. */
public final boolean loadRemoteStylesheets;

Expand All @@ -27,11 +30,12 @@ public class CssInlineConfig {
public final int preallocateNodeCapacity;

private CssInlineConfig(boolean inlineStyleTags, boolean keepStyleTags, boolean keepLinkTags,
boolean loadRemoteStylesheets, String baseUrl, String extraCss, int cacheSize,
boolean keepAtRules, boolean loadRemoteStylesheets, String baseUrl, String extraCss, int cacheSize,
int preallocateNodeCapacity) {
this.inlineStyleTags = inlineStyleTags;
this.keepStyleTags = keepStyleTags;
this.keepLinkTags = keepLinkTags;
this.keepAtRules = keepAtRules;
this.loadRemoteStylesheets = loadRemoteStylesheets;
this.baseUrl = baseUrl;
this.extraCss = extraCss;
Expand All @@ -46,6 +50,7 @@ public static class Builder {
private boolean inlineStyleTags = true;
private boolean keepStyleTags = false;
private boolean keepLinkTags = false;
private boolean keepAtRules = false;
private boolean loadRemoteStylesheets = true;
private String baseUrl = null;
private String extraCss = null;
Expand Down Expand Up @@ -94,6 +99,17 @@ public Builder setKeepLinkTags(boolean b) {
return this;
}

/**
* Keep "at-rules" after inlining.
*
* @param b true to preserve at-rules, false to remove them
* @return this builder instance for method chaining
*/
public Builder setKeepAtRules(boolean b) {
this.keepAtRules = b;
return this;
}

/**
* Whether remote stylesheets should be loaded or not.
*
Expand Down Expand Up @@ -168,7 +184,7 @@ public Builder setPreallocateNodeCapacity(int cap) {
* @return a new immutable configuration instance
*/
public CssInlineConfig build() {
return new CssInlineConfig(inlineStyleTags, keepStyleTags, keepLinkTags, loadRemoteStylesheets, baseUrl,
return new CssInlineConfig(inlineStyleTags, keepStyleTags, keepLinkTags, keepAtRules, loadRemoteStylesheets, baseUrl,
extraCss, cacheSize, preallocateNodeCapacity);
}
}
Expand Down
2 changes: 2 additions & 0 deletions bindings/java/src/main/rust/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ fn build_inliner(
let inline_style_tags = env.get_bool_field(&cfg, "inlineStyleTags")?;
let keep_style_tags = env.get_bool_field(&cfg, "keepStyleTags")?;
let keep_link_tags = env.get_bool_field(&cfg, "keepLinkTags")?;
let keep_at_rules = env.get_bool_field(&cfg, "keepAtRules")?;
let load_remote_stylesheets = env.get_bool_field(&cfg, "loadRemoteStylesheets")?;
let cache_size = env.get_int_field(&cfg, "cacheSize")?;
let preallocate_node_capacity = env.get_int_field(&cfg, "preallocateNodeCapacity")?;
Expand All @@ -84,6 +85,7 @@ fn build_inliner(
.inline_style_tags(inline_style_tags)
.keep_style_tags(keep_style_tags)
.keep_link_tags(keep_link_tags)
.keep_at_rules(keep_at_rules)
.load_remote_stylesheets(load_remote_stylesheets)
.extra_css(extra_css.map(Cow::Owned))
.preallocate_node_capacity(preallocate_node_capacity as usize);
Expand Down
4 changes: 4 additions & 0 deletions bindings/javascript/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased]

### Added

- `keep_at_rules` option [#265](https://github.com/Stranger6667/css-inline/issues/265)

## [0.16.0] - 2025-07-16

### Changed
Expand Down
19 changes: 17 additions & 2 deletions bindings/javascript/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ var inlined = inlineFragment(
- `inlineStyleTags`. Specifies whether to inline CSS from "style" tags. Default: `true`
- `keepStyleTags`. Specifies whether to keep "style" tags after inlining. Default: `false`
- `keepLinkTags`. Specifies whether to keep "link" tags after inlining. Default: `false`
- `keep_at_rules`. Specifies whether to keep "at-rules" (starting with `@`) after inlining. Default: `false`
- `baseUrl`. The base URL used to resolve relative URLs. If you'd like to load stylesheets from your filesystem, use the `file://` scheme. Default: `null`
- `loadRemoteStylesheets`. Specifies whether remote stylesheets should be loaded. Default: `true`
- `cache`. Specifies caching options for external stylesheets (for example, `{size: 5}`). Default: `null`
Expand Down Expand Up @@ -148,7 +149,8 @@ The `data-css-inline="ignore"` attribute also allows you to skip `link` and `sty
```

Alternatively, you may keep `style` from being removed by using the `data-css-inline="keep"` attribute.
This is useful if you want to keep `@media` queries for responsive emails in separate `style` tags:
This is useful if you want to keep `@media` queries for responsive emails in separate `style` tags.
Such tags will be kept in the resulting HTML even if the `keep_style_tags` option is set to `false`.

```html
<head>
Expand All @@ -160,7 +162,20 @@ This is useful if you want to keep `@media` queries for responsive emails in sep
</body>
```

Such tags will be kept in the resulting HTML even if the `keep_style_tags` option is set to `false`.
Another possibility is to set `keep_at_rules` option to `true`. At-rules cannot be inlined into HTML therefore they
get removed by default. This is useful if you want to keep at-rules, e.g. `@media` queries for responsive emails in
separate `style` tags but inline any styles which can be inlined.
Such tags will be kept in the resulting HTML even if the `keep_style_tags` option is explicitly set to `false`.

```html
<head>
<!-- With keep_at_rules=true "color:blue" will get inlined into <h1> but @media will be kept in <style> -->
<style>h1 { color: blue; } @media (max-width: 600px) { h1 { font-size: 18px; } }</style>
</head>
<body>
<h1>Big Text</h1>
</body>
```

You can also cache external stylesheets to avoid excessive network requests:

Expand Down
1 change: 1 addition & 0 deletions bindings/javascript/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ const INLINE: &'static str = r#"export interface InlineOptions {
inlineStyleTags?: boolean,
keepStyleTags?: boolean,
keepLinkTags?: boolean,
keepAtRules?: boolean,
baseUrl?: string,
loadRemoteStylesheets?: boolean,
extraCss?: string,
Expand Down
3 changes: 3 additions & 0 deletions bindings/javascript/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ pub struct Options {
pub keep_style_tags: Option<bool>,
/// Keep "link" tags after inlining.
pub keep_link_tags: Option<bool>,
/// Keep "at-rules" after inlining.
pub keep_at_rules: Option<bool>,
/// Used for loading external stylesheets via relative URLs.
pub base_url: Option<String>,
/// Whether remote stylesheets should be loaded or not.
Expand All @@ -62,6 +64,7 @@ impl TryFrom<Options> for css_inline::InlineOptions<'_> {
inline_style_tags: value.inline_style_tags.unwrap_or(true),
keep_style_tags: value.keep_style_tags.unwrap_or(false),
keep_link_tags: value.keep_link_tags.unwrap_or(false),
keep_at_rules: value.keep_at_rules.unwrap_or(false),
base_url: parse_url(value.base_url)?,
load_remote_stylesheets: value.load_remote_stylesheets.unwrap_or(true),
extra_css: value.extra_css.map(Cow::Owned),
Expand Down
3 changes: 2 additions & 1 deletion bindings/javascript/wasm/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// Generated by dts-bundle-generator v9.1.0
// Generated by dts-bundle-generator v9.5.1

/* tslint:disable */
/* eslint-disable */
export interface InlineOptions {
inlineStyleTags?: boolean;
keepStyleTags?: boolean;
keepLinkTags?: boolean;
keepAtRules?: boolean;
baseUrl?: string;
loadRemoteStylesheets?: boolean;
extraCss?: string;
Expand Down
6 changes: 2 additions & 4 deletions bindings/javascript/wasm/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,7 @@ function passStringToWasm0(arg, malloc, realloc) {
let offset = 0;
for (; offset < len; offset++) {
const code = arg.charCodeAt(offset);
if (code > 127)
break;
if (code > 127) break;
mem[ptr + offset] = code;
}
if (offset !== len) {
Expand Down Expand Up @@ -373,8 +372,7 @@ function __wbg_finalize_init(instance, module2) {
return wasm;
}
async function __wbg_init(module_or_path) {
if (wasm !== void 0)
return wasm;
if (wasm !== void 0) return wasm;
if (typeof module_or_path !== "undefined") {
if (Object.getPrototypeOf(module_or_path) === Object.prototype) {
({ module_or_path } = module_or_path);
Expand Down
6 changes: 2 additions & 4 deletions bindings/javascript/wasm/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,7 @@ function passStringToWasm0(arg, malloc, realloc) {
let offset = 0;
for (; offset < len; offset++) {
const code = arg.charCodeAt(offset);
if (code > 127)
break;
if (code > 127) break;
mem[ptr + offset] = code;
}
if (offset !== len) {
Expand Down Expand Up @@ -344,8 +343,7 @@ function __wbg_finalize_init(instance, module) {
return wasm;
}
async function __wbg_init(module_or_path) {
if (wasm !== void 0)
return wasm;
if (wasm !== void 0) return wasm;
if (typeof module_or_path !== "undefined") {
if (Object.getPrototypeOf(module_or_path) === Object.prototype) {
({ module_or_path } = module_or_path);
Expand Down
Binary file modified bindings/javascript/wasm/index_bg.wasm
Binary file not shown.
4 changes: 4 additions & 0 deletions bindings/python/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased]

### Added

- `keep_at_rules` option [#265](https://github.com/Stranger6667/css-inline/issues/265)

## [0.16.0] - 2025-07-16

### Changed
Expand Down
Loading
Loading