askama-minify 0.3.4

A procedural macro for minifying Askama templates at compile time
Documentation
# Askama Minify

[简体中文](README.zh-CN.md)

A procedural macro crate that minifies Askama templates at compile time.

Starting with `0.3.0`, `askama-minify` no longer ships a CLI. It reads a template during compilation, minifies HTML and inline CSS/JavaScript, and injects the result as Askama's `#[template(source = "...", ext = "...")]`.

Important: place `#[template_minify(...)]` above `#[derive(Template)]` so the macro can generate Askama's `#[template(...)]` attribute before the derive macro runs.

## Usage

```rust
use askama::Template;
use askama_minify::template_minify;

#[template_minify(path = "index.html")]
#[derive(Template)]
struct IndexTemplate<'a> {
    title: &'a str,
}
```

Path resolution:

- directories configured through Askama's `[general].dirs`
- `CARGO_MANIFEST_DIR/templates/<path>` when no custom directories are configured
- `CARGO_MANIFEST_DIR/<path>` as a compatibility fallback

This means the common Askama layout can use the short path directly:

```rust
#[template_minify(path = "index.html")]
#[derive(Template)]
struct IndexTemplate;
```

With the corresponding file:

```text
templates/index.html
```

## Inline Templates

```rust
use askama::Template;
use askama_minify::template_minify;

#[template_minify(source = "<h1>{{ title }}</h1>", ext = "html")]
#[derive(Template)]
struct InlineTemplate<'a> {
    title: &'a str,
}
```

When `source` is used, `ext` is required.

## Forwarded Askama Arguments

`path`, `source`, and `ext` are handled by `askama-minify`. Any other arguments are forwarded to Askama's `#[template(...)]` attribute:

```rust
#[template_minify(path = "page.html", escape = "none")]
#[derive(Template)]
struct PageTemplate;
```

This expands to a minified source template:

```rust
#[template(source = "...", ext = "html", escape = "none")]
```

## Notes

- Template files and their `include`, `extends`, and `import` dependencies are tracked through `include_str!`, so Cargo rebuilds when any source template changes.
- Askama config files are tracked too. Template dependencies are resolved relative to the source template, recursively minified, and passed to Askama through isolated generated files.
- Included HTML fragments retain boundary whitespace. If a dependency graph inserts fragments into whitespace-sensitive or raw-text contexts (`pre`, `textarea`, `script`, `style`, or attributes), or contains incomplete markup whose context cannot be determined, its templates are preserved conservatively. This also protects inherited blocks and imported macros in these contexts.
- `html` and `htm` templates are minified as HTML. JavaScript without Askama syntax is parsed before it is minified; scripts containing Askama syntax or unsupported JavaScript are preserved unchanged.
- JavaScript and advanced CSS minification fall back to the original content when the result could introduce an HTML end tag. Askama strings, nested comments, raw blocks, quoted HTML attributes, and non-ASCII whitespace are preserved.
- The built-in CSS minifier preserves stylesheets containing custom-property syntax unchanged because custom-property token whitespace can be significant.
- Necessary CSS comment boundaries are retained as empty comments rather than changed into selector whitespace.
- Template file contents and compressed results are cached within each compiler process. File metadata changes invalidate the file cache; all original dependencies remain tracked, and identical generated files are not rewritten.

## Features

The default features preserve the full behavior:

- `askama-config`: reads `[general].dirs` from `askama.toml`, using the lightweight `basic-toml` parser. Parsed config and glob results are cached within each compiler process.
- `js-minify`: enables parser-backed JavaScript minification. Disable it to preserve inline JavaScript unchanged and avoid compiling the JavaScript parser.
- `advanced-css`: uses `lightningcss` for fuller CSS minification. This is opt-in because it adds a substantially larger compiler dependency graph.

For the smallest dependency graph, disable default features. In this mode template paths use the default `templates/` directory (plus the manifest-root compatibility fallback), and inline JavaScript is preserved:

```toml
askama-minify = { version = "0.3", default-features = false }
```

Enable advanced CSS minification with:

```toml
askama-minify = { version = "0.3", features = ["advanced-css"] }
```

- CSS containing Askama syntax still falls back to the built-in minifier to avoid failed CSS parser work during compilation.
- Non-HTML extensions are injected unchanged as Askama `source` templates.

## Architecture

`askama-minify` is split into small modules around the procedural macro pipeline:

- `src/lib.rs`: proc-macro entry point. It parses the attribute and target item, then delegates expansion.
- `src/args.rs`: parses `path`, `source`, `ext`, and collects extra Askama arguments for forwarding.
- `src/item.rs`: parses the target derive item and rejects an existing `#[template(...)]` attribute.
- `src/loader.rs`: reads Askama config, recursively resolves and minifies template dependencies, and writes isolated generated templates for Askama.
- `src/expand.rs`: builds the generated `#[template(source = "...", ext = "...")]` attribute and adds `include_str!` tracking for template files.
- `src/minifier.rs`: public internal entry for HTML minification.
- `src/minifier/html.rs`: HTML scanner that preserves Askama syntax and delegates inline `<style>` and `<script>` content.
- `src/minifier/css.rs`: CSS minification. It uses the built-in conservative minifier by default and `lightningcss` when `advanced-css` is enabled.
- `src/minifier/js.rs`: parser-backed JavaScript minification with a lossless fallback for Askama syntax and unsupported JavaScript.
- `src/minifier/template.rs`: shared Askama block copier for `{{ ... }}`, `{% ... %}`, and `{# ... #}`.
- `src/minifier/util.rs`: shared string trimming helpers.

The expansion flow is:

```text
template_minify attribute
  -> parse MacroArgs
  -> parse TemplateItem
  -> load or read source template
  -> recursively resolve and minify template dependencies
  -> inject Askama #[template(source = "...", ext = "...")]
  -> emit include_str! tracking for source template files
```

## Compile-time Benchmark

Run `scripts/benchmark-compile.sh` to measure cold `cargo check` time and peak memory for the minimal, default, and all-features dependency graphs. Each measurement uses an isolated temporary target directory and excludes dependency download time.

To measure dependency scanning separately from compiler startup and dependency builds:

```sh
cargo test --release --lib --locked benchmark_template_scanning -- --ignored --nocapture
```