Skip to main content

rustolio_build_utils/
lib.rs

1//
2// SPDX-License-Identifier: MPL-2.0
3//
4// Copyright (c) 2026 Tobias Binnewies. All rights reserved.
5//
6// This Source Code Form is subject to the terms of the Mozilla Public
7// License, v. 2.0. If a copy of the MPL was not distributed with this
8// file, You can obtain one at http://mozilla.org/MPL/2.0/.
9//
10
11mod fs;
12mod paths;
13mod tailwind;
14
15use std::env;
16
17pub use rustolio_utils::{prelude::*, Error, Result};
18
19pub fn handle_lib() -> Result<()> {
20    if !is_wasm_build()? {
21        return Ok(());
22    }
23    tailwind::push_info()?;
24    fs::copy(
25        &paths::static_dir()?,
26        &paths::web_build_dir()?.join(cargo_pkg_name()?),
27    )?;
28    Ok(())
29}
30
31pub fn handle_bin() -> Result<()> {
32    if !is_wasm_build()? {
33        return Ok(());
34    }
35    force_rebuild()?; // To always run tailwind
36    create_default_static_files()?;
37    tailwind::execute()?;
38    fs::copy(&paths::web_build_dir()?, &paths::pkg_dir()?)?;
39    fs::copy(&paths::static_dir()?, &paths::pkg_dir()?)?;
40    Ok(())
41}
42
43fn create_default_static_files() -> Result<()> {
44    let static_dir = paths::static_dir()?;
45
46    let index_html = default_index_html()?;
47    fs::create(&static_dir.join("index.html"), &index_html)?;
48
49    Ok(())
50}
51
52fn default_index_html() -> Result<String> {
53    const INDEX_CONTENT: &str = r#"
54<!DOCTYPE html>
55<html lang="en-US">
56  <head>
57    <meta charset="utf-8" />
58    <title>{PRETTY_PKG_NAME}</title>
59    {LINKS}
60  </head>
61  <body>
62    <div id="root"></div>
63    <script type="module">
64      import init, { entry } from "/./{PKG_NAME}.js"
65      init().then(() => {
66        entry()
67      })
68    </script>
69  </body>
70</html>
71"#;
72    const TAILWIND_LINK: &str = r#"<link rel="stylesheet" href="/./tailwind.css" />"#;
73
74    let mut links = Vec::new();
75    if tailwind::enabled()? {
76        links.push(TAILWIND_LINK.to_string());
77    }
78    let links = links.join("\n    ");
79
80    Ok(INDEX_CONTENT
81        .replace("{LINKS}", &links)
82        .replace("{PRETTY_PKG_NAME}", &pretty_pkg_name()?)
83        .replace("{PKG_NAME}", &cargo_pkg_name()?.replace("-", "_")))
84}
85
86fn is_wasm_build() -> Result<bool> {
87    Ok(env::var("CARGO_CFG_TARGET_ARCH").context("CARGO_CFG_TARGET_ARCH not set")? == "wasm32")
88}
89
90/// E.g. "my_cool_app" -> "My Cool App"
91fn pretty_pkg_name() -> Result<String> {
92    Ok(cargo_pkg_name()?
93        .replace("_", " ")
94        .replace("-", " ")
95        .split(' ')
96        .map(|s| {
97            let mut c = s.chars();
98            match c.next() {
99                None => String::new(),
100                Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
101            }
102        })
103        .collect::<Vec<_>>()
104        .join(" "))
105}
106
107fn cargo_pkg_name() -> Result<String> {
108    env::var("CARGO_PKG_NAME").context("CARGO_PKG_NAME not set")
109}
110
111fn force_rebuild() -> Result<()> {
112    let dummy_timestamp = paths::out_dir()?.join("dummy.timestamp");
113
114    // Always rerun when this dummy file changes
115    println!(
116        "cargo:rerun-if-changed={}",
117        dummy_timestamp
118            .to_str()
119            .context("Failed to convert to str")?
120    );
121
122    // Update the timestamp
123    let timestamp = std::time::SystemTime::now()
124        .duration_since(std::time::UNIX_EPOCH)
125        .unwrap()
126        .as_secs();
127    std::fs::write(dummy_timestamp, timestamp.to_string()).context("Failed to write dummy file")?;
128    Ok(())
129}