Skip to content

Commit

Permalink
Restore manganis optimizations (#3195)
Browse files Browse the repository at this point in the history
* Create a crate for constant serialization of config structs for manganis
* use SerializeConst for the image asset builder
* switch to a declarative macro for assets
* add serializable options for each asset type
* implement the new manganis macro
* optimize assets in the CLI
* Fix assets with dioxus formattable
* reduce fuzzing test limits
* use the new syntax in examples
* fix avif support
* Fix manganis doc tests
* cache asset optimization
* Split out asset and bundled asset
* make hash pointer width independent
* Fix manganis macro docs
* add constvec::is_empty method to fix clippy lint
* remove nasm feature
* simplify test_rsplit_once test so typos passes
* revert example change from clippy fix
* always accept unix paths
* fix windows path seperator
* fix folder asset hash
* Optimize assets in a blocking task
* Fix asset options docs
* Document Asset and BundledAsset
* simplify the linker macro a bit
* add more docs to AssetParser expansion
* add image format helpers
* Split by  both unix and windows path separators and take the smallest one
  • Loading branch information
ealmloff authored Nov 26, 2024
1 parent 31d72db commit f9617c8
Show file tree
Hide file tree
Showing 64 changed files with 6,250 additions and 721 deletions.
2,506 changes: 2,310 additions & 196 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ members = [
"packages/devtools-types",
"packages/isrg",
"packages/rsx-hotreload",
"packages/const-serialize",
"packages/const-serialize-macro",
"packages/dx-wire-format",

# Playwright tests
Expand All @@ -71,9 +73,8 @@ members = [

# manganis
"packages/manganis/manganis",
"packages/manganis/manganis-macro",
"packages/manganis/manganis-core",

"packages/manganis/manganis-macro",

# Full project examples
"example-projects/fullstack-hackernews",
Expand Down Expand Up @@ -135,6 +136,8 @@ dioxus-devtools = { path = "packages/devtools", version = "0.6.0-alpha.5" }
dioxus-devtools-types = { path = "packages/devtools-types", version = "0.6.0-alpha.5" }
dioxus-fullstack = { path = "packages/fullstack", version = "0.6.0-alpha.5" }
dioxus_server_macro = { path = "packages/server-macro", version = "0.6.0-alpha.5", default-features = false }
const-serialize = { path = "packages/const-serialize", version = "0.6.0-alpha.5" }
const-serialize-macro = { path = "packages/const-serialize-macro", version = "0.6.0-alpha.5" }
dioxus-dx-wire-format = { path = "packages/dx-wire-format", version = "0.6.0-alpha.5" }

lazy-js-bundle = { path = "packages/lazy-js-bundle", version = "0.6.0-alpha.5" }
Expand Down
70 changes: 70 additions & 0 deletions packages/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,76 @@ prettyplease = { workspace = true }
brotli = "6.0.0"
ignore = "0.4.22"
env_logger = { workspace = true }
const-serialize = { workspace = true, features = ["serde"] }

# Image compression/conversion
# - JPEG
mozjpeg = { version = "0.10.7", default-features = false, features = [
"parallel",
] }
# - PNG
imagequant = "4.2.0"
png = "0.17.9"
# Image format/conversion
image = { version = "0.25", features = ["avif"] }

# CSS Minification
lightningcss = "1.0.0-alpha.60"

# Js minification - swc has introduces minor versions with breaking changes in the past so we pin all of their crates
swc = "=0.283.0"
swc_allocator = { version = "=0.1.8", default-features = false }
swc_atoms = { version = "=0.6.7", default-features = false }
swc_cached = { version = "=0.3.20", default-features = false }
swc_common = { version = "=0.37.5", default-features = false }
swc_compiler_base = { version = "=0.19.0", default-features = false }
swc_config = { version = "=0.1.15", default-features = false }
swc_config_macro = { version = "=0.1.4", default-features = false }
swc_ecma_ast = { version = "=0.118.2", default-features = false }
swc_ecma_codegen = { version = "=0.155.1", default-features = false }
swc_ecma_codegen_macros = { version = "=0.7.7", default-features = false }
swc_ecma_compat_bugfixes = { version = "=0.12.0", default-features = false }
swc_ecma_compat_common = { version = "=0.11.0", default-features = false }
swc_ecma_compat_es2015 = { version = "=0.12.0", default-features = false }
swc_ecma_compat_es2016 = { version = "=0.12.0", default-features = false }
swc_ecma_compat_es2017 = { version = "=0.12.0", default-features = false }
swc_ecma_compat_es2018 = { version = "=0.12.0", default-features = false }
swc_ecma_compat_es2019 = { version = "=0.12.0", default-features = false }
swc_ecma_compat_es2020 = { version = "=0.12.0", default-features = false }
swc_ecma_compat_es2021 = { version = "=0.12.0", default-features = false }
swc_ecma_compat_es2022 = { version = "=0.12.0", default-features = false }
swc_ecma_compat_es3 = { version = "=0.12.0", default-features = false }
swc_ecma_ext_transforms = { version = "=0.120.0", default-features = false }
swc_ecma_lints = { version = "=0.100.0", default-features = false }
swc_ecma_loader = { version = "=0.49.1", default-features = false }
swc_ecma_minifier = { version = "=0.204.0", default-features = false }
swc_ecma_parser = { version = "=0.149.1", default-features = false }
swc_ecma_preset_env = { version = "=0.217.0", default-features = false, features = [
"serde",
] }
swc_ecma_transforms = { version = "=0.239.0", default-features = false }
swc_ecma_transforms_base = { version = "=0.145.0", default-features = false }
swc_ecma_transforms_classes = { version = "=0.134.0", default-features = false }
swc_ecma_transforms_compat = { version = "=0.171.0", default-features = false }
swc_ecma_transforms_macros = { version = "=0.5.5", default-features = false }
swc_ecma_transforms_module = { version = "=0.190.0", default-features = false }
swc_ecma_transforms_optimization = { version = "=0.208.0", default-features = false }
swc_ecma_transforms_proposal = { version = "=0.178.0", default-features = false }
swc_ecma_transforms_react = { version = "=0.191.0", default-features = false }
swc_ecma_transforms_typescript = { version = "=0.198.1", default-features = false }
swc_ecma_usage_analyzer = { version = "=0.30.3", default-features = false }
swc_ecma_utils = { version = "=0.134.2", default-features = false }
swc_ecma_visit = { version = "=0.104.8", default-features = false }
swc_eq_ignore_macros = { version = "=0.1.4", default-features = false }
swc_error_reporters = { version = "=0.21.0", default-features = false }
swc_fast_graph = { version = "=0.25.0", default-features = false }
swc_macros_common = { version = "=0.3.13", default-features = false }
swc_node_comments = { version = "=0.24.0", default-features = false }
swc_timer = { version = "=0.25.0", default-features = false }
swc_trace_macro = { version = "=0.1.3", default-features = false }
swc_transform_common = { version = "=0.1.1", default-features = false }
swc_typescript = { version = "=0.5.0", default-features = false }
swc_visit = { version = "=0.6.2", default-features = false }

tracing-subscriber = { version = "0.3.18", features = ["std", "env-filter", "json"] }
console-subscriber = { version = "0.3.0", optional = true }
Expand Down
42 changes: 42 additions & 0 deletions packages/cli/src/assets/css.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use std::path::Path;

use anyhow::Context;
use lightningcss::{
printer::PrinterOptions,
stylesheet::{MinifyOptions, ParserOptions, StyleSheet},
};
use manganis_core::CssAssetOptions;

pub(crate) fn process_css(
css_options: &CssAssetOptions,
source: &Path,
output_path: &Path,
) -> anyhow::Result<()> {
let css = std::fs::read_to_string(source)?;

let css = if css_options.minified() {
minify_css(&css)
} else {
css
};

std::fs::write(output_path, css).with_context(|| {
format!(
"Failed to write css to output location: {}",
output_path.display()
)
})?;

Ok(())
}

pub(crate) fn minify_css(css: &str) -> String {
let mut stylesheet = StyleSheet::parse(css, ParserOptions::default()).unwrap();
stylesheet.minify(MinifyOptions::default()).unwrap();
let printer = PrinterOptions {
minify: true,
..Default::default()
};
let res = stylesheet.to_css(printer).unwrap();
res.code
}
75 changes: 75 additions & 0 deletions packages/cli/src/assets/file.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use anyhow::Context;
use manganis_core::{AssetOptions, CssAssetOptions, ImageAssetOptions, JsAssetOptions};
use std::path::Path;

use super::{
css::process_css, folder::process_folder, image::process_image, js::process_js,
json::process_json,
};

/// Process a specific file asset with the given options reading from the source and writing to the output path
pub(crate) fn process_file_to(
options: &AssetOptions,
source: &Path,
output_path: &Path,
) -> anyhow::Result<()> {
// If the file already exists, then we must have a file with the same hash
// already. The hash has the file contents and options, so if we find a file
// with the same hash, we probably already created this file in the past
if output_path.exists() {
return Ok(());
}
if let Some(parent) = output_path.parent() {
if !parent.exists() {
std::fs::create_dir_all(parent)?;
}
}
match options {
AssetOptions::Unknown => match source.extension().map(|e| e.to_string_lossy()).as_deref() {
Some("css") => {
process_css(&CssAssetOptions::new(), source, output_path)?;
}
Some("js") => {
process_js(&JsAssetOptions::new(), source, output_path)?;
}
Some("json") => {
process_json(source, output_path)?;
}
Some("jpg" | "jpeg" | "png" | "webp" | "avif") => {
process_image(&ImageAssetOptions::new(), source, output_path)?;
}
None if source.is_dir() => {
process_folder(source, output_path)?;
}
Some(_) | None => {
let source_file = std::fs::File::open(source)?;
let mut reader = std::io::BufReader::new(source_file);
let output_file = std::fs::File::create(output_path)?;
let mut writer = std::io::BufWriter::new(output_file);
std::io::copy(&mut reader, &mut writer).with_context(|| {
format!(
"Failed to write file to output location: {}",
output_path.display()
)
})?;
}
},
AssetOptions::Css(options) => {
process_css(options, source, output_path)?;
}
AssetOptions::Js(options) => {
process_js(options, source, output_path)?;
}
AssetOptions::Image(options) => {
process_image(options, source, output_path)?;
}
AssetOptions::Folder(_) => {
process_folder(source, output_path)?;
}
_ => {
tracing::warn!("Unknown asset options: {:?}", options);
}
}

Ok(())
}
41 changes: 41 additions & 0 deletions packages/cli/src/assets/folder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use std::path::Path;

use rayon::iter::{IntoParallelRefIterator, ParallelIterator};

use super::file::process_file_to;

/// Process a folder, optimizing and copying all assets into the output folder
pub fn process_folder(source: &Path, output_folder: &Path) -> anyhow::Result<()> {
// Create the folder
std::fs::create_dir_all(output_folder)?;

// Then optimize children
let files: Vec<_> = std::fs::read_dir(source)
.into_iter()
.flatten()
.flatten()
.collect();

files.par_iter().try_for_each(|file| {
let file = file.path();
let metadata = file.metadata()?;
let output_path = output_folder.join(file.strip_prefix(source)?);
if metadata.is_dir() {
process_folder(&file, &output_path)
} else {
process_file_minimal(&file, &output_path)
}
})?;

Ok(())
}

/// Optimize a file without changing any of its contents significantly (e.g. by changing the extension)
fn process_file_minimal(input_path: &Path, output_path: &Path) -> anyhow::Result<()> {
process_file_to(
&manganis_core::AssetOptions::Unknown,
input_path,
output_path,
)?;
Ok(())
}
23 changes: 23 additions & 0 deletions packages/cli/src/assets/image/jpg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use image::{DynamicImage, EncodableLayout};
use std::{
io::{BufWriter, Write},
path::Path,
};

pub(crate) fn compress_jpg(image: DynamicImage, output_location: &Path) -> anyhow::Result<()> {
let mut comp = mozjpeg::Compress::new(mozjpeg::ColorSpace::JCS_EXT_RGBX);
let width = image.width() as usize;
let height = image.height() as usize;

comp.set_size(width, height);
let mut comp = comp.start_compress(Vec::new())?; // any io::Write will work

comp.write_scanlines(image.to_rgba8().as_bytes())?;

let jpeg_bytes = comp.finish()?;

let file = std::fs::File::create(output_location)?;
let w = &mut BufWriter::new(file);
w.write_all(&jpeg_bytes)?;
Ok(())
}
62 changes: 62 additions & 0 deletions packages/cli/src/assets/image/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use std::path::Path;

use anyhow::Context;
use jpg::compress_jpg;
use manganis_core::{ImageAssetOptions, ImageFormat, ImageSize};
use png::compress_png;

mod jpg;
mod png;

pub(crate) fn process_image(
image_options: &ImageAssetOptions,
source: &Path,
output_path: &Path,
) -> anyhow::Result<()> {
let mut image = image::ImageReader::new(std::io::Cursor::new(&*std::fs::read(source)?))
.with_guessed_format()?
.decode();

if let Ok(image) = &mut image {
if let ImageSize::Manual { width, height } = image_options.size() {
*image = image.resize_exact(width, height, image::imageops::FilterType::Lanczos3);
}
}

match (image, image_options.format()) {
(image, ImageFormat::Png) => {
compress_png(image?, output_path);
}
(image, ImageFormat::Jpg) => {
compress_jpg(image?, output_path)?;
}
(Ok(image), ImageFormat::Avif) => {
if let Err(error) = image.save(output_path) {
tracing::error!("Failed to save avif image: {} with path {}. You must have the avif feature enabled to use avif assets", error, output_path.display());
}
}
(Ok(image), ImageFormat::Webp) => {
if let Err(err) = image.save(output_path) {
tracing::error!("Failed to save webp image: {}. You must have the avif feature enabled to use webp assets", err);
}
}
(Ok(image), _) => {
image.save(output_path)?;
}
// If we can't decode the image or it is of an unknown type, we just copy the file
_ => {
let source_file = std::fs::File::open(source)?;
let mut reader = std::io::BufReader::new(source_file);
let output_file = std::fs::File::create(output_path)?;
let mut writer = std::io::BufWriter::new(output_file);
std::io::copy(&mut reader, &mut writer).with_context(|| {
format!(
"Failed to write image to output location: {}",
output_path.display()
)
})?;
}
}

Ok(())
}
Loading

0 comments on commit f9617c8

Please sign in to comment.