// build.rs // // 把前端产物 stash-web/dist 同步到 $OUT_DIR/web, // 供 include_dir!("$OUT_DIR/web") 在编译期内嵌进二进制。 // // 这里只做「校验 + 搬运」,不在这里跑 npm: // 那样会把 Node 和网络依赖带进 cargo build。 // 编译 Rust 之前请先执行:cd stash-web && npm install && npm run build use std::{ env, fs, path::{Path, PathBuf}, }; use walkdir::WalkDir; /// 前端工程目录,相对 crate 根目录。 const WEB_PROJECT_DIR: &str = "stash-web"; /// 前端构建产物目录,相对前端工程目录。 const WEB_DIST_DIR: &str = "dist"; /// 同步到 $OUT_DIR 下的子目录名,需与 include_dir! 中的路径保持一致。 const EMBED_DIR: &str = "web"; fn main() { // 前端产物变了要重新嵌入(Cargo 对目录会递归扫描) println!("cargo:rerun-if-changed={}/{}", WEB_PROJECT_DIR, WEB_DIST_DIR); println!("cargo:rerun-if-changed={}/package.json", WEB_PROJECT_DIR); let dist = Path::new(env!("CARGO_MANIFEST_DIR")) .join(WEB_PROJECT_DIR) .join(WEB_DIST_DIR); let out = PathBuf::from(env::var("OUT_DIR").expect("构建环境缺少 OUT_DIR")).join(EMBED_DIR); // 缺产物就给出人能看懂的报错,而不是 proc-macro 的 "No such file or directory" assert!( dist.join("index.html").is_file(), "未找到前端产物:{}\n请先执行: cd {} && npm install && npm run build", dist.display(), WEB_PROJECT_DIR ); // 必须整体重建,否则 Vite 换 hash 后旧 chunk 会残留在二进制里 let _ = fs::remove_dir_all(&out); fs::create_dir_all(&out).expect("创建内嵌目录失败"); copy_dir(&dist, &out).expect("复制前端产物失败"); } /// 用 walkdir 递归复制 `from` 到 `to`,并保留目录结构。 fn copy_dir(from: &Path, to: &Path) -> Result<(), Box> { let mut copied = 0usize; for entry in WalkDir::new(from) { let entry = entry?; let relative = entry.path().strip_prefix(from)?; // 跳过 dist 目录自身 if relative.as_os_str().is_empty() { continue; } let destination = to.join(relative); if entry.file_type().is_dir() { fs::create_dir_all(&destination)?; continue; } // 符号链接等非普通文件不参与内嵌 if !entry.file_type().is_file() { continue; } fs::copy(entry.path(), &destination)?; copied += 1; } if copied == 0 { return Err(std::io::Error::other(format!( "{} 中没有任何文件可内嵌", from.display() )) .into()); } Ok(()) }