Fix bundling/path errors, sidecar support, iOS/Android simulator support, asset hotreload fixes, serverfn hotrelad, native tailwind support (#2779)

* wip: fix manganis import path
* upgrade tauri bundler, convert todo!() to unimplemented!()
* feat: mobile hotreloading + ios asset configuration
* remove oid demo and its associated env vars
* nuke all the things that cause cache thrashing
* swap to Asset type
* add some more logs
* display impl for attrvalue
* dont panic on collect failure
* cut down web deps to 150
* clean up deps in a lot of places, simplify build scripts
* clean up asset resolution and cli-dev profile
* wire up absolute paths for manganis asset in a particular mode
* move document related things around to shorten compile times
* move most things to `document::Item` and then decompose the cli-config
* switch link to stylesheet for clarity in examples
* move manganis workspace example to examples folder
* decompose manganis-cli-support
* dont need mobile demo anymore
* remove gloo dialogs
* html doesnt need document
* rename hotreload to devtools
* really clean up the html crate
* fix weird rsx spacing
* clean up desktop to use callback
* clean up document a bit
* re-wire up devsocket
* fix utf css
* yeeeet that js out of here
* synthetic web system works
* web crate almost done being cleaned up
* desktop mostly cleaned up too
* clean up cli a bit
* more cli cleanup
* cli builds again
* clean up cli, inline structs, cut down number of unique types where possible
* hotreload works again for desktop and this time, mobile too
* cfg out tungstenite
* devserver err
* more refactor to cli builder
* switch to unbounded_send
* new structure is much cleaner
* add http serve subcommand
* bundled hotreload
* kick stylsheets
* clean up a bit more, split up eventloop
* amazingly serve seems done
* change from pub to pub(crate) in cli
* remove tools
* bit more polish to cli
* fix issue with join
* gracefully handle fullstack without a server
* fullstack mobile demo
* fix launch function, move projects into example-projects folder
* hoist examples
* add "run" command
* clean up launch
* remove old manual websocket receiver
* doctor command
* allow desktop to scroll
* cut apart router crate
* dont put launch in prelude
* use dioxus::launch where possible
* rename rsx, cut out hotreload tests
* remove liveview project
* bump native
* fix compile for renderers
* move sync event response out of interpreter
* move render in serve
* rollback settings change
* cli compiles, huzzah!
* change uris for asset
* fix asset
* new tui screen
* new cargo-like tui works
* very very verty close
* it works! very small bug with incorrect grapheme calc
* Clean up devserver a bit
* status sytem
* tidy up debug filters
* clean up logging situation
* Fix a number of bugs with log printing
* new printing system is more reliable
* wire up more stuff
* things working but fullstack is having issues
* fullstack works again!
* hotreloading bundled assets works again
* bundled hotreload and beginnings of macos bundling
* Hotreload desktop
* combined server builds
* add build handle
* fix fullstack assets
* make open async, add some hooks for ios
* migrate filemap to runner
* wip global crate system
* fixup bundles + organize asset
* fix asset location bug
* all the bundled reloading!
* open ios simulator!
* full hotreload support for mobile + serverfn
* basic cleanups
* clean up dx
* Move filemap
* fix cutting of newlines
* assets workibg, some android
* hoist wry/tao
* use sync locks and headers to fix issues with android
* desktop -> mac/win/linux with alias
* better logging
* feat: workspace (entire computer!) hotreload
* should rebuild toggle, ios simulator bootup
* proper mobile support in launch
* more robust handling of assets
* fix cargo
* bring back some of tauri bundle
* make warnings go away, clippy happy on cli
* some final clippy cleanups
* fmt
* move manganis to its own folder
* upgrade bundle to stable
* drastically slim down manganis, prepping for merge
* typos, failing test, docsrs config
* remove static gen test
* nix static gen test
* we use --platform web instead of --platform fullstack now
* only bind dev urls in desktop/mobile
* install gtk
* nix static gen
* split build dir by app name
This commit is contained in:
Jonathan Kelley authored and GitHub committed 2024-10-25 17:23:45 -07:00
1 parent ef436e4ed0
commit 7ec3453ca3
156 files changed
+11544 -9005

No files matched your search

@@ -0,0 +1,10 @@
[package]
name = "manganis-core"
edition = "2021"
version.workspace = true
# be careful with dependencies you add here - these need to get compiled for the proc macro and therefore
# prevent the main code from compiling!
[dependencies]
serde_json = "1.0"
serde = { workspace = true, features = ["derive"] }
@@ -0,0 +1,85 @@
use serde::{Deserialize, Serialize};
use std::{
hash::{Hash, Hasher},
path::PathBuf,
time::SystemTime,
};
/// The location we'll write to the link section - needs to be serializable
///
/// This basically is 1:1 with `manganis/Asset` but with more metadata to be useful to the macro and cli
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub struct ResourceAsset {
/// The input path `/assets/blah.css`
pub input: PathBuf,
/// The canonicalized asset
///
/// `Users/dioxus/dev/app/assets/blah.css`
pub absolute: PathBuf,
/// The post-bundle name of the asset - do we include the `assets` name?
///
/// `blahcss123.css`
pub bundled: String,
}
#[derive(Debug)]
pub struct AssetError {}
impl ResourceAsset {
pub fn parse_any(raw: &str) -> Result<Self, AssetError> {
// get the location where the asset is absolute, relative to
//
// IE
// /users/dioxus/dev/app/
// is the root of
// /users/dioxus/dev/app/assets/blah.css
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")
.map(PathBuf::from)
.unwrap();
// 1. the input file should be a pathbuf
let input = PathBuf::from(raw);
// 2. absolute path to the asset
let absolute = manifest_dir
.join(raw.trim_start_matches('/'))
.canonicalize()
.unwrap();
// 3. the bundled path is the unique name of the asset
let bundled = Self::make_unique_name(absolute.clone());
Ok(Self {
input,
absolute,
bundled,
})
}
fn make_unique_name(file_path: PathBuf) -> String {
// Create a hasher
let mut hash = std::collections::hash_map::DefaultHasher::new();
// Open the file to get its options
let file = std::fs::File::open(&file_path).unwrap();
let metadata = file.metadata().unwrap();
let modified = metadata.modified().unwrap_or(SystemTime::UNIX_EPOCH);
// Hash a bunch of metadata
// name, options, modified time, and maybe the version of our crate
// Hash the last time the file was updated and the file source. If either of these change, we need to regenerate the unique name
modified.hash(&mut hash);
file_path.hash(&mut hash);
let uuid = hash.finish();
let file_name = file_path.file_stem().unwrap().to_string_lossy();
let extension = file_path
.extension()
.map(|f| f.to_string_lossy())
.unwrap_or_default();
format!("{file_name}-{uuid:x}.{extension}")
}
}
@@ -0,0 +1,5 @@
mod asset;
mod linker;
pub use asset::*;
pub use linker::*;
@@ -0,0 +1,69 @@
/// Information about the manganis link section for a given platform
#[derive(Debug, Clone, Copy)]
pub struct LinkSection {
/// The link section we pass to the static
pub link_section: &'static str,
/// The name of the section we find in the binary
pub name: &'static str,
}
impl LinkSection {
/// The list of link sections for all supported platforms
pub const ALL: &'static [&'static LinkSection] =
&[Self::WASM, Self::MACOS, Self::WINDOWS, Self::ILLUMOS];
/// Returns the link section used in linux, android, fuchsia, psp, freebsd, and wasm32
pub const WASM: &'static LinkSection = &LinkSection {
link_section: "manganis",
name: "manganis",
};
/// Returns the link section used in macOS, iOS, tvOS
pub const MACOS: &'static LinkSection = &LinkSection {
link_section: "__DATA,manganis,regular,no_dead_strip",
name: "manganis",
};
/// Returns the link section used in windows
pub const WINDOWS: &'static LinkSection = &LinkSection {
link_section: "mg",
name: "mg",
};
/// Returns the link section used in illumos
pub const ILLUMOS: &'static LinkSection = &LinkSection {
link_section: "set_manganis",
name: "set_manganis",
};
/// The link section used on the current platform
pub const CURRENT: &'static LinkSection = {
#[cfg(any(
target_os = "none",
target_os = "linux",
target_os = "android",
target_os = "fuchsia",
target_os = "psp",
target_os = "freebsd",
target_arch = "wasm32"
))]
{
Self::WASM
}
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos"))]
{
Self::MACOS
}
#[cfg(target_os = "windows")]
{
Self::WINDOWS
}
#[cfg(target_os = "illumos")]
{
Self::ILLUMOS
}
};
}
@@ -0,0 +1,26 @@
[package]
name = "manganis-macro"
version.workspace = true
edition = "2021"
authors = ["Evan Almloff"]
description = "Ergonomic, automatic, cross crate asset collection and optimization"
license = "MIT OR Apache-2.0"
repository = "https://github.com/DioxusLabs/manganis/"
homepage = "https://dioxuslabs.com"
keywords = ["assets"]
[lib]
proc-macro = true
# be careful with dependencies you add here - these need to get compiled for the proc macro and therefore
# prevent the main code from compiling!
[dependencies]
proc-macro2 = { version = "1.0" }
quote = "1.0"
syn = { version = "2.0", features = ["full", "extra-traits"] }
serde_json = "1.0"
serde = { workspace = true, features = ["derive"] }
manganis-core = { workspace = true }
[features]
default = []
@@ -0,0 +1,3 @@
# Manganis Macro
This crate contains the macro used to interact with the Manganis asset system.
@@ -0,0 +1,96 @@
use manganis_core::ResourceAsset;
use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens};
use syn::{
parse::{Parse, ParseStream},
LitStr,
};
pub struct AssetParser {
/// The asset itself
asset: ResourceAsset,
/// The source of the trailing options
options: TokenStream2,
}
impl Parse for AssetParser {
// we can take
//
// This gives you the Asset type - it's generic and basically unrefined
// ```
// asset!("myfile.png")
// ```
//
// To narrow the type, use a method call to get the refined type
// ```
// asset!(
// "myfile.png",
// asset::image()
// .format(ImageType::Jpg)
// .size(512, 512)
// )
// ```
//
// But we need to decide the hint first before parsing the options
fn parse(input: ParseStream) -> syn::Result<Self> {
// And then parse the options
let src = input.parse::<LitStr>()?;
let asset = ResourceAsset::parse_any(&src.value()).unwrap();
let options = input.parse()?;
Ok(Self { asset, options })
}
}
impl ToTokens for AssetParser {
// Need to generate:
//
// - 1. absolute file path on the user's system: `/users/dioxus/dev/project/assets/blah.css`
// - 2. original input in case that's useful: `../blah.css`
// - 3. path relative to the CARGO_MANIFEST_DIR - and then we'll add a `/`: `/assets/blah.css
// - 4. file from which this macro was called: `/users/dioxus/dev/project/src/lib.rs`
// - 5: The link section containing all this data
// - 6: the input tokens such that the builder gets validated by the const code
// - 7: the bundled name `/blahcss123.css`
//
// Not that we'll use everything, but at least we have this metadata for more post-processing.
//
// For now, `2` and `3` will be the same since we don't support relative paths... a bit of
// a limitation from rust itself. We technically could support them but not without some hoops
// to jump through
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
// 1. the link section itself
let link_section = crate::generate_link_section(&self.asset);
// 2. original
let input = self.asset.input.display().to_string();
// 3. resolved on the user's system
let local = self.asset.absolute.display().to_string();
// 4. bundled
let bundled = self.asset.bundled.to_string();
// 5. source tokens
let option_source = &self.options;
tokens.extend(quote! {
Asset::new(
{
#link_section
manganis::Asset {
// "/assets/blah.css"
input: #input,
// "/users/dioxus/dev/app/assets/blah.css"
local: #local,
// "/blahcss123.css"
bundled: #bundled,
}
}
) #option_source
})
}
}
@@ -0,0 +1,19 @@
#![doc = include_str!("../README.md")]
#![deny(missing_docs)]
use proc_macro::TokenStream;
use quote::{quote, ToTokens};
use syn::parse_macro_input;
pub(crate) mod asset;
pub(crate) mod linker;
use linker::generate_link_section;
/// The asset macro collects assets that will be included in the final binary
#[proc_macro]
pub fn asset(input: TokenStream) -> TokenStream {
let asset = parse_macro_input!(input as asset::AssetParser);
quote! { #asset }.into_token_stream().into()
}
@@ -0,0 +1,21 @@
use proc_macro2::TokenStream as TokenStream2;
use serde::Serialize;
/// this new approach will store the assets descriptions *inside the executable*.
/// The trick is to use the `link_section` attribute.
/// We force rust to store a json representation of the asset description
/// inside a particular region of the binary, with the label "manganis".
/// After linking, the "manganis" sections of the different executables will be merged.
pub fn generate_link_section(asset: &impl Serialize) -> TokenStream2 {
let position = proc_macro2::Span::call_site();
let asset_description = serde_json::to_string(asset).unwrap();
let len = asset_description.as_bytes().len();
let asset_bytes = syn::LitByteStr::new(asset_description.as_bytes(), position);
let section_name = syn::LitStr::new(manganis_core::LinkSection::CURRENT.link_section, position);
quote::quote! {
#[link_section = #section_name]
#[used]
static ASSET: [u8; #len] = * #asset_bytes;
}
}
+41
View File
@@ -0,0 +1,41 @@
[package]
# Manganese is a rusting catalyst. Manganis makes it faster to collect rust assets (and has almost no google search results)
name = "manganis"
version.workspace = true
authors = ["Evan Almloff"]
edition = "2021"
description = "Ergonomic, automatic, cross crate asset collection and optimization"
license = "MIT OR Apache-2.0"
repository = "https://github.com/DioxusLabs/manganis/"
homepage = "https://dioxuslabs.com"
keywords = ["assets"]
[lib]
[dependencies]
manganis-macro = { workspace = true, optional = true }
dioxus-core-types = { workspace = true }
# dunce = "1.0.2"
# [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]
# core-foundation = "0.10.0"
# [target.'cfg(target_os = "macos")'.dependencies]
# core-foundation = "0.9.3"
# [target.'cfg(target_os ="macos")'.dependencies]
# infer = { workspace = true }
# dirs = "5.0.1"
# infer = { workspace = true }
# manganis-common = { workspace = true }
once_cell = "1.19.0"
dunce = "1.0.2"
serde = { version = "1.0.183", features = ["derive"] }
anyhow = "1"
base64 = { workspace = true }
[features]
default = ["macro"]
html = []
# url-encoding = ["manganis-macro/url-encoding"]
macro = ["dep:manganis-macro"]
+71
View File
@@ -0,0 +1,71 @@
use dioxus_core_types::DioxusFormattable;
use std::path::PathBuf;
/// Asset
#[derive(Debug, PartialEq, PartialOrd, Clone, Copy, Hash)]
pub struct Asset {
/// The input URI given to the macro
pub input: &'static str,
/// The absolute path to the asset on the filesystem
pub local: &'static str,
/// The asset location after its been bundled
///
/// `blah-123.css``
pub bundled: &'static str,
}
impl Asset {
/// Create a new asset
pub const fn new(self) -> Self {
self
}
/// Get the path to the asset
pub fn path(&self) -> PathBuf {
PathBuf::from(self.input.to_string())
}
/// Get the path to the asset
pub fn relative_path(&self) -> PathBuf {
PathBuf::from(self.input.trim_start_matches('/').to_string())
}
/// Return a canonicalized path to the asset
///
/// Attempts to resolve it against an `assets` folder in the current directory.
/// If that doesn't exist, it will resolve against the cargo manifest dir
pub fn resolve(&self) -> PathBuf {
// If the asset is relative, we resolve the asset at the current directory
if !dioxus_core_types::is_bundled_app() {
return PathBuf::from(self.local);
}
// Otherwise presumably we're bundled and we can use the bundled path
PathBuf::from("/assets/").join(PathBuf::from(self.bundled.trim_start_matches('/')))
}
}
impl From<Asset> for String {
fn from(value: Asset) -> Self {
value.to_string()
}
}
impl From<Asset> for Option<String> {
fn from(value: Asset) -> Self {
Some(value.to_string())
}
}
impl std::fmt::Display for Asset {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.resolve().display())
}
}
impl DioxusFormattable for Asset {
fn format(&self) -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Owned(self.to_string())
}
}
+31
View File
@@ -0,0 +1,31 @@
use crate::Asset;
/// This is basically a compile-time version of ResourceAsset
/// A struct that contains the relative and absolute paths of an asset
#[derive(Debug, PartialEq, PartialOrd, Clone, Hash)]
pub struct FolderAsset {
src: Asset,
}
impl Asset {
/// Create a new folder using `Asset` as the base type
pub const fn folder(self) -> FolderAsset {
FolderAsset::new(self)
}
}
impl FolderAsset {
/// Create a new folder asset from an `Asset`
pub const fn new(src: Asset) -> Self {
Self { src }
}
}
/// The builder for [`FolderAsset`]
pub struct FolderAssetBuilder;
/// Create an folder asset from the local path
#[allow(unused)]
pub const fn folder(src: &'static str) -> FolderAssetBuilder {
FolderAssetBuilder {}
}
+141
View File
@@ -0,0 +1,141 @@
use dioxus_core_types::DioxusFormattable;
use crate::Asset;
/// An image asset that is built by the [`asset!`] macro
#[derive(Debug, PartialEq, PartialOrd, Clone, Hash, Copy)]
pub struct ImageAsset {
/// The path to the image
asset: Asset,
/// A low quality preview of the image that is URL encoded
preview: Option<&'static str>,
/// A caption for the image
caption: Option<&'static str>,
/// The format of the image
format: Option<ImageType>,
}
impl Asset {
/// Convert this asset into an image asset
pub const fn image(self) -> ImageAsset {
ImageAsset::new(self)
}
}
impl ImageAsset {
/// Creates a new image asset
pub const fn new(path: Asset) -> Self {
Self {
asset: path,
preview: None,
caption: None,
format: None,
}
}
/// Returns the path to the image
pub const fn path(&self) -> &'static str {
self.asset.bundled
}
/// Returns the preview of the image
pub const fn preview(&self) -> Option<&'static str> {
self.preview
}
/// Sets the preview of the image
pub const fn with_preview(self, preview: Option<&'static str>) -> Self {
Self { preview, ..self }
}
/// Returns the caption of the image
pub const fn caption(&self) -> Option<&'static str> {
self.caption
}
/// Sets the caption of the image
pub const fn with_caption(self, caption: Option<&'static str>) -> Self {
Self { caption, ..self }
}
/// Sets the format of the image
pub const fn format(self, format: Option<ImageType>) -> Self {
Self { format, ..self }
}
}
impl std::ops::Deref for ImageAsset {
type Target = Asset;
fn deref(&self) -> &Self::Target {
&self.asset
}
}
impl std::fmt::Display for ImageAsset {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.asset.fmt(f)
}
}
impl DioxusFormattable for ImageAsset {
fn format(&self) -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Owned(self.to_string())
}
}
/// The type of an image. You can read more about the tradeoffs between image formats [here](https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types)
#[derive(Debug, PartialEq, PartialOrd, Clone, Copy, Hash)]
pub enum ImageType {
/// A png image. Png images cannot contain transparency and tend to compress worse than other formats
Png,
/// A jpg image. Jpg images can contain transparency and tend to compress better than png images
Jpg,
/// A webp image. Webp images can contain transparency and tend to compress better than jpg images
Webp,
/// An avif image. Avif images can compress slightly better than webp images but are not supported by all browsers
Avif,
}
/// A builder for an image asset. This must be used in the [`asset!`] macro.
///
/// > **Note**: This will do nothing outside of the `asset!` macro
pub struct ImageAssetBuilder;
impl ImageAssetBuilder {
/// Sets the format of the image
#[allow(unused)]
pub const fn format(self, format: ImageType) -> Self {
Self
}
/// Sets the size of the image
#[allow(unused)]
pub const fn size(self, x: u32, y: u32) -> Self {
Self
}
/// Make the image use a low quality preview
#[allow(unused)]
pub const fn low_quality_preview(self) -> Self {
Self
}
/// Make the image preloaded
#[allow(unused)]
pub const fn preload(self) -> Self {
Self
}
/// Make the image URL encoded
#[allow(unused)]
pub const fn url_encoded(self) -> Self {
Self
}
}
/// Create an image asset from the local path or url to the image
#[allow(unused)]
pub const fn image(path: &'static str) -> ImageAssetBuilder {
ImageAssetBuilder
}
+15
View File
@@ -0,0 +1,15 @@
//! Manganis is a Rust library for bundling assets into a final binary.
#![deny(missing_docs)]
#[cfg(feature = "macro")]
pub use manganis_macro::*;
mod folder;
pub use folder::*;
mod images;
pub use images::*;
mod builder;
pub use builder::*;