1use anyhow::Result;
2use serde_json;
3
4#[cfg(feature = "pwa")]
5mod pwa;
6#[cfg(feature = "pwa")]
7pub use pwa::*;
8
9#[cfg(feature = "typescript")]
10mod swc_bundler;
11#[cfg(feature = "typescript")]
12mod typescript {
13 use std::collections::HashMap;
14 use serde_json;
15
16 pub fn bundle_ts() {
17 let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_default();
19 println!("cargo:warning=DEBUG: CARGO_MANIFEST_DIR = {}", manifest_dir);
20
21 let is_publishing = std::env::var("CARGO_PUBLISH").is_ok()
22 || std::env::var("DOCS_RS").is_ok()
23 || std::env::var("PREST_OFFLINE").is_ok()
24 || manifest_dir.contains("/target/package/"); println!("cargo:warning=DEBUG: is_publishing = {}", is_publishing);
27 println!("cargo:warning=DEBUG: CARGO_PUBLISH = {:?}", std::env::var("CARGO_PUBLISH"));
28 println!("cargo:warning=DEBUG: DOCS_RS = {:?}", std::env::var("DOCS_RS"));
29 println!("cargo:warning=DEBUG: PREST_OFFLINE = {:?}", std::env::var("PREST_OFFLINE"));
30 println!("cargo:warning=DEBUG: manifest_dir.contains('/target/package/') = {}", manifest_dir.contains("/target/package/"));
31
32 let (exports, custom_node_modules_path) = if is_publishing {
33 let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR should be set in build context");
35 println!("cargo:warning=Publishing mode detected. Using OUT_DIR for NPM packages: {}", out_dir);
36
37 let exports = match std::env::var("PREST_PREBUILT_EXPORTS") {
39 Ok(exports_json) => {
40 serde_json::from_str(&exports_json).unwrap_or_else(|_| {
41 println!("cargo:warning=Failed to parse PREST_PREBUILT_EXPORTS, using minimal fallback");
42 create_minimal_exports()
43 })
44 },
45 Err(_) => {
46 let rt = tokio::runtime::Runtime::new().expect("Failed to create tokio runtime");
48 match rt.block_on(prest_npm::run_with_path(&out_dir)) {
49 Ok(exports) => exports,
50 Err(e) => {
51 println!("cargo:warning=NPM download failed in publishing mode: {}", e);
52 println!("cargo:warning=Using minimal fallback exports");
53 create_minimal_exports()
54 }
55 }
56 }
57 };
58 (exports, Some(out_dir))
59 } else {
60 println!("cargo:warning=Development mode detected. Using standard NPM path");
62 (prest_npm::run().unwrap(), None::<String>)
63 };
64
65 for (name, path) in exports.iter() {
67 let result = if let Some(custom_path) = &custom_node_modules_path {
68 crate::swc_bundler::bundle_js_with_node_modules(path, name, Some(custom_path))
69 } else {
70 crate::swc_bundler::bundle_js(path, name)
71 };
72
73 if let Err(e) = result {
74 println!("cargo:warning=Failed to bundle {}: {}", name, e);
75 }
76 }
77 }
78
79 fn create_minimal_exports() -> HashMap<String, String> {
80 let mut exports = HashMap::new();
81
82 exports.insert("htmx".to_string(), "ui/preset.ts".to_string());
85
86 exports
87 }
88}
89#[cfg(feature = "typescript")]
90pub use typescript::bundle_ts;
91
92#[cfg(feature = "sass")]
93mod sass {
94 use std::{fs::write, path::Path};
95 pub fn bundle_sass(path: &str) -> anyhow::Result<()> {
96 let opts = grass::Options::default().style(grass::OutputStyle::Compressed);
97 let css = grass::from_path(path, &opts)?;
98 let scss_filename = Path::new(path).file_name().unwrap().to_str().unwrap();
99 let css_filename = scss_filename
100 .replace(".scss", ".css")
101 .replace(".sass", ".css");
102 let out_file = super::out_path(&css_filename);
103 write(out_file, css)?;
104 Ok(())
105 }
106}
107#[cfg(feature = "sass")]
108pub use sass::bundle_sass;
109
110pub use cfg_aliases::cfg_aliases;
111
112pub fn default_cfg_aliases() {
113 cfg_aliases! {
114 wasm: { target_arch = "wasm32" },
115 sw: { wasm },
116 not_wasm: { not(wasm) },
117 host: { not_wasm },
118 debug: { debug_assertions },
119 release: { not(debug_assertions) },
120 }
121}
122
123pub fn read_lib_name() -> Result<String> {
124 use toml::{Table, Value};
125 let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")?;
126 let manifest_path = &format!("{manifest_dir}/Cargo.toml");
127 let manifest = std::fs::read_to_string(manifest_path)?;
128 let parsed = manifest.parse::<Table>()?;
129 let lib_name = if parsed.contains_key("lib") {
130 let Value::Table(lib_table) = &parsed["lib"] else {
131 panic!("should be unreachable");
132 };
133 if lib_table.contains_key("name") {
134 lib_table["name"].as_str().unwrap().to_owned()
135 } else {
136 parsed["package"]["name"].as_str().unwrap().to_owned()
137 }
138 } else {
139 parsed["package"]["name"].as_str().unwrap().to_owned()
140 };
141 Ok(lib_name.replace("-", "_"))
142}
143
144pub fn find_target_dir() -> Option<String> {
146 use std::{ffi::OsStr, path::PathBuf};
147 if let Some(target_dir) = std::env::var_os("CARGO_TARGET_DIR") {
148 let target_dir = PathBuf::from(target_dir);
149 if target_dir.is_absolute() {
150 if let Some(str) = target_dir.to_str() {
151 return Some(str.to_owned());
152 } else {
153 return None;
154 }
155 } else {
156 return None;
157 };
158 }
159
160 let mut dir = PathBuf::from(out_path(""));
161 loop {
162 if dir.join(".rustc_info.json").exists()
163 || dir.join("CACHEDIR.TAG").exists()
164 || dir.file_name() == Some(OsStr::new("target"))
165 && dir
166 .parent()
167 .map_or(false, |parent| parent.join("Cargo.toml").exists())
168 {
169 if let Some(str) = dir.to_str() {
170 return Some(str.to_owned());
171 } else {
172 return None;
173 }
174 }
175 if dir.pop() {
176 continue;
177 }
178 return None;
179 }
180}
181
182pub fn out_path(filename: &str) -> String {
184 let dir = std::env::var("OUT_DIR").unwrap();
185 format!("{dir}/{filename}")
186}