builder.rs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802
  1. use crate::{
  2. assets::{
  3. asset_manifest, copy_assets_dir, create_assets_head, pre_compress_folder, process_assets,
  4. AssetConfigDropGuard,
  5. },
  6. error::{Error, Result},
  7. tools::Tool,
  8. };
  9. use anyhow::Context;
  10. use cargo_metadata::{diagnostic::Diagnostic, Message};
  11. use dioxus_cli_config::{crate_root, CrateConfig, ExecutableType};
  12. use indicatif::{ProgressBar, ProgressStyle};
  13. use lazy_static::lazy_static;
  14. use manganis_cli_support::{AssetManifest, ManganisSupportGuard};
  15. use std::{
  16. env,
  17. fs::{copy, create_dir_all, File},
  18. io::{self, IsTerminal, Read},
  19. panic,
  20. path::PathBuf,
  21. process::Command,
  22. time::Duration,
  23. };
  24. use wasm_bindgen_cli_support::Bindgen;
  25. lazy_static! {
  26. static ref PROGRESS_BARS: indicatif::MultiProgress = indicatif::MultiProgress::new();
  27. }
  28. #[derive(Debug, Clone)]
  29. pub struct BuildResult {
  30. pub warnings: Vec<Diagnostic>,
  31. pub executable: Option<PathBuf>,
  32. pub elapsed_time: u128,
  33. pub assets: Option<AssetManifest>,
  34. }
  35. /// This trait is only created for the convenient and concise way to set
  36. /// `RUSTFLAGS` environment variable for the `subprocess::Exec`.
  37. pub trait ExecWithRustFlagsSetter {
  38. fn set_rust_flags(self, rust_flags: Option<String>) -> Self;
  39. }
  40. impl ExecWithRustFlagsSetter for subprocess::Exec {
  41. /// Sets (appends to, if already set) `RUSTFLAGS` environment variable if
  42. /// `rust_flags` is not `None`.
  43. fn set_rust_flags(self, rust_flags: Option<String>) -> Self {
  44. if let Some(rust_flags) = rust_flags {
  45. // Some `RUSTFLAGS` might be already set in the environment or provided
  46. // by the user. They should take higher priority than the default flags.
  47. // If no default flags are provided, then there is no point in
  48. // redefining the environment variable with the same value, if it is
  49. // even set. If no custom flags are set, then there is no point in
  50. // adding the unnecessary whitespace to the command.
  51. self.env(
  52. "RUSTFLAGS",
  53. if let Ok(custom_rust_flags) = env::var("RUSTFLAGS") {
  54. rust_flags + " " + custom_rust_flags.as_str()
  55. } else {
  56. rust_flags
  57. },
  58. )
  59. } else {
  60. self
  61. }
  62. }
  63. }
  64. /// Build client (WASM).
  65. /// Note: `rust_flags` argument is only used for the fullstack platform.
  66. pub fn build_web(
  67. config: &CrateConfig,
  68. skip_assets: bool,
  69. rust_flags: Option<String>,
  70. ) -> Result<BuildResult> {
  71. // [1] Build the project with cargo, generating a wasm32-unknown-unknown target (is there a more specific, better target to leverage?)
  72. // [2] Generate the appropriate build folders
  73. // [3] Wasm-bindgen the .wasm file, and move it into the {builddir}/modules/xxxx/xxxx_bg.wasm
  74. // [4] Wasm-opt the .wasm file with whatever optimizations need to be done
  75. // [5][OPTIONAL] Builds the Tailwind CSS file using the Tailwind standalone binary
  76. // [6] Link up the html page to the wasm module
  77. let CrateConfig {
  78. crate_dir,
  79. target_dir,
  80. executable,
  81. dioxus_config,
  82. ..
  83. } = config;
  84. let out_dir = config.out_dir();
  85. let _guard = AssetConfigDropGuard::new();
  86. let _manganis_support = ManganisSupportGuard::default();
  87. // start to build the assets
  88. build_assets(config)?;
  89. let t_start = std::time::Instant::now();
  90. let _guard = dioxus_cli_config::__private::save_config(config);
  91. // [1] Build the .wasm module
  92. tracing::info!("🚅 Running build command...");
  93. // If the user has rustup, we can check if the wasm32-unknown-unknown target is installed
  94. // Otherwise we can just assume it is installed - which i snot great...
  95. // Eventually we can poke at the errors and let the user know they need to install the target
  96. if let Ok(wasm_check_command) = Command::new("rustup").args(["show"]).output() {
  97. let wasm_check_output = String::from_utf8(wasm_check_command.stdout).unwrap();
  98. if !wasm_check_output.contains("wasm32-unknown-unknown") {
  99. tracing::info!("wasm32-unknown-unknown target not detected, installing..");
  100. let _ = Command::new("rustup")
  101. .args(["target", "add", "wasm32-unknown-unknown"])
  102. .output()?;
  103. }
  104. }
  105. let cmd = subprocess::Exec::cmd("cargo")
  106. .set_rust_flags(rust_flags)
  107. .env("CARGO_TARGET_DIR", target_dir)
  108. .cwd(crate_dir)
  109. .arg("build")
  110. .arg("--target")
  111. .arg("wasm32-unknown-unknown")
  112. .arg("--message-format=json-render-diagnostics");
  113. // TODO: make the initial variable mutable to simplify all the expressions
  114. // below. Look inside the `build_desktop()` as an example.
  115. let cmd = if config.release {
  116. cmd.arg("--release")
  117. } else {
  118. cmd
  119. };
  120. let cmd = if config.verbose {
  121. cmd.arg("--verbose")
  122. } else {
  123. cmd.arg("--quiet")
  124. };
  125. let cmd = if config.custom_profile.is_some() {
  126. let custom_profile = config.custom_profile.as_ref().unwrap();
  127. cmd.arg("--profile").arg(custom_profile)
  128. } else {
  129. cmd
  130. };
  131. let cmd = if config.features.is_some() {
  132. let features_str = config.features.as_ref().unwrap().join(" ");
  133. cmd.arg("--features").arg(features_str)
  134. } else {
  135. cmd
  136. };
  137. let cmd = cmd.args(&config.cargo_args);
  138. let cmd = match executable {
  139. ExecutableType::Binary(name) => cmd.arg("--bin").arg(name),
  140. ExecutableType::Lib(name) => cmd.arg("--lib").arg(name),
  141. ExecutableType::Example(name) => cmd.arg("--example").arg(name),
  142. };
  143. let CargoBuildResult {
  144. warnings,
  145. output_location,
  146. } = prettier_build(cmd)?;
  147. let output_location = output_location.context("No output location found")?;
  148. // [2] Establish the output directory structure
  149. let bindgen_outdir = out_dir.join("assets").join("dioxus");
  150. let input_path = output_location.with_extension("wasm");
  151. tracing::info!("Running wasm-bindgen");
  152. let run_wasm_bindgen = || {
  153. // [3] Bindgen the final binary for use easy linking
  154. let mut bindgen_builder = Bindgen::new();
  155. bindgen_builder
  156. .input_path(&input_path)
  157. .web(true)
  158. .unwrap()
  159. .debug(true)
  160. .demangle(true)
  161. .keep_debug(true)
  162. .remove_name_section(false)
  163. .remove_producers_section(false)
  164. .out_name(&dioxus_config.application.name)
  165. .generate(&bindgen_outdir)
  166. .unwrap();
  167. };
  168. let bindgen_result = panic::catch_unwind(run_wasm_bindgen);
  169. // WASM bindgen requires the exact version of the bindgen schema to match the version the CLI was built with
  170. // If we get an error, we can try to recover by pinning the user's wasm-bindgen version to the version we used
  171. if let Err(err) = bindgen_result {
  172. tracing::error!("Bindgen build failed: {:?}", err);
  173. update_wasm_bindgen_version()?;
  174. run_wasm_bindgen();
  175. }
  176. // check binaryen:wasm-opt tool
  177. tracing::info!("Running optimization with wasm-opt...");
  178. let dioxus_tools = dioxus_config.application.tools.clone();
  179. if dioxus_tools.contains_key("binaryen") {
  180. let info = dioxus_tools.get("binaryen").unwrap();
  181. let binaryen = crate::tools::Tool::Binaryen;
  182. if binaryen.is_installed() {
  183. if let Some(sub) = info.as_table() {
  184. if sub.contains_key("wasm_opt")
  185. && sub.get("wasm_opt").unwrap().as_bool().unwrap_or(false)
  186. {
  187. tracing::info!("Optimizing WASM size with wasm-opt...");
  188. let target_file = out_dir
  189. .join("assets")
  190. .join("dioxus")
  191. .join(format!("{}_bg.wasm", dioxus_config.application.name));
  192. if target_file.is_file() {
  193. let mut args = vec![
  194. target_file.to_str().unwrap(),
  195. "-o",
  196. target_file.to_str().unwrap(),
  197. ];
  198. if config.release {
  199. args.push("-Oz");
  200. }
  201. binaryen.call("wasm-opt", args)?;
  202. }
  203. }
  204. }
  205. } else {
  206. tracing::warn!(
  207. "Binaryen tool not found, you can use `dx tool add binaryen` to install it."
  208. );
  209. }
  210. } else {
  211. tracing::info!("Skipping optimization with wasm-opt, binaryen tool not found.");
  212. }
  213. // If pre-compressing is enabled, we can pre_compress the wasm-bindgen output
  214. if config.should_pre_compress_web_assets() {
  215. pre_compress_folder(&bindgen_outdir)?;
  216. }
  217. // [5][OPTIONAL] If tailwind is enabled and installed we run it to generate the CSS
  218. if dioxus_tools.contains_key("tailwindcss") {
  219. let info = dioxus_tools.get("tailwindcss").unwrap();
  220. let tailwind = crate::tools::Tool::Tailwind;
  221. if tailwind.is_installed() {
  222. if let Some(sub) = info.as_table() {
  223. tracing::info!("Building Tailwind bundle CSS file...");
  224. let input_path = match sub.get("input") {
  225. Some(val) => val.as_str().unwrap(),
  226. None => "./public",
  227. };
  228. let config_path = match sub.get("config") {
  229. Some(val) => val.as_str().unwrap(),
  230. None => "./src/tailwind.config.js",
  231. };
  232. let mut args = vec![
  233. "-i",
  234. input_path,
  235. "-o",
  236. "dist/tailwind.css",
  237. "-c",
  238. config_path,
  239. ];
  240. if config.release {
  241. args.push("--minify");
  242. }
  243. tailwind.call("tailwindcss", args)?;
  244. }
  245. } else {
  246. tracing::warn!(
  247. "Tailwind tool not found, you can use `dx tool add tailwindcss` to install it."
  248. );
  249. }
  250. }
  251. // this code will copy all public file to the output dir
  252. copy_assets_dir(config, dioxus_cli_config::Platform::Web)?;
  253. let assets = if !skip_assets {
  254. tracing::info!("Processing assets");
  255. let assets = asset_manifest(executable.executable(), config);
  256. process_assets(config, &assets)?;
  257. Some(assets)
  258. } else {
  259. None
  260. };
  261. Ok(BuildResult {
  262. warnings,
  263. executable: Some(output_location),
  264. elapsed_time: t_start.elapsed().as_millis(),
  265. assets,
  266. })
  267. }
  268. // Attempt to automatically recover from a bindgen failure by updating the wasm-bindgen version
  269. fn update_wasm_bindgen_version() -> Result<()> {
  270. let cli_bindgen_version = wasm_bindgen_shared::version();
  271. tracing::info!("Attempting to recover from bindgen failure by setting the wasm-bindgen version to {cli_bindgen_version}...");
  272. let output = Command::new("cargo")
  273. .args([
  274. "update",
  275. "-p",
  276. "wasm-bindgen",
  277. "--precise",
  278. &cli_bindgen_version,
  279. ])
  280. .output();
  281. let mut error_message = None;
  282. if let Ok(output) = output {
  283. if output.status.success() {
  284. tracing::info!("Successfully updated wasm-bindgen to {cli_bindgen_version}");
  285. return Ok(());
  286. } else {
  287. error_message = Some(output);
  288. }
  289. }
  290. if let Some(output) = error_message {
  291. tracing::error!("Failed to update wasm-bindgen: {:#?}", output);
  292. }
  293. Err(Error::BuildFailed(format!("WASM bindgen build failed!\nThis is probably due to the Bindgen version, dioxus-cli is using `{cli_bindgen_version}` which is not compatible with your crate.\nPlease reinstall the dioxus cli to fix this issue.\nYou can reinstall the dioxus cli by running `cargo install dioxus-cli --force` and then rebuild your project")))
  294. }
  295. /// Note: `rust_flags` argument is only used for the fullstack platform
  296. /// (server).
  297. pub fn build_desktop(
  298. config: &CrateConfig,
  299. _is_serve: bool,
  300. skip_assets: bool,
  301. rust_flags: Option<String>,
  302. ) -> Result<BuildResult> {
  303. tracing::info!("🚅 Running build [Desktop] command...");
  304. let t_start = std::time::Instant::now();
  305. build_assets(config)?;
  306. let _guard = dioxus_cli_config::__private::save_config(config);
  307. let _manganis_support = ManganisSupportGuard::default();
  308. let _guard = AssetConfigDropGuard::new();
  309. let mut cmd = subprocess::Exec::cmd("cargo")
  310. .set_rust_flags(rust_flags)
  311. .env("CARGO_TARGET_DIR", &config.target_dir)
  312. .cwd(&config.crate_dir)
  313. .arg("build")
  314. .arg("--message-format=json-render-diagnostics");
  315. if config.release {
  316. cmd = cmd.arg("--release");
  317. }
  318. if config.verbose {
  319. cmd = cmd.arg("--verbose");
  320. } else {
  321. cmd = cmd.arg("--quiet");
  322. }
  323. if config.custom_profile.is_some() {
  324. let custom_profile = config.custom_profile.as_ref().unwrap();
  325. cmd = cmd.arg("--profile").arg(custom_profile);
  326. }
  327. if config.features.is_some() {
  328. let features_str = config.features.as_ref().unwrap().join(" ");
  329. cmd = cmd.arg("--features").arg(features_str);
  330. }
  331. if let Some(target) = &config.target {
  332. cmd = cmd.arg("--target").arg(target);
  333. }
  334. cmd = cmd.args(&config.cargo_args);
  335. let cmd = match &config.executable {
  336. ExecutableType::Binary(name) => cmd.arg("--bin").arg(name),
  337. ExecutableType::Lib(name) => cmd.arg("--lib").arg(name),
  338. ExecutableType::Example(name) => cmd.arg("--example").arg(name),
  339. };
  340. let warning_messages = prettier_build(cmd)?;
  341. let file_name: String = config.executable.executable().unwrap().to_string();
  342. let target_file = if cfg!(windows) {
  343. format!("{}.exe", &file_name)
  344. } else {
  345. file_name
  346. };
  347. if !config.out_dir().is_dir() {
  348. create_dir_all(config.out_dir())?;
  349. }
  350. let output_path = config.out_dir().join(target_file);
  351. if let Some(res_path) = &warning_messages.output_location {
  352. copy(res_path, &output_path)?;
  353. }
  354. copy_assets_dir(config, dioxus_cli_config::Platform::Desktop)?;
  355. let assets = if !skip_assets {
  356. tracing::info!("Processing assets");
  357. let assets = asset_manifest(config.executable.executable(), config);
  358. // Collect assets
  359. process_assets(config, &assets)?;
  360. // Create the __assets_head.html file for bundling
  361. create_assets_head(config, &assets)?;
  362. Some(assets)
  363. } else {
  364. None
  365. };
  366. tracing::info!(
  367. "🚩 Build completed: [./{}]",
  368. config.dioxus_config.application.out_dir.clone().display()
  369. );
  370. println!("build desktop done");
  371. Ok(BuildResult {
  372. warnings: warning_messages.warnings,
  373. executable: Some(output_path),
  374. elapsed_time: t_start.elapsed().as_millis(),
  375. assets,
  376. })
  377. }
  378. struct CargoBuildResult {
  379. warnings: Vec<Diagnostic>,
  380. output_location: Option<PathBuf>,
  381. }
  382. struct Outputter {
  383. progress_bar: Option<ProgressBar>,
  384. }
  385. impl Outputter {
  386. pub fn new() -> Self {
  387. let stdout = io::stdout().lock();
  388. let mut myself = Self { progress_bar: None };
  389. if stdout.is_terminal() {
  390. let mut pb = ProgressBar::new_spinner();
  391. pb.enable_steady_tick(Duration::from_millis(200));
  392. pb = PROGRESS_BARS.add(pb);
  393. pb.set_style(
  394. ProgressStyle::with_template("{spinner:.dim.bold} {wide_msg}")
  395. .unwrap()
  396. .tick_chars("/|\\- "),
  397. );
  398. myself.progress_bar = Some(pb);
  399. }
  400. myself
  401. }
  402. pub fn println(&self, msg: impl ToString) {
  403. let msg = msg.to_string();
  404. if let Some(pb) = &self.progress_bar {
  405. pb.set_message(msg)
  406. } else {
  407. println!("{msg}");
  408. }
  409. }
  410. pub fn finish_with_message(&self, msg: impl ToString) {
  411. let msg = msg.to_string();
  412. if let Some(pb) = &self.progress_bar {
  413. pb.finish_with_message(msg)
  414. } else {
  415. println!("{msg}");
  416. }
  417. }
  418. }
  419. fn prettier_build(cmd: subprocess::Exec) -> anyhow::Result<CargoBuildResult> {
  420. let mut warning_messages: Vec<Diagnostic> = vec![];
  421. let output = Outputter::new();
  422. output.println("💼 Waiting to start building the project...");
  423. let stdout = cmd.detached().stream_stdout()?;
  424. let reader = std::io::BufReader::new(stdout);
  425. let mut output_location = None;
  426. for message in cargo_metadata::Message::parse_stream(reader) {
  427. match message.unwrap() {
  428. Message::CompilerMessage(msg) => {
  429. let message = msg.message;
  430. match message.level {
  431. cargo_metadata::diagnostic::DiagnosticLevel::Error => {
  432. return {
  433. Err(anyhow::anyhow!(message
  434. .rendered
  435. .unwrap_or("Unknown".into())))
  436. };
  437. }
  438. cargo_metadata::diagnostic::DiagnosticLevel::Warning => {
  439. warning_messages.push(message.clone());
  440. }
  441. _ => {}
  442. }
  443. }
  444. Message::CompilerArtifact(artifact) => {
  445. output.println(format!("⚙ Compiling {} ", artifact.package_id));
  446. if let Some(executable) = artifact.executable {
  447. output_location = Some(executable.into());
  448. }
  449. }
  450. Message::BuildScriptExecuted(script) => {
  451. let _package_id = script.package_id.to_string();
  452. }
  453. Message::BuildFinished(finished) => {
  454. if finished.success {
  455. output.finish_with_message("👑 Build done.");
  456. } else {
  457. output.finish_with_message("❌ Build failed.");
  458. return Err(anyhow::anyhow!("Build failed"));
  459. }
  460. }
  461. _ => {
  462. // Unknown message
  463. }
  464. }
  465. }
  466. Ok(CargoBuildResult {
  467. warnings: warning_messages,
  468. output_location,
  469. })
  470. }
  471. pub fn gen_page(config: &CrateConfig, manifest: Option<&AssetManifest>, serve: bool) -> String {
  472. let _guard = AssetConfigDropGuard::new();
  473. let crate_root = crate_root().unwrap();
  474. let custom_html_file = crate_root.join("index.html");
  475. let mut html = if custom_html_file.is_file() {
  476. let mut buf = String::new();
  477. let mut file = File::open(custom_html_file).unwrap();
  478. if file.read_to_string(&mut buf).is_ok() {
  479. buf
  480. } else {
  481. String::from(include_str!("./assets/index.html"))
  482. }
  483. } else {
  484. String::from(include_str!("./assets/index.html"))
  485. };
  486. let resources = config.dioxus_config.web.resource.clone();
  487. let mut style_list = resources.style.unwrap_or_default();
  488. let mut script_list = resources.script.unwrap_or_default();
  489. if serve {
  490. let mut dev_style = resources.dev.style.clone();
  491. let mut dev_script = resources.dev.script.clone();
  492. style_list.append(&mut dev_style);
  493. script_list.append(&mut dev_script);
  494. }
  495. let mut style_str = String::new();
  496. for style in style_list {
  497. style_str.push_str(&format!(
  498. "<link rel=\"stylesheet\" href=\"{}\">\n",
  499. &style.to_str().unwrap(),
  500. ))
  501. }
  502. if config
  503. .dioxus_config
  504. .application
  505. .tools
  506. .clone()
  507. .contains_key("tailwindcss")
  508. {
  509. style_str.push_str("<link rel=\"stylesheet\" href=\"/{base_path}/tailwind.css\">\n");
  510. }
  511. if let Some(manifest) = manifest {
  512. style_str.push_str(&manifest.head());
  513. }
  514. replace_or_insert_before("{style_include}", &style_str, "</head", &mut html);
  515. let mut script_str = String::new();
  516. for script in script_list {
  517. script_str.push_str(&format!(
  518. "<script src=\"{}\"></script>\n",
  519. &script.to_str().unwrap(),
  520. ))
  521. }
  522. replace_or_insert_before("{script_include}", &script_str, "</body", &mut html);
  523. if serve {
  524. html += &format!(
  525. "<script>{}</script>",
  526. include_str!("./assets/autoreload.js")
  527. );
  528. }
  529. let base_path = match &config.dioxus_config.web.app.base_path {
  530. Some(path) => path.trim_matches('/'),
  531. None => ".",
  532. };
  533. let app_name = &config.dioxus_config.application.name;
  534. // Check if a script already exists
  535. if html.contains("{app_name}") && html.contains("{base_path}") {
  536. html = html.replace("{app_name}", app_name);
  537. html = html.replace("{base_path}", base_path);
  538. } else {
  539. // If not, insert the script
  540. html = html.replace(
  541. "</body",
  542. &format!(
  543. r#"<script type="module">
  544. import init from "/{base_path}/assets/dioxus/{app_name}.js";
  545. init("/{base_path}/assets/dioxus/{app_name}_bg.wasm").then(wasm => {{
  546. if (wasm.__wbindgen_start == undefined) {{
  547. wasm.main();
  548. }}
  549. }});
  550. </script>
  551. </body"#
  552. ),
  553. );
  554. }
  555. let title = config.dioxus_config.web.app.title.clone();
  556. replace_or_insert_before("{app_title}", &title, "</title", &mut html);
  557. html
  558. }
  559. fn replace_or_insert_before(
  560. replace: &str,
  561. with: &str,
  562. or_insert_before: &str,
  563. content: &mut String,
  564. ) {
  565. if content.contains(replace) {
  566. *content = content.replace(replace, with);
  567. } else {
  568. *content = content.replace(or_insert_before, &format!("{}{}", with, or_insert_before));
  569. }
  570. }
  571. // this function will build some assets file
  572. // like sass tool resources
  573. // this function will return a array which file don't need copy to out_dir.
  574. fn build_assets(config: &CrateConfig) -> Result<Vec<PathBuf>> {
  575. let mut result = vec![];
  576. let dioxus_config = &config.dioxus_config;
  577. let dioxus_tools = dioxus_config.application.tools.clone();
  578. // check sass tool state
  579. let sass = Tool::Sass;
  580. if sass.is_installed() && dioxus_tools.contains_key("sass") {
  581. let sass_conf = dioxus_tools.get("sass").unwrap();
  582. if let Some(tab) = sass_conf.as_table() {
  583. let source_map = tab.contains_key("source_map");
  584. let source_map = if source_map && tab.get("source_map").unwrap().is_bool() {
  585. if tab.get("source_map").unwrap().as_bool().unwrap_or_default() {
  586. "--source-map"
  587. } else {
  588. "--no-source-map"
  589. }
  590. } else {
  591. "--source-map"
  592. };
  593. if tab.contains_key("input") {
  594. if tab.get("input").unwrap().is_str() {
  595. let file = tab.get("input").unwrap().as_str().unwrap().trim();
  596. if file == "*" {
  597. // if the sass open auto, we need auto-check the assets dir.
  598. let asset_dir = config.asset_dir().clone();
  599. if asset_dir.is_dir() {
  600. for entry in walkdir::WalkDir::new(&asset_dir)
  601. .into_iter()
  602. .filter_map(|e| e.ok())
  603. {
  604. let temp = entry.path();
  605. if temp.is_file() {
  606. let suffix = temp.extension();
  607. if suffix.is_none() {
  608. continue;
  609. }
  610. let suffix = suffix.unwrap().to_str().unwrap();
  611. if suffix == "scss" || suffix == "sass" {
  612. // if file suffix is `scss` / `sass` we need transform it.
  613. let out_file = format!(
  614. "{}.css",
  615. temp.file_stem().unwrap().to_str().unwrap()
  616. );
  617. let target_path = config
  618. .out_dir()
  619. .join(
  620. temp.strip_prefix(&asset_dir)
  621. .unwrap()
  622. .parent()
  623. .unwrap(),
  624. )
  625. .join(out_file);
  626. let res = sass.call(
  627. "sass",
  628. vec![
  629. temp.to_str().unwrap(),
  630. target_path.to_str().unwrap(),
  631. source_map,
  632. ],
  633. );
  634. if res.is_ok() {
  635. result.push(temp.to_path_buf());
  636. }
  637. }
  638. }
  639. }
  640. }
  641. } else {
  642. // just transform one file.
  643. let relative_path = if &file[0..1] == "/" {
  644. &file[1..file.len()]
  645. } else {
  646. file
  647. };
  648. let path = config.asset_dir().join(relative_path);
  649. let out_file =
  650. format!("{}.css", path.file_stem().unwrap().to_str().unwrap());
  651. let target_path = config
  652. .out_dir()
  653. .join(PathBuf::from(relative_path).parent().unwrap())
  654. .join(out_file);
  655. if path.is_file() {
  656. let res = sass.call(
  657. "sass",
  658. vec![
  659. path.to_str().unwrap(),
  660. target_path.to_str().unwrap(),
  661. source_map,
  662. ],
  663. );
  664. if res.is_ok() {
  665. result.push(path);
  666. } else {
  667. tracing::error!("{:?}", res);
  668. }
  669. }
  670. }
  671. } else if tab.get("input").unwrap().is_array() {
  672. // check files list.
  673. let list = tab.get("input").unwrap().as_array().unwrap();
  674. for i in list {
  675. if i.is_str() {
  676. let path = i.as_str().unwrap();
  677. let relative_path = if &path[0..1] == "/" {
  678. &path[1..path.len()]
  679. } else {
  680. path
  681. };
  682. let path = config.asset_dir().join(relative_path);
  683. let out_file =
  684. format!("{}.css", path.file_stem().unwrap().to_str().unwrap());
  685. let target_path = config
  686. .out_dir()
  687. .join(PathBuf::from(relative_path).parent().unwrap())
  688. .join(out_file);
  689. if path.is_file() {
  690. let res = sass.call(
  691. "sass",
  692. vec![
  693. path.to_str().unwrap(),
  694. target_path.to_str().unwrap(),
  695. source_map,
  696. ],
  697. );
  698. if res.is_ok() {
  699. result.push(path);
  700. }
  701. }
  702. }
  703. }
  704. }
  705. }
  706. }
  707. }
  708. // SASS END
  709. Ok(result)
  710. }