Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restore manganis optimizations #3195

Merged
merged 46 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
3fb6d4f
Create a crate for constant serialization of config structs for manganis
ealmloff Nov 11, 2024
349acab
use SerializeConst for the image asset builder
ealmloff Nov 11, 2024
3fe59ed
switch to a declarative macro for assets
ealmloff Nov 11, 2024
4db9c9a
clean up asset macro a bit
ealmloff Nov 11, 2024
b6533d0
add serializable options for each asset type
ealmloff Nov 14, 2024
d6c23b4
serialize const vec
ealmloff Nov 14, 2024
a346486
Add unique path formatting
ealmloff Nov 14, 2024
96043f1
implement the new manganis macro
ealmloff Nov 16, 2024
123b429
Merge branch 'main' into restore-manganis
ealmloff Nov 16, 2024
5faf8d5
optimize assets in the CLI
ealmloff Nov 18, 2024
a6aebfa
Fix clippy
ealmloff Nov 18, 2024
2c2c941
Fix assets with dioxus formattable
ealmloff Nov 18, 2024
f0e57c2
reduce fuzzing test limits
ealmloff Nov 18, 2024
32460a4
use the new syntax in examples
ealmloff Nov 18, 2024
7d92da5
fix formatting
ealmloff Nov 18, 2024
3904749
Final doc and test pass on const-serialize
ealmloff Nov 18, 2024
cc40767
fix avif support
ealmloff Nov 18, 2024
2d43173
Fix manganis doc tests
ealmloff Nov 18, 2024
000d143
cache asset optimization
ealmloff Nov 18, 2024
fa304a3
Split out asset and bundled asset
ealmloff Nov 18, 2024
713954d
Merge branch 'main' into restore-manganis
ealmloff Nov 18, 2024
0a8726f
make hash pointer width independent
ealmloff Nov 18, 2024
bb3ad52
remove const serialize cargo lock
ealmloff Nov 18, 2024
0502863
Fix manganis macro docs
ealmloff Nov 18, 2024
df38d3d
all tests passing
ealmloff Nov 18, 2024
712e9e3
add constvec::is_empty method to fix clippy lint
ealmloff Nov 18, 2024
59438c6
remove nasm feature
ealmloff Nov 18, 2024
8b22f6b
simplify test_rsplit_once test so typos passes
ealmloff Nov 18, 2024
50d0d41
fix range syntax for stable
ealmloff Nov 18, 2024
06985f7
revert example change from clippy fix
ealmloff Nov 18, 2024
67eaa4b
remove dioxus-static-site-generation workspace dependency
ealmloff Nov 18, 2024
0eaca01
always accept unix paths
ealmloff Nov 18, 2024
fb46b91
fix windows path seperator
ealmloff Nov 20, 2024
280b4de
fix folder asset hash
ealmloff Nov 20, 2024
c5b60a7
Optimize assets in a blocking task
ealmloff Nov 20, 2024
a7f7ed8
Fix asset options docs
ealmloff Nov 20, 2024
7d0dba1
Document Asset and BundledAsset
ealmloff Nov 20, 2024
d5b0809
move manganis back into it's own folder
ealmloff Nov 20, 2024
da5ee69
simplify the linker macro a bit
ealmloff Nov 20, 2024
14e5e53
add more docs to AssetParser expansion
ealmloff Nov 20, 2024
812a37c
fix manganis core doc test
ealmloff Nov 20, 2024
46c1312
add image format helpers
ealmloff Nov 20, 2024
d8b370c
Fill in new cargo.tomls
ealmloff Nov 20, 2024
627fb07
fix folders with explicit options
ealmloff Nov 21, 2024
9889aa0
Split by both unix and windows path separators and take the smallest…
ealmloff Nov 21, 2024
1e70dbc
fix string length
ealmloff Nov 21, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 }
Comment on lines +108 to +143
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it really would not be terrible to cut out the asset system (Manifest, optimizer, etc) into its own crate again... and maybe with some feature flags so we can gate dev mode dx to not have to include all this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. I would like to pull out asset optimization and resolution into a separate crate, but I don't want to block the release and it would take longer to make an more stable API. How about a follow up PR after more pressing issues like the base path are fixed? I have an issue ready once this is merged

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
Loading