rip out tauri bundle (v2), swc -> esbuild, blacksmith -> warpbuild, FAST CI 🚀 (#5328)
- inline mac, windows, linux bundling in the cli - remove tauri bundle from our dep tree - trim down the rpm crate for fewer dependencies in tree - migrate from blacksmith to warpbuild - move off swc and instead use esbuild via tool - use dev drives in windows CI - use fork of nix action for faster caching - test bundling in CI - fix asset handling in linux - add default APPDATA location for webview2 installs for dioxus apps on windows
This commit is contained in:
1 parent
be7296f4f0
commit
b2d01e6779
83 files changed
+7756
-5403
No files matched your search
@@ -0,0 +1,184 @@
|
||||
//! Utilities for creating hashed paths to assets in Manganis. This module defines [`AssetHash`] which is used to create a hashed path to an asset in both the CLI and the macro.
|
||||
|
||||
use std::{
|
||||
hash::{Hash, Hasher},
|
||||
io::Read,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use crate::opt::{
|
||||
css::hash_scss,
|
||||
file::{resolve_asset_options, ResolvedAssetType},
|
||||
js::hash_js,
|
||||
};
|
||||
use manganis::{AssetOptions, BundledAsset};
|
||||
|
||||
/// The opaque hash type manganis uses to identify assets. Each time an asset or asset options change, this hash will
|
||||
/// change. This hash is included in the URL of the bundled asset for cache busting.
|
||||
struct AssetHash {
|
||||
/// We use a wrapper type here to hide the exact size of the hash so we can switch to a sha hash in a minor version bump
|
||||
hash: [u8; 8],
|
||||
}
|
||||
|
||||
impl AssetHash {
|
||||
/// Create a new asset hash
|
||||
const fn new(hash: u64) -> Self {
|
||||
Self {
|
||||
hash: hash.to_le_bytes(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the hash bytes
|
||||
pub const fn bytes(&self) -> &[u8] {
|
||||
&self.hash
|
||||
}
|
||||
|
||||
/// Create a new asset hash for a file. The input file to this function should be fully resolved
|
||||
pub fn hash_file_contents(
|
||||
options: &AssetOptions,
|
||||
file_path: impl AsRef<Path>,
|
||||
) -> anyhow::Result<AssetHash> {
|
||||
hash_file(options, file_path.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
/// Process a specific file asset with the given options reading from the source and writing to the output path
|
||||
fn hash_file(options: &AssetOptions, source: &Path) -> anyhow::Result<AssetHash> {
|
||||
// Create a hasher
|
||||
let mut hash = std::collections::hash_map::DefaultHasher::new();
|
||||
options.hash(&mut hash);
|
||||
|
||||
// Hash the version of the CLI
|
||||
hash.write(crate::dx_build_info::PKG_VERSION.as_bytes());
|
||||
hash_file_with_options(options, source, &mut hash, false)?;
|
||||
|
||||
let hash = hash.finish();
|
||||
Ok(AssetHash::new(hash))
|
||||
}
|
||||
|
||||
/// Process a specific file asset with additional options
|
||||
pub(crate) fn hash_file_with_options(
|
||||
options: &AssetOptions,
|
||||
source: &Path,
|
||||
hasher: &mut impl Hasher,
|
||||
in_folder: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
let resolved_options = resolve_asset_options(source, options.variant());
|
||||
|
||||
match &resolved_options {
|
||||
// Scss and JS can import files during the bundling process. We need to hash
|
||||
// both the files themselves and any imports they have
|
||||
ResolvedAssetType::Scss(options) => {
|
||||
hash_scss(options, source, hasher)?;
|
||||
}
|
||||
|
||||
ResolvedAssetType::Js(options) => {
|
||||
hash_js(options, source, hasher, !in_folder)?;
|
||||
}
|
||||
|
||||
// Otherwise, we can just hash the file contents
|
||||
ResolvedAssetType::CssModule(_)
|
||||
| ResolvedAssetType::Css(_)
|
||||
| ResolvedAssetType::Image(_)
|
||||
| ResolvedAssetType::Json
|
||||
| ResolvedAssetType::File => {
|
||||
hash_file_contents(source, hasher)?;
|
||||
}
|
||||
|
||||
// Or the folder contents recursively
|
||||
ResolvedAssetType::Folder(_) => {
|
||||
for file in std::fs::read_dir(source)?.flatten() {
|
||||
let path = file.path();
|
||||
hash_file_with_options(
|
||||
// We can't reuse the options here since they contain the source variant which no
|
||||
// longer applies to the nested files
|
||||
//
|
||||
// We don't hash nested files either since we assume the parent here is already being hashed
|
||||
// (or being opted out of hashing)
|
||||
&AssetOptions::builder()
|
||||
.with_hash_suffix(false)
|
||||
.into_asset_options(),
|
||||
&path,
|
||||
hasher,
|
||||
true,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn hash_file_contents(source: &Path, hasher: &mut impl Hasher) -> anyhow::Result<()> {
|
||||
// Otherwise, open the file to get its contents
|
||||
let mut file = std::fs::File::open(source)?;
|
||||
|
||||
// We add a hash to the end of the file so it is invalidated when the bundled version of the file changes
|
||||
// The hash includes the file contents, the options, and the version of manganis. From the macro, we just
|
||||
// know the file contents, so we only include that hash
|
||||
let mut buffer = [0; 8192];
|
||||
loop {
|
||||
let read = file.read(&mut buffer)?;
|
||||
if read == 0 {
|
||||
break;
|
||||
}
|
||||
hasher.write(&buffer[..read]);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add a hash to the asset, or log an error if it fails
|
||||
pub(crate) fn add_hash_to_asset(asset: &mut BundledAsset) {
|
||||
let source = asset.absolute_source_path();
|
||||
match AssetHash::hash_file_contents(asset.options(), source) {
|
||||
Ok(hash) => {
|
||||
let options = *asset.options();
|
||||
|
||||
// Set the bundled path to the source path with the hash appended before the extension
|
||||
let source_path = PathBuf::from(source);
|
||||
let Some(file_name) = source_path.file_name() else {
|
||||
tracing::error!("Failed to get file name from path: {source}");
|
||||
return;
|
||||
};
|
||||
|
||||
// The output extension path is the extension set by the options
|
||||
// or the extension of the source file if we don't recognize the file
|
||||
let mut ext = asset.options().extension().map(Into::into).or_else(|| {
|
||||
source_path
|
||||
.extension()
|
||||
.map(|ext| ext.to_string_lossy().to_string())
|
||||
});
|
||||
|
||||
// Rewrite scss as css
|
||||
if let Some("scss" | "sass") = ext.as_deref() {
|
||||
ext = Some("css".to_string());
|
||||
}
|
||||
|
||||
let hash = hash.bytes();
|
||||
let hash = hash
|
||||
.iter()
|
||||
.map(|byte| format!("{byte:x}"))
|
||||
.collect::<String>();
|
||||
let file_stem = source_path.file_stem().unwrap_or(file_name);
|
||||
let mut bundled_path = if asset.options().hash_suffix() {
|
||||
PathBuf::from(format!("{}-dxh{hash}", file_stem.to_string_lossy()))
|
||||
} else {
|
||||
PathBuf::from(file_stem)
|
||||
};
|
||||
|
||||
if let Some(ext) = ext {
|
||||
// Push the extension to the bundled path. There may be multiple extensions (e.g. .js.map)
|
||||
// with one left after the file_stem is extracted above so we need to push the extension
|
||||
// instead of setting it
|
||||
bundled_path.as_mut_os_string().push(format!(".{ext}"));
|
||||
}
|
||||
|
||||
let bundled_path = bundled_path.to_string_lossy().to_string();
|
||||
|
||||
*asset = BundledAsset::new(source, &bundled_path, options);
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to hash asset {source}: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user