Support public dir (#4783)
* Add optional static_dir support - add optional application.static_dir field defaulting to none - copy configured static directory into web output during build/serve - document config knob and add unit tests - force full rebuild when static_dir contents change - sync removes stale outputs and logs copy activity - track static manifest next to platform dir Tests: cargo fmt * default to public dir to match vite * static_dir -> public_dir * wip * deser as none... * wip... use .manifest.json * implement dumb form of asset manifest generation * implement manifest using asset system directly * cleanup * dont create extra asset dir for server --------- Co-authored-by: Evan Almloff <evanalmloff@gmail.com> Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
This commit is contained in:
10 files changed
+245
-78
No files matched your search
@@ -3,7 +3,7 @@ use manganis::AssetOptions;
|
|||||||
use manganis_core::BundledAsset;
|
use manganis_core::BundledAsset;
|
||||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{BTreeMap, HashSet};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ pub use hash::add_hash_to_asset;
|
|||||||
#[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize)]
|
||||||
pub struct AssetManifest {
|
pub struct AssetManifest {
|
||||||
/// Map of bundled asset name to the asset itself
|
/// Map of bundled asset name to the asset itself
|
||||||
assets: HashMap<PathBuf, HashSet<BundledAsset>>,
|
assets: BTreeMap<PathBuf, HashSet<BundledAsset>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AssetManifest {
|
impl AssetManifest {
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ name = "project_name"
|
|||||||
# `build` & `serve` output path
|
# `build` & `serve` output path
|
||||||
out_dir = "dist"
|
out_dir = "dist"
|
||||||
|
|
||||||
# The static resource path
|
# Static files copied verbatim into the built app (set to enable, e.g. "public")
|
||||||
asset_dir = "public"
|
public_dir = "public"
|
||||||
|
|
||||||
[web.app]
|
[web.app]
|
||||||
|
|
||||||
|
|||||||
@@ -60,9 +60,11 @@ name = "project-name"
|
|||||||
# Currently supported platforms: web, desktop
|
# Currently supported platforms: web, desktop
|
||||||
default_platform = "web"
|
default_platform = "web"
|
||||||
|
|
||||||
|
# Optional: enable copying from a static directory (e.g. "public")
|
||||||
|
public_dir = "public"
|
||||||
|
|
||||||
[web.app]
|
[web.app]
|
||||||
title = "Hello"
|
title = "Hello"
|
||||||
|
|
||||||
[web.resource.dev]
|
[web.resource.dev]
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
//! The build manifest for `dx` applications, containing metadata about the build including
|
||||||
|
//! the CLI version, Rust version, and all bundled assets.
|
||||||
|
//!
|
||||||
|
//! We eventually plan to use this manifest to support tighter integration with deployment platforms
|
||||||
|
//! and CDNs.
|
||||||
|
//!
|
||||||
|
//! This manifest contains the list of assets, rust version, and cli version used to build the app.
|
||||||
|
//! Eventually, we might want to expand this to include more metadata about the build, including
|
||||||
|
//! build time, target platform, etc.
|
||||||
|
|
||||||
|
use dioxus_cli_opt::AssetManifest;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Default, Serialize, Deserialize)]
|
||||||
|
pub struct AppManifest {
|
||||||
|
/// Stable since 0.7.0
|
||||||
|
pub cli_version: String,
|
||||||
|
|
||||||
|
/// Stable since 0.7.0
|
||||||
|
pub rust_version: String,
|
||||||
|
|
||||||
|
/// Stable since 0.7.0
|
||||||
|
pub assets: AssetManifest,
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
mod assets;
|
mod assets;
|
||||||
mod builder;
|
mod builder;
|
||||||
mod context;
|
mod context;
|
||||||
|
mod manifest;
|
||||||
mod patch;
|
mod patch;
|
||||||
mod pre_render;
|
mod pre_render;
|
||||||
mod request;
|
mod request;
|
||||||
@@ -19,6 +20,7 @@ mod tools;
|
|||||||
pub(crate) use assets::*;
|
pub(crate) use assets::*;
|
||||||
pub(crate) use builder::*;
|
pub(crate) use builder::*;
|
||||||
pub(crate) use context::*;
|
pub(crate) use context::*;
|
||||||
|
pub(crate) use manifest::*;
|
||||||
pub(crate) use patch::*;
|
pub(crate) use patch::*;
|
||||||
pub(crate) use pre_render::*;
|
pub(crate) use pre_render::*;
|
||||||
pub(crate) use request::*;
|
pub(crate) use request::*;
|
||||||
|
|||||||
@@ -320,9 +320,9 @@
|
|||||||
//! - xbuild: <https://github.com/rust-mobile/xbuild/blob/master/xbuild/src/command/build.rs>
|
//! - xbuild: <https://github.com/rust-mobile/xbuild/blob/master/xbuild/src/command/build.rs>
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AndroidTools, BuildContext, BuildId, BundleFormat, DioxusConfig, Error, LinkAction,
|
AndroidTools, AppManifest, BuildContext, BuildId, BundleFormat, DioxusConfig, Error,
|
||||||
LinkerFlavor, Platform, Renderer, Result, RustcArgs, TargetArgs, TraceSrc, WasmBindgen,
|
LinkAction, LinkerFlavor, Platform, Renderer, Result, RustcArgs, TargetArgs, TraceSrc,
|
||||||
WasmOptConfig, Workspace, DX_RUSTC_WRAPPER_ENV_VAR,
|
WasmBindgen, WasmOptConfig, Workspace, DX_RUSTC_WRAPPER_ENV_VAR,
|
||||||
};
|
};
|
||||||
use anyhow::{bail, Context};
|
use anyhow::{bail, Context};
|
||||||
use cargo_metadata::diagnostic::Diagnostic;
|
use cargo_metadata::diagnostic::Diagnostic;
|
||||||
@@ -333,8 +333,8 @@ use dioxus_cli_config::{APP_TITLE_ENV, ASSET_ROOT_ENV};
|
|||||||
use dioxus_cli_opt::{process_file_to, AssetManifest};
|
use dioxus_cli_opt::{process_file_to, AssetManifest};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use krates::{cm::TargetKind, NodeId};
|
use krates::{cm::TargetKind, NodeId};
|
||||||
use manganis::AssetOptions;
|
use manganis::{AssetOptions, BundledAsset};
|
||||||
use manganis_core::AssetVariant;
|
use manganis_core::{AssetOptionsBuilder, AssetVariant};
|
||||||
use rayon::prelude::{IntoParallelRefIterator, ParallelIterator};
|
use rayon::prelude::{IntoParallelRefIterator, ParallelIterator};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{borrow::Cow, ffi::OsString};
|
use std::{borrow::Cow, ffi::OsString};
|
||||||
@@ -1295,13 +1295,40 @@ impl BuildRequest {
|
|||||||
/// Collect the assets from the final executable and modify the binary in place to point to the right
|
/// Collect the assets from the final executable and modify the binary in place to point to the right
|
||||||
/// hashed asset location.
|
/// hashed asset location.
|
||||||
async fn collect_assets(&self, exe: &Path, ctx: &BuildContext) -> Result<AssetManifest> {
|
async fn collect_assets(&self, exe: &Path, ctx: &BuildContext) -> Result<AssetManifest> {
|
||||||
// walk every file in the incremental cache dir, reading and inserting items into the manifest.
|
|
||||||
let mut manifest = AssetManifest::default();
|
|
||||||
|
|
||||||
// And then add from the exe directly, just in case it's LTO compiled and has no incremental cache
|
// And then add from the exe directly, just in case it's LTO compiled and has no incremental cache
|
||||||
if !self.skip_assets {
|
if self.skip_assets {
|
||||||
ctx.status_extracting_assets();
|
return Ok(AssetManifest::default());
|
||||||
manifest = super::assets::extract_assets_from_file(exe).await?;
|
}
|
||||||
|
|
||||||
|
ctx.status_extracting_assets();
|
||||||
|
|
||||||
|
let mut manifest = super::assets::extract_assets_from_file(exe).await?;
|
||||||
|
|
||||||
|
// If the user has a public dir, we submit all the entries there as assets too
|
||||||
|
//
|
||||||
|
// These don't receive a hash in their filename, since they're user-provided static assets
|
||||||
|
// We only do this for web builds
|
||||||
|
if matches!(self.bundle, BundleFormat::Web)
|
||||||
|
&& matches!(ctx.mode, BuildMode::Base { .. } | BuildMode::Fat)
|
||||||
|
{
|
||||||
|
if let Some(dir) = self.user_public_dir() {
|
||||||
|
for entry in walkdir::WalkDir::new(&dir)
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|e| e.ok())
|
||||||
|
.filter(|e| e.file_type().is_file())
|
||||||
|
{
|
||||||
|
let from = entry.path().to_path_buf();
|
||||||
|
let relative_path = from.strip_prefix(&dir).unwrap();
|
||||||
|
let to = format!("../{}", relative_path.display());
|
||||||
|
manifest.insert_asset(BundledAsset::new(
|
||||||
|
from.to_string_lossy().as_ref(),
|
||||||
|
to.as_str(),
|
||||||
|
AssetOptionsBuilder::new()
|
||||||
|
.with_hash_suffix(false)
|
||||||
|
.into_asset_options(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(manifest)
|
Ok(manifest)
|
||||||
@@ -1494,14 +1521,13 @@ impl BuildRequest {
|
|||||||
.map(|a| asset_dir.join(a.bundled_path()))
|
.map(|a| asset_dir.join(a.bundled_path()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// The CLI creates a .version file in the asset dir to keep track of what version of the optimizer
|
// The CLI creates a .manifest.json file in the asset dir to keep track of the assets and
|
||||||
// the asset was processed. If that version doesn't match the CLI version, we need to re-optimize
|
// other build metadata. If we can't parse this file (or the CLI version changed), then we
|
||||||
// all assets.
|
// want to re-copy all the assets rather than trying to do an incremental update.
|
||||||
let version_file = self.asset_optimizer_version_file();
|
let clear_cache = self
|
||||||
let clear_cache = std::fs::read_to_string(&version_file)
|
.load_manifest()
|
||||||
.ok()
|
.map(|manifest| manifest.cli_version != crate::VERSION.as_str())
|
||||||
.filter(|s| s == crate::VERSION.as_str())
|
.unwrap_or(true);
|
||||||
.is_none();
|
|
||||||
if clear_cache {
|
if clear_cache {
|
||||||
keep_bundled_output_paths.clear();
|
keep_bundled_output_paths.clear();
|
||||||
}
|
}
|
||||||
@@ -1511,20 +1537,6 @@ impl BuildRequest {
|
|||||||
keep_bundled_output_paths
|
keep_bundled_output_paths
|
||||||
);
|
);
|
||||||
|
|
||||||
// use walkdir::WalkDir;
|
|
||||||
// for item in WalkDir::new(&asset_dir).into_iter().flatten() {
|
|
||||||
// // If this asset is in the manifest, we don't need to remove it
|
|
||||||
// let canonicalized = dunce::canonicalize(item.path())?;
|
|
||||||
// if !keep_bundled_output_paths.contains(canonicalized.as_path()) {
|
|
||||||
// // Remove empty dirs, remove files not in the manifest
|
|
||||||
// if item.file_type().is_dir() && item.path().read_dir()?.next().is_none() {
|
|
||||||
// std::fs::remove_dir(item.path())?;
|
|
||||||
// } else {
|
|
||||||
// std::fs::remove_file(item.path())?;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// todo(jon): we also want to eventually include options for each asset's optimization and compression, which we currently aren't
|
// todo(jon): we also want to eventually include options for each asset's optimization and compression, which we currently aren't
|
||||||
let mut assets_to_transfer = vec![];
|
let mut assets_to_transfer = vec![];
|
||||||
|
|
||||||
@@ -1589,7 +1601,7 @@ impl BuildRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write the version file so we know what version of the optimizer we used
|
// Write the version file so we know what version of the optimizer we used
|
||||||
std::fs::write(self.asset_optimizer_version_file(), crate::VERSION.as_str())?;
|
self.write_app_manifest(assets).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -3116,8 +3128,18 @@ impl BuildRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a workdir for the given platform
|
||||||
|
/// This can be used as a temporary directory for the build, but in an observable way such that
|
||||||
|
/// you can see the files in the directory via `target`
|
||||||
|
///
|
||||||
|
/// target/dx/build/app/web/
|
||||||
|
/// target/dx/build/app/web/public/
|
||||||
|
/// target/dx/build/app/web/server.exe
|
||||||
fn platform_dir(&self) -> PathBuf {
|
fn platform_dir(&self) -> PathBuf {
|
||||||
self.build_dir(self.bundle, self.release)
|
self.internal_out_dir()
|
||||||
|
.join(&self.main_target)
|
||||||
|
.join(if self.release { "release" } else { "debug" })
|
||||||
|
.join(self.bundle.build_folder_name())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn platform_exe_name(&self) -> String {
|
fn platform_exe_name(&self) -> String {
|
||||||
@@ -3416,20 +3438,6 @@ impl BuildRequest {
|
|||||||
dir
|
dir
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a workdir for the given platform
|
|
||||||
/// This can be used as a temporary directory for the build, but in an observable way such that
|
|
||||||
/// you can see the files in the directory via `target`
|
|
||||||
///
|
|
||||||
/// target/dx/build/app/web/
|
|
||||||
/// target/dx/build/app/web/public/
|
|
||||||
/// target/dx/build/app/web/server.exe
|
|
||||||
pub(crate) fn build_dir(&self, bundle: BundleFormat, release: bool) -> PathBuf {
|
|
||||||
self.internal_out_dir()
|
|
||||||
.join(&self.main_target)
|
|
||||||
.join(if release { "release" } else { "debug" })
|
|
||||||
.join(bundle.build_folder_name())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// target/dx/bundle/app/
|
/// target/dx/bundle/app/
|
||||||
/// target/dx/bundle/app/blah.app
|
/// target/dx/bundle/app/blah.app
|
||||||
/// target/dx/bundle/app/blah.exe
|
/// target/dx/bundle/app/blah.exe
|
||||||
@@ -4422,12 +4430,13 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{
|
|||||||
.join("main")
|
.join("main")
|
||||||
.join("assets"),
|
.join("assets"),
|
||||||
|
|
||||||
|
// We put assets in public/assets for server apps
|
||||||
|
BundleFormat::Server => self.root_dir().join("public").join("assets"),
|
||||||
|
|
||||||
// everyone else is soooo normal, just app/assets :)
|
// everyone else is soooo normal, just app/assets :)
|
||||||
BundleFormat::Web
|
BundleFormat::Web | BundleFormat::Ios | BundleFormat::Windows | BundleFormat::Linux => {
|
||||||
| BundleFormat::Ios
|
self.root_dir().join("assets")
|
||||||
| BundleFormat::Windows
|
}
|
||||||
| BundleFormat::Linux
|
|
||||||
| BundleFormat::Server => self.root_dir().join("assets"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4482,9 +4491,21 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{
|
|||||||
.with_extension("wasm")
|
.with_extension("wasm")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the path to the asset optimizer version file
|
/// Get the path to the app manifest file
|
||||||
pub(crate) fn asset_optimizer_version_file(&self) -> PathBuf {
|
///
|
||||||
self.platform_dir().join(".cli-version")
|
/// This includes metadata about the build such as the bundle format, target triple, features, etc.
|
||||||
|
/// Manifests are only written by the `PRIMARY` build.
|
||||||
|
pub(crate) fn app_manifest(&self) -> PathBuf {
|
||||||
|
self.platform_dir().join(".manifest.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn load_manifest(&self) -> Result<AppManifest> {
|
||||||
|
let manifest_path = self.app_manifest();
|
||||||
|
let manifest_data = std::fs::read_to_string(&manifest_path)
|
||||||
|
.with_context(|| format!("Failed to read manifest at {:?}", &manifest_path))?;
|
||||||
|
let manifest: AppManifest = serde_json::from_str(&manifest_data)
|
||||||
|
.with_context(|| format!("Failed to parse manifest at {:?}", &manifest_path))?;
|
||||||
|
Ok(manifest)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check for tooling that might be required for this build.
|
/// Check for tooling that might be required for this build.
|
||||||
@@ -4882,6 +4903,34 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolve the configured public directory relative to the crate, if any.
|
||||||
|
pub(crate) fn user_public_dir(&self) -> Option<PathBuf> {
|
||||||
|
let path = self.config.application.public_dir.as_ref()?;
|
||||||
|
|
||||||
|
if path.as_os_str().is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(if path.is_absolute() {
|
||||||
|
path.clone()
|
||||||
|
} else {
|
||||||
|
self.crate_dir().join(path)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn path_is_in_public_dir(&self, path: &Path) -> bool {
|
||||||
|
let Some(static_dir) = self.user_public_dir() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Canonicalize when possible so we work with editors that use tmp files
|
||||||
|
let canonical_static =
|
||||||
|
dunce::canonicalize(&static_dir).unwrap_or_else(|_| static_dir.clone());
|
||||||
|
let canonical_path = dunce::canonicalize(path).unwrap_or_else(|_| path.to_path_buf());
|
||||||
|
|
||||||
|
canonical_path.starts_with(&canonical_static)
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the base path from the config or None if this is not a web or server build
|
/// Get the base path from the config or None if this is not a web or server build
|
||||||
pub(crate) fn base_path(&self) -> Option<&str> {
|
pub(crate) fn base_path(&self) -> Option<&str> {
|
||||||
self.base_path
|
self.base_path
|
||||||
@@ -5289,4 +5338,17 @@ We checked the folders:
|
|||||||
TEAM_IDENTIFIER = mbfile.team_identifier[0],
|
TEAM_IDENTIFIER = mbfile.team_identifier[0],
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn write_app_manifest(&self, assets: &AssetManifest) -> Result<()> {
|
||||||
|
let manifest = AppManifest {
|
||||||
|
assets: assets.clone(),
|
||||||
|
cli_version: crate::VERSION.to_string(),
|
||||||
|
rust_version: self.workspace.rustc_version.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let manifest_path = self.app_manifest();
|
||||||
|
std::fs::write(&manifest_path, serde_json::to_string_pretty(&manifest)?)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,10 @@ pub(crate) struct ApplicationConfig {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub(crate) out_dir: Option<PathBuf>,
|
pub(crate) out_dir: Option<PathBuf>,
|
||||||
|
|
||||||
|
#[serde(default = "public_dir_default")]
|
||||||
|
#[serde(deserialize_with = "empty_string_is_none")]
|
||||||
|
pub(crate) public_dir: Option<PathBuf>,
|
||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub(crate) tailwind_input: Option<PathBuf>,
|
pub(crate) tailwind_input: Option<PathBuf>,
|
||||||
|
|
||||||
@@ -43,3 +47,19 @@ pub(crate) struct ApplicationConfig {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub(crate) android_min_sdk_version: Option<u32>,
|
pub(crate) android_min_sdk_version: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn public_dir_default() -> Option<PathBuf> {
|
||||||
|
Some("public".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn empty_string_is_none<'de, D>(deserializer: D) -> Result<Option<PathBuf>, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let opt: Option<String> = Option::deserialize(deserializer)?;
|
||||||
|
match opt {
|
||||||
|
Some(s) if s.is_empty() => Ok(None),
|
||||||
|
Some(s) => Ok(Some(PathBuf::from(s))),
|
||||||
|
None => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ impl Default for DioxusConfig {
|
|||||||
Self {
|
Self {
|
||||||
application: ApplicationConfig {
|
application: ApplicationConfig {
|
||||||
out_dir: None,
|
out_dir: None,
|
||||||
|
public_dir: Some("public".into()),
|
||||||
tailwind_input: None,
|
tailwind_input: None,
|
||||||
tailwind_output: None,
|
tailwind_output: None,
|
||||||
ios_info_plist: None,
|
ios_info_plist: None,
|
||||||
@@ -55,3 +56,42 @@ impl Default for DioxusConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn static_dir_defaults_to_public() {
|
||||||
|
let config = DioxusConfig::default();
|
||||||
|
assert_eq!(
|
||||||
|
config.application.public_dir,
|
||||||
|
Some(std::path::PathBuf::from("public"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn static_dir_can_be_overridden() {
|
||||||
|
let source = r#"
|
||||||
|
[application]
|
||||||
|
public_dir = "public2"
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let config: DioxusConfig = toml::from_str(source).expect("parse config");
|
||||||
|
assert_eq!(
|
||||||
|
config.application.public_dir.as_deref(),
|
||||||
|
Some(std::path::Path::new("public2"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn static_dir_can_be_disabled() {
|
||||||
|
let source = r#"
|
||||||
|
[application]
|
||||||
|
public_dir = ""
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let config: DioxusConfig = toml::from_str(source).expect("parse config");
|
||||||
|
assert_eq!(config.application.public_dir.as_deref(), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -379,6 +379,12 @@ impl AppServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If it's in the public dir, we sync it and trigger a full rebuild
|
||||||
|
if self.client.build.path_is_in_public_dir(path) {
|
||||||
|
needs_full_rebuild = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// If it's a rust file, we want to hotreload it using the filemap
|
// If it's a rust file, we want to hotreload it using the filemap
|
||||||
if ext == "rs" {
|
if ext == "rs" {
|
||||||
// And grabout the contents
|
// And grabout the contents
|
||||||
@@ -1028,6 +1034,15 @@ impl AppServer {
|
|||||||
let mut watched_crates = self.local_dependencies(crate_package);
|
let mut watched_crates = self.local_dependencies(crate_package);
|
||||||
watched_crates.push(crate_dir);
|
watched_crates.push(crate_dir);
|
||||||
|
|
||||||
|
// Watch the `public` directory if this is the client crate
|
||||||
|
if self.client.build.crate_package == crate_package {
|
||||||
|
if let Some(public_dir) = self.client.build.user_public_dir() {
|
||||||
|
if public_dir.exists() {
|
||||||
|
watched_paths.push(public_dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Now, watch all the folders in the crates, but respecting their respective ignore files
|
// Now, watch all the folders in the crates, but respecting their respective ignore files
|
||||||
for krate_root in watched_crates {
|
for krate_root in watched_crates {
|
||||||
// Build the ignore builder for this crate, but with our default ignore list as well
|
// Build the ignore builder for this crate, but with our default ignore list as well
|
||||||
|
|||||||
@@ -222,32 +222,34 @@ const MAX_STR_SIZE: usize = 256;
|
|||||||
|
|
||||||
/// A string that is stored in a constant sized buffer that can be serialized and deserialized at compile time
|
/// A string that is stored in a constant sized buffer that can be serialized and deserialized at compile time
|
||||||
#[derive(Eq, PartialEq, PartialOrd, Clone, Copy, Hash)]
|
#[derive(Eq, PartialEq, PartialOrd, Clone, Copy, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
pub struct ConstStr {
|
pub struct ConstStr {
|
||||||
#[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
|
|
||||||
bytes: [u8; MAX_STR_SIZE],
|
bytes: [u8; MAX_STR_SIZE],
|
||||||
len: u32,
|
len: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
mod serde_bytes {
|
mod serde_bytes {
|
||||||
use serde::{Deserialize, Serializer};
|
use serde::{Deserialize, Serialize, Serializer};
|
||||||
|
|
||||||
pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
|
use crate::ConstStr;
|
||||||
where
|
|
||||||
S: Serializer,
|
impl Serialize for ConstStr {
|
||||||
{
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
serializer.serialize_bytes(bytes)
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(self.as_str())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<[u8; super::MAX_STR_SIZE], D::Error>
|
impl<'de> Deserialize<'de> for ConstStr {
|
||||||
where
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
D: serde::Deserializer<'de>,
|
where
|
||||||
{
|
D: serde::Deserializer<'de>,
|
||||||
let bytes = Vec::<u8>::deserialize(deserializer)?;
|
{
|
||||||
bytes
|
let s = String::deserialize(deserializer)?;
|
||||||
.try_into()
|
Ok(ConstStr::new(&s))
|
||||||
.map_err(|_| serde::de::Error::custom("Failed to convert bytes to a fixed size array"))
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user