dioxus_cli_opt/
file.rs

1use anyhow::Context;
2use manganis::{AssetOptions, CssModuleAssetOptions, FolderAssetOptions};
3use manganis_core::{AssetVariant, CssAssetOptions, ImageAssetOptions, JsAssetOptions};
4use std::path::Path;
5
6use crate::css::{process_css_module, process_scss};
7
8use super::{
9    css::process_css, folder::process_folder, image::process_image, js::process_js,
10    json::process_json,
11};
12
13/// Process a specific file asset with the given options reading from the source and writing to the output path
14pub fn process_file_to(
15    options: &AssetOptions,
16    source: &Path,
17    output_path: &Path,
18) -> anyhow::Result<()> {
19    process_file_to_with_options(options, source, output_path, false)
20}
21
22/// Process a specific file asset with additional options
23pub(crate) fn process_file_to_with_options(
24    options: &AssetOptions,
25    source: &Path,
26    output_path: &Path,
27    in_folder: bool,
28) -> anyhow::Result<()> {
29    // If the file already exists and this is a hashed asset, then we must have a file
30    // with the same hash already. The hash has the file contents and options, so if we
31    // find a file with the same hash, we probably already created this file in the past
32    if output_path.exists() && options.hash_suffix() {
33        return Ok(());
34    }
35    if let Some(parent) = output_path.parent() {
36        if !parent.exists() {
37            std::fs::create_dir_all(parent).context("Failed to create directory")?;
38        }
39    }
40
41    // Processing can be slow. Write to a temporary file first and then rename it to the final output path. If everything
42    // goes well. Without this, the user could quit in the middle of processing and the file will look complete to the
43    // caching system even though it is empty.
44    let temp_path = output_path.with_file_name(format!(
45        "partial.{}",
46        output_path
47            .file_name()
48            .unwrap_or_default()
49            .to_string_lossy()
50    ));
51    let resolved_options = resolve_asset_options(source, options.variant());
52
53    match &resolved_options {
54        ResolvedAssetType::Css(options) => {
55            process_css(options, source, &temp_path)?;
56        }
57        ResolvedAssetType::CssModule(options) => {
58            process_css_module(options, source, &temp_path)?;
59        }
60        ResolvedAssetType::Scss(options) => {
61            process_scss(options, source, &temp_path)?;
62        }
63        ResolvedAssetType::Js(options) => {
64            process_js(options, source, &temp_path, !in_folder)?;
65        }
66        ResolvedAssetType::Image(options) => {
67            process_image(options, source, &temp_path)?;
68        }
69        ResolvedAssetType::Json => {
70            process_json(source, &temp_path)?;
71        }
72        ResolvedAssetType::Folder(_) => {
73            process_folder(source, &temp_path)?;
74        }
75        ResolvedAssetType::File => {
76            let source_file = std::fs::File::open(source)?;
77            let mut reader = std::io::BufReader::new(source_file);
78            let output_file = std::fs::File::create(&temp_path)?;
79            let mut writer = std::io::BufWriter::new(output_file);
80            std::io::copy(&mut reader, &mut writer).with_context(|| {
81                format!(
82                    "Failed to write file to output location: {}",
83                    temp_path.display()
84                )
85            })?;
86        }
87    }
88
89    // Remove the existing output file if it exists
90    if output_path.exists() {
91        if output_path.is_file() {
92            std::fs::remove_file(output_path).context("Failed to remove previous output file")?;
93        } else if output_path.is_dir() {
94            std::fs::remove_dir_all(output_path)
95                .context("Failed to remove previous output file")?;
96        }
97    }
98
99    // If everything was successful, rename the temp file to the final output path
100    std::fs::rename(temp_path, output_path)
101        .with_context(|| format!("Failed to rename output file to: {}", output_path.display()))?;
102
103    Ok(())
104}
105
106pub(crate) enum ResolvedAssetType {
107    /// An image asset
108    Image(ImageAssetOptions),
109    /// A css asset
110    Css(CssAssetOptions),
111    /// A css module asset
112    CssModule(CssModuleAssetOptions),
113    /// A SCSS asset
114    Scss(CssAssetOptions),
115    /// A javascript asset
116    Js(JsAssetOptions),
117    /// A json asset
118    Json,
119    /// A folder asset
120    Folder(FolderAssetOptions),
121    /// A generic file
122    File,
123}
124
125pub(crate) fn resolve_asset_options(source: &Path, options: &AssetVariant) -> ResolvedAssetType {
126    match options {
127        AssetVariant::Image(image) => ResolvedAssetType::Image(*image),
128        AssetVariant::Css(css) => ResolvedAssetType::Css(*css),
129        AssetVariant::CssModule(css) => ResolvedAssetType::CssModule(*css),
130        AssetVariant::Js(js) => ResolvedAssetType::Js(*js),
131        AssetVariant::Folder(folder) => ResolvedAssetType::Folder(*folder),
132        AssetVariant::Unknown => resolve_unknown_asset_options(source),
133        _ => {
134            tracing::warn!("Unknown asset options... you may need to update the Dioxus CLI. Defaulting to a generic file: {:?}", options);
135            resolve_unknown_asset_options(source)
136        }
137    }
138}
139
140fn resolve_unknown_asset_options(source: &Path) -> ResolvedAssetType {
141    match source.extension().map(|e| e.to_string_lossy()).as_deref() {
142        Some("scss" | "sass") => ResolvedAssetType::Scss(CssAssetOptions::default()),
143        Some("css") => ResolvedAssetType::Css(CssAssetOptions::default()),
144        Some("js") => ResolvedAssetType::Js(JsAssetOptions::default()),
145        Some("json") => ResolvedAssetType::Json,
146        Some("jpg" | "jpeg" | "png" | "webp" | "avif") => {
147            ResolvedAssetType::Image(ImageAssetOptions::default())
148        }
149        _ if source.is_dir() => ResolvedAssetType::Folder(FolderAssetOptions::default()),
150        _ => ResolvedAssetType::File,
151    }
152}