markup_css_once/
lib.rs

1//! # markup-css-once
2//!
3//! Render embedded styles only once per template with [Markup][] Rust template engine
4//!
5//! ## Usage
6//!
7//! Let's say we have a template we'd like to use on the page multiple times. We also have styles
8//! related to this template inside an embedded `<style>` tag.
9//!
10//! ```rust
11//! use markup_css_once::{CssOnce, css_once};
12//!
13//! markup::define! {
14//!     Hello<'a>(
15//!         css: &'a CssOnce,
16//!         name: &'a str,
17//!     ) {
18//!         @css_once!(css,
19//!             "p { background: blue }"
20//!             "b { color: yellow }"
21//!         )
22//!         p { "Hello, " b { @name } }
23//!     }
24//! }
25//!
26//! // We need an tracker for components with already rendered css
27//! let css = CssOnce::new();
28//!
29//! // The first time the template is rendered with styles
30//! assert_eq!(
31//!     Hello { css: &css, name: "World" }.to_string(),
32//!     "<style>p { background: blue }b { color: yellow }</style>\n<p>Hello, <b>World</b></p>"
33//! );
34//!
35//! // But all subsequent calls will render only it's HTML
36//! assert_eq!(
37//!     Hello { css: &css, name: "World" }.to_string(),
38//!     "<p>Hello, <b>World</b></p>"
39//! );
40//! ```
41//!
42//! [Markup]: https://github.com/utkarshkukreti/markup.rs
43
44#![warn(clippy::all, missing_docs, nonstandard_style, future_incompatible)]
45
46use std::any::type_name;
47use std::cell::Cell;
48use std::collections::HashSet;
49
50/// A tracker for rendered css
51#[derive(Default)]
52pub struct CssOnce(Cell<HashSet<&'static str>>);
53
54impl CssOnce {
55    /// Creates a new instance of the tracker
56    pub fn new() -> Self {
57        Self::default()
58    }
59
60    /// Checks if styles for template `T` is already rendered
61    pub fn is_rendered<T>(&self) -> bool {
62        let mut inner = self.0.take();
63        let inserted = inner.insert(type_name::<T>());
64        self.0.set(inner);
65        !inserted
66    }
67}
68
69/// A macro for the Markup templates to ensure the css is rendered only once
70#[macro_export]
71macro_rules! css_once {
72    ($css:ident, $($str:tt)+) => {
73        if CssOnce::is_rendered::<Self>($css) {
74            markup::raw("")
75        } else {
76            markup::raw(concat!("<style>", $($str),+, "</style>\n"))
77        }
78    };
79}