assets.rs 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. use brotli::enc::BrotliEncoderParams;
  2. use std::path::Path;
  3. use std::{ffi::OsString, path::PathBuf};
  4. use walkdir::WalkDir;
  5. use std::{fs::File, io::Write};
  6. use crate::Result;
  7. use dioxus_cli_config::CrateConfig;
  8. use dioxus_cli_config::Platform;
  9. use manganis_cli_support::{AssetManifest, AssetManifestExt};
  10. pub fn asset_manifest(bin: Option<&str>, crate_config: &CrateConfig) -> AssetManifest {
  11. AssetManifest::load_from_path(
  12. bin,
  13. crate_config.crate_dir.join("Cargo.toml"),
  14. crate_config.workspace_dir.join("Cargo.lock"),
  15. )
  16. }
  17. /// Create a head file that contains all of the imports for assets that the user project uses
  18. pub fn create_assets_head(config: &CrateConfig, manifest: &AssetManifest) -> Result<()> {
  19. let mut file = File::create(config.out_dir().join("__assets_head.html"))?;
  20. file.write_all(manifest.head().as_bytes())?;
  21. Ok(())
  22. }
  23. /// Process any assets collected from the binary
  24. pub(crate) fn process_assets(config: &CrateConfig, manifest: &AssetManifest) -> anyhow::Result<()> {
  25. let static_asset_output_dir = PathBuf::from(
  26. config
  27. .dioxus_config
  28. .web
  29. .app
  30. .base_path
  31. .clone()
  32. .unwrap_or_default(),
  33. );
  34. let static_asset_output_dir = config.out_dir().join(static_asset_output_dir);
  35. manifest.copy_static_assets_to(static_asset_output_dir)?;
  36. Ok(())
  37. }
  38. /// A guard that sets up the environment for the web renderer to compile in. This guard sets the location that assets will be served from
  39. pub(crate) struct AssetConfigDropGuard;
  40. impl AssetConfigDropGuard {
  41. pub fn new() -> Self {
  42. // Set up the collect asset config
  43. manganis_cli_support::Config::default()
  44. .with_assets_serve_location("/")
  45. .save();
  46. Self {}
  47. }
  48. }
  49. impl Drop for AssetConfigDropGuard {
  50. fn drop(&mut self) {
  51. // Reset the config
  52. manganis_cli_support::Config::default().save();
  53. }
  54. }
  55. pub fn copy_assets_dir(config: &CrateConfig, platform: Platform) -> anyhow::Result<()> {
  56. tracing::info!("Copying public assets to the output directory...");
  57. let out_dir = config.out_dir();
  58. let asset_dir = config.asset_dir();
  59. if asset_dir.is_dir() {
  60. // Only pre-compress the assets from the web build. Desktop assets are not served, so they don't need to be pre_compressed
  61. let pre_compress = platform == Platform::Web && config.should_pre_compress_web_assets();
  62. copy_dir_to(asset_dir, out_dir, pre_compress)?;
  63. }
  64. Ok(())
  65. }
  66. fn copy_dir_to(src_dir: PathBuf, dest_dir: PathBuf, pre_compress: bool) -> std::io::Result<()> {
  67. let entries = std::fs::read_dir(&src_dir)?;
  68. let mut children: Vec<std::thread::JoinHandle<std::io::Result<()>>> = Vec::new();
  69. for entry in entries.flatten() {
  70. let entry_path = entry.path();
  71. let path_relative_to_src = entry_path.strip_prefix(&src_dir).unwrap();
  72. let output_file_location = dest_dir.join(path_relative_to_src);
  73. children.push(std::thread::spawn(move || {
  74. if entry.file_type()?.is_dir() {
  75. // If the file is a directory, recursively copy it into the output directory
  76. if let Err(err) =
  77. copy_dir_to(entry_path.clone(), output_file_location, pre_compress)
  78. {
  79. tracing::error!(
  80. "Failed to pre-compress directory {}: {}",
  81. entry_path.display(),
  82. err
  83. );
  84. }
  85. } else {
  86. // Make sure the directory exists
  87. std::fs::create_dir_all(output_file_location.parent().unwrap())?;
  88. // Copy the file to the output directory
  89. std::fs::copy(&entry_path, &output_file_location)?;
  90. // Then pre-compress the file if needed
  91. if pre_compress {
  92. if let Err(err) = pre_compress_file(&entry_path.clone()) {
  93. tracing::error!(
  94. "Failed to pre-compress static assets {}: {}",
  95. entry_path.display(),
  96. err
  97. );
  98. }
  99. }
  100. }
  101. Ok(())
  102. }));
  103. }
  104. for child in children {
  105. child.join().unwrap()?;
  106. }
  107. Ok(())
  108. }
  109. /// pre-compress a file with brotli
  110. pub(crate) fn pre_compress_file(path: &Path) -> std::io::Result<()> {
  111. let new_extension = match path.extension() {
  112. Some(ext) => {
  113. if ext.to_string_lossy().to_lowercase().ends_with("br") {
  114. return Ok(());
  115. }
  116. let mut ext = ext.to_os_string();
  117. ext.push(".br");
  118. ext
  119. }
  120. None => OsString::from("br"),
  121. };
  122. let file = std::fs::File::open(path)?;
  123. let mut stream = std::io::BufReader::new(file);
  124. let output = path.with_extension(new_extension);
  125. let mut buffer = std::fs::File::create(output)?;
  126. let params = BrotliEncoderParams::default();
  127. brotli::BrotliCompress(&mut stream, &mut buffer, &params)?;
  128. Ok(())
  129. }
  130. /// pre-compress all files in a folder
  131. pub(crate) fn pre_compress_folder(path: &Path) -> std::io::Result<()> {
  132. let walk_dir = WalkDir::new(path);
  133. for entry in walk_dir.into_iter().filter_map(|e| e.ok()) {
  134. let entry_path = entry.path();
  135. if entry_path.is_file() {
  136. pre_compress_file(entry_path)?;
  137. }
  138. }
  139. Ok(())
  140. }