Skip to main content

sofie_engine/
asset.rs

1use sofie_core::Fnv1a64;
2
3use crate::render::push_display;
4
5#[cfg(feature = "compression")]
6mod precompressed;
7#[cfg(feature = "compression")]
8pub use precompressed::Precompressed;
9#[cfg(feature = "islands")]
10mod island_bundle;
11
12/// Served under its content hash, so a browser may cache it until the file changes.
13#[derive(Clone, Copy, Debug)]
14pub struct Asset {
15    stem: &'static str,
16    extension: &'static str,
17    pub bytes: &'static [u8],
18    pub content_type: &'static str,
19    hash: u64,
20    #[cfg(feature = "compression")]
21    precompressed: Option<Precompressed>,
22}
23
24/// Its compressed copies are the files beside it with `.br` and `.gz` added.
25#[cfg(feature = "compression")]
26#[doc(hidden)]
27#[macro_export]
28macro_rules! compressed_asset {
29    ($asset:expr, $file:expr) => {
30        $asset.with_precompressed($crate::Precompressed {
31            brotli: include_bytes!(concat!($file, ".br")),
32            gzip: include_bytes!(concat!($file, ".gz")),
33        })
34    };
35}
36
37#[cfg(not(feature = "compression"))]
38#[doc(hidden)]
39#[macro_export]
40macro_rules! compressed_asset {
41    ($asset:expr, $file:expr) => {
42        $asset
43    };
44}
45
46/// Compressed by the engine's build script.
47macro_rules! engine_asset {
48    ($stem:literal, $extension:literal, $content_type:expr) => {
49        $crate::compressed_asset!(
50            Asset::new(
51                $stem,
52                $extension,
53                include_bytes!(concat!("../assets/", $stem, ".", $extension)),
54                $content_type,
55            ),
56            concat!(env!("OUT_DIR"), "/", $stem, ".", $extension)
57        )
58    };
59}
60
61/// htmx, pinned; see `assets/PROVENANCE`.
62pub const CLIENT_SCRIPT: Asset = engine_asset!("htmx", "min.js", JAVASCRIPT);
63
64/// Field state htmx cannot keep, menus and panels, and a notice for an unanswered request.
65pub const ENGINE_SCRIPT: Asset = engine_asset!("engine", "js", JAVASCRIPT);
66
67pub const STYLESHEET: Asset = engine_asset!("sofie", "css", CSS);
68
69pub(crate) const JAVASCRIPT: &str = "application/javascript; charset=utf-8";
70pub(crate) const CSS: &str = "text/css; charset=utf-8";
71
72impl Asset {
73    pub(crate) const fn new(
74        stem: &'static str,
75        extension: &'static str,
76        bytes: &'static [u8],
77        content_type: &'static str,
78    ) -> Self {
79        Self {
80            stem,
81            extension,
82            bytes,
83            content_type,
84            hash: Fnv1a64::new().write(bytes).finish(),
85            #[cfg(feature = "compression")]
86            precompressed: None,
87        }
88    }
89
90    #[must_use]
91    pub fn path(&self) -> String {
92        let mut path = String::new();
93        self.push_path(&mut path);
94        path
95    }
96
97    pub(crate) fn push_path(&self, out: &mut String) {
98        let (stem, hash, extension) = (self.stem, self.hash, self.extension);
99        push_display(out, format_args!("/_sofie/{stem}-{hash:016x}.{extension}"));
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::{CLIENT_SCRIPT, STYLESHEET};
106
107    const PROVENANCE: &str = include_str!("../assets/PROVENANCE");
108
109    #[test]
110    fn the_client_script_is_the_file_recorded_in_provenance() {
111        let recorded = PROVENANCE
112            .lines()
113            .find_map(|line| line.strip_prefix("fnv1a64 "))
114            .expect("PROVENANCE records the fnv1a64 of htmx.min.js");
115
116        assert_eq!(
117            CLIENT_SCRIPT.path(),
118            format!("/_sofie/htmx-{recorded}.min.js")
119        );
120    }
121
122    #[test]
123    fn paths_name_the_content() {
124        let path = STYLESHEET.path();
125        assert_eq!(path.len(), "/_sofie/sofie-".len() + 16 + ".css".len());
126        assert!(
127            path.starts_with("/_sofie/sofie-") && path.contains(".css"),
128            "{path}"
129        );
130        assert_eq!(STYLESHEET.content_type, "text/css; charset=utf-8");
131    }
132}