From 511f2e2013c53f7658fe3dfdc25547c868fb6df5 Mon Sep 17 00:00:00 2001 From: Tim Jentzsch Date: Sun, 22 Dec 2024 20:46:17 +0100 Subject: [PATCH 01/15] Begin setting up bundling process --- src/run/args.rs | 4 +++ src/run/bundle.rs | 71 +++++++++++++++++++++++++++++++++++++++++++++++ src/run/mod.rs | 1 + 3 files changed, 76 insertions(+) create mode 100644 src/run/bundle.rs diff --git a/src/run/args.rs b/src/run/args.rs index b358f81..73dda22 100644 --- a/src/run/args.rs +++ b/src/run/args.rs @@ -50,4 +50,8 @@ pub struct RunWebArgs { /// Open the app in the browser. #[arg(short = 'o', long = "open", action = ArgAction::SetTrue, default_value_t = false)] pub open: bool, + + // Bundle all web artifacts into a single folder. + #[arg(short = 'b', long = "bundle", action = ArgAction::SetTrue, default_value_t = false)] + pub create_bundle: bool, } diff --git a/src/run/bundle.rs b/src/run/bundle.rs new file mode 100644 index 0000000..dda1bbd --- /dev/null +++ b/src/run/bundle.rs @@ -0,0 +1,71 @@ +use std::{ + fs, + path::{Path, PathBuf}, +}; + +use super::{cargo::metadata::Metadata, BinTarget}; + +pub struct LinkedBundle { + wasm_path: PathBuf, + js_path: PathBuf, + assets_path: Option, + custom_index: Option, +} + +pub struct PackedBundle { + path: PathBuf, +} + +pub enum WebBundle { + Linked(LinkedBundle), + Packed(PackedBundle), +} + +pub fn create_bundle( + metadata: &Metadata, + profile: &str, + bin_target: BinTarget, + packed: bool, +) -> anyhow::Result { + let assets_path = Path::new("assets"); + // The "_bg" suffix is needed to reference the bindings created by wasm_bindgen, + // instead of the artifact created directly by cargo. + let wasm_file_name = format!("{}_bg.wasm", bin_target.bin_name); + let js_file_name = format!("{}.js", bin_target.bin_name); + + let linked = LinkedBundle { + wasm_path: bin_target.artifact_directory.join(&wasm_file_name), + js_path: bin_target.artifact_directory.join(&js_file_name), + assets_path: if assets_path.exists() { + Some(assets_path.to_owned()) + } else { + None + }, + // TODO: Determine if index is customized + custom_index: None, + }; + + if packed { + let base_path = metadata + .target_directory + .join("bevy_web") + .join(profile) + .join(bin_target.bin_name); + + // Build artifacts + fs::create_dir_all(base_path.join("build"))?; + fs::copy( + linked.wasm_path, + base_path.join("build").join(&wasm_file_name), + )?; + fs::copy(linked.js_path, base_path.join("build").join(&js_file_name))?; + + // TODO: Copy assets + + // TODO: Copy index + + Ok(WebBundle::Packed(PackedBundle { path: base_path })) + } else { + Ok(WebBundle::Linked(linked)) + } +} diff --git a/src/run/mod.rs b/src/run/mod.rs index a202ef2..c671f4d 100644 --- a/src/run/mod.rs +++ b/src/run/mod.rs @@ -13,6 +13,7 @@ use crate::{ pub use self::args::RunArgs; mod args; +mod bundle; mod serve; pub fn run(args: &RunArgs) -> anyhow::Result<()> { From 6b60424f0bc98cc07d42a7a169750a26b2e2a631 Mon Sep 17 00:00:00 2001 From: Tim Jentzsch Date: Sun, 22 Dec 2024 20:51:05 +0100 Subject: [PATCH 02/15] Include static index file in bundle --- src/run/bundle.rs | 29 +++++++++++++++++++++++++++-- src/run/serve.rs | 22 +--------------------- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/src/run/bundle.rs b/src/run/bundle.rs index dda1bbd..e13a149 100644 --- a/src/run/bundle.rs +++ b/src/run/bundle.rs @@ -5,11 +5,16 @@ use std::{ use super::{cargo::metadata::Metadata, BinTarget}; +pub enum Index { + File(PathBuf), + Static(&'static str), +} + pub struct LinkedBundle { wasm_path: PathBuf, js_path: PathBuf, assets_path: Option, - custom_index: Option, + index: Index, } pub struct PackedBundle { @@ -42,7 +47,7 @@ pub fn create_bundle( None }, // TODO: Determine if index is customized - custom_index: None, + index: Index::Static(default_index(&bin_target)), }; if packed { @@ -69,3 +74,23 @@ pub fn create_bundle( Ok(WebBundle::Linked(linked)) } } + +/// Create the default `index.html` if the user didn't provide one. +pub fn default_index(bin_target: &BinTarget) -> &'static str { + let template = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/assets/web/index.html" + )); + + // Insert correct path to JS bindings + let index = template.replace( + "./build/bevy_app.js", + format!("./build/{}.js", bin_target.bin_name).as_str(), + ); + + // Only static strings can be served in the web app, + // so we leak the string memory to convert it to a static reference. + // PERF: This is assumed to be used only once and is needed for the rest of the app running + // time, making the memory leak acceptable. + Box::leak(index.into_boxed_str()) +} diff --git a/src/run/serve.rs b/src/run/serve.rs index 5ca5343..daf1367 100644 --- a/src/run/serve.rs +++ b/src/run/serve.rs @@ -2,7 +2,7 @@ use actix_web::{rt, web, App, HttpResponse, HttpServer, Responder}; use std::path::Path; -use super::BinTarget; +use super::{bundle::default_index, BinTarget}; /// Serve a static HTML file with the given content. async fn serve_static_html(content: &'static str) -> impl Responder { @@ -15,26 +15,6 @@ async fn serve_static_html(content: &'static str) -> impl Responder { .body(content) } -/// Create the default `index.html` if the user didn't provide one. -fn default_index(bin_target: &BinTarget) -> &'static str { - let template = include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/assets/web/index.html" - )); - - // Insert correct path to JS bindings - let index = template.replace( - "./build/bevy_app.js", - format!("./build/{}.js", bin_target.bin_name).as_str(), - ); - - // Only static strings can be served in the web app, - // so we leak the string memory to convert it to a static reference. - // PERF: This is assumed to be used only once and is needed for the rest of the app running - // time, making the memory leak acceptable. - Box::leak(index.into_boxed_str()) -} - /// Launch a web server running the Bevy app. pub(crate) fn serve(bin_target: BinTarget, port: u16) -> anyhow::Result<()> { let index_html = default_index(&bin_target); From 92e8a347666b9e2a5e209d1ef66e5a3a2dbdd481 Mon Sep 17 00:00:00 2001 From: Tim Jentzsch Date: Sun, 22 Dec 2024 20:54:04 +0100 Subject: [PATCH 03/15] Copy index file into bundle --- src/run/bundle.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/run/bundle.rs b/src/run/bundle.rs index e13a149..3f5df2c 100644 --- a/src/run/bundle.rs +++ b/src/run/bundle.rs @@ -67,7 +67,15 @@ pub fn create_bundle( // TODO: Copy assets - // TODO: Copy index + let index_path = base_path.join("index.html"); + match linked.index { + Index::File(path) => { + fs::copy(path, index_path)?; + } + Index::Static(contents) => { + fs::write(index_path, contents)?; + } + } Ok(WebBundle::Packed(PackedBundle { path: base_path })) } else { From 7e00d354f389244876cbcba7ea85be0a413aef59 Mon Sep 17 00:00:00 2001 From: Tim Jentzsch Date: Mon, 23 Dec 2024 10:56:20 +0100 Subject: [PATCH 04/15] Add copying of assets folder --- Cargo.lock | 7 +++++ Cargo.toml | 3 +++ src/run/bundle.rs | 69 +++++++++++++++++++++++++++-------------------- 3 files changed, 50 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a933dfa..562612d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -492,6 +492,7 @@ dependencies = [ "cargo-generate", "clap", "dialoguer", + "fs_extra", "regex", "reqwest", "semver", @@ -1536,6 +1537,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures-channel" version = "0.3.31" diff --git a/Cargo.toml b/Cargo.toml index 36ae76d..4f0d2e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,3 +42,6 @@ actix-web = "4.9.0" # Opening the app in the browser webbrowser = "1.0.2" + +# Copying directories +fs_extra = "1.3.0" diff --git a/src/run/bundle.rs b/src/run/bundle.rs index 3f5df2c..3a68eb5 100644 --- a/src/run/bundle.rs +++ b/src/run/bundle.rs @@ -26,7 +26,7 @@ pub enum WebBundle { Packed(PackedBundle), } -pub fn create_bundle( +pub fn create_web_bundle( metadata: &Metadata, profile: &str, bin_target: BinTarget, @@ -50,37 +50,48 @@ pub fn create_bundle( index: Index::Static(default_index(&bin_target)), }; - if packed { - let base_path = metadata - .target_directory - .join("bevy_web") - .join(profile) - .join(bin_target.bin_name); - - // Build artifacts - fs::create_dir_all(base_path.join("build"))?; - fs::copy( - linked.wasm_path, - base_path.join("build").join(&wasm_file_name), + if !packed { + return Ok(WebBundle::Linked(linked)); + } + + let base_path = metadata + .target_directory + .join("bevy_web") + .join(profile) + .join(bin_target.bin_name); + + // Build artifacts + fs::create_dir_all(base_path.join("build"))?; + fs::copy( + linked.wasm_path, + base_path.join("build").join(&wasm_file_name), + )?; + fs::copy(linked.js_path, base_path.join("build").join(&js_file_name))?; + + // Assets + if let Some(assets_path) = linked.assets_path { + fs_extra::dir::copy( + assets_path, + base_path.join("assets"), + &fs_extra::dir::CopyOptions { + overwrite: true, + ..Default::default() + }, )?; - fs::copy(linked.js_path, base_path.join("build").join(&js_file_name))?; - - // TODO: Copy assets - - let index_path = base_path.join("index.html"); - match linked.index { - Index::File(path) => { - fs::copy(path, index_path)?; - } - Index::Static(contents) => { - fs::write(index_path, contents)?; - } - } + } - Ok(WebBundle::Packed(PackedBundle { path: base_path })) - } else { - Ok(WebBundle::Linked(linked)) + // Index + let index_path = base_path.join("index.html"); + match linked.index { + Index::File(path) => { + fs::copy(path, index_path)?; + } + Index::Static(contents) => { + fs::write(index_path, contents)?; + } } + + Ok(WebBundle::Packed(PackedBundle { path: base_path })) } /// Create the default `index.html` if the user didn't provide one. From abea5e5fd927e6e351a8035c8c16247ef2e1140e Mon Sep 17 00:00:00 2001 From: Tim Jentzsch Date: Mon, 23 Dec 2024 11:33:57 +0100 Subject: [PATCH 05/15] Serve app from bundle info --- src/run/bundle.rs | 40 +++++++++++++++---------- src/run/mod.rs | 12 +++++++- src/run/serve.rs | 75 ++++++++++++++++++++++++++--------------------- 3 files changed, 78 insertions(+), 49 deletions(-) diff --git a/src/run/bundle.rs b/src/run/bundle.rs index 3a68eb5..1725e62 100644 --- a/src/run/bundle.rs +++ b/src/run/bundle.rs @@ -1,26 +1,32 @@ use std::{ + ffi::OsString, fs, path::{Path, PathBuf}, }; use super::{cargo::metadata::Metadata, BinTarget}; +#[derive(Debug, Clone)] pub enum Index { - File(PathBuf), + Folder(PathBuf), Static(&'static str), } +#[derive(Debug, Clone)] pub struct LinkedBundle { - wasm_path: PathBuf, - js_path: PathBuf, - assets_path: Option, - index: Index, + pub build_artifact_path: PathBuf, + pub wasm_file_name: OsString, + pub js_file_name: OsString, + pub assets_path: Option, + pub index: Index, } +#[derive(Debug, Clone)] pub struct PackedBundle { - path: PathBuf, + pub path: PathBuf, } +#[derive(Debug, Clone)] pub enum WebBundle { Linked(LinkedBundle), Packed(PackedBundle), @@ -35,12 +41,13 @@ pub fn create_web_bundle( let assets_path = Path::new("assets"); // The "_bg" suffix is needed to reference the bindings created by wasm_bindgen, // instead of the artifact created directly by cargo. - let wasm_file_name = format!("{}_bg.wasm", bin_target.bin_name); - let js_file_name = format!("{}.js", bin_target.bin_name); + let wasm_file_name = OsString::from(format!("{}_bg.wasm", bin_target.bin_name)); + let js_file_name = OsString::from(format!("{}.js", bin_target.bin_name)); let linked = LinkedBundle { - wasm_path: bin_target.artifact_directory.join(&wasm_file_name), - js_path: bin_target.artifact_directory.join(&js_file_name), + build_artifact_path: bin_target.artifact_directory.clone(), + wasm_file_name, + js_file_name, assets_path: if assets_path.exists() { Some(assets_path.to_owned()) } else { @@ -63,10 +70,13 @@ pub fn create_web_bundle( // Build artifacts fs::create_dir_all(base_path.join("build"))?; fs::copy( - linked.wasm_path, - base_path.join("build").join(&wasm_file_name), + linked.build_artifact_path.join(&linked.wasm_file_name), + base_path.join("build").join(&linked.wasm_file_name), + )?; + fs::copy( + linked.build_artifact_path.join(&linked.js_file_name), + base_path.join("build").join(&linked.js_file_name), )?; - fs::copy(linked.js_path, base_path.join("build").join(&js_file_name))?; // Assets if let Some(assets_path) = linked.assets_path { @@ -83,7 +93,7 @@ pub fn create_web_bundle( // Index let index_path = base_path.join("index.html"); match linked.index { - Index::File(path) => { + Index::Folder(path) => { fs::copy(path, index_path)?; } Index::Static(contents) => { @@ -95,7 +105,7 @@ pub fn create_web_bundle( } /// Create the default `index.html` if the user didn't provide one. -pub fn default_index(bin_target: &BinTarget) -> &'static str { +fn default_index(bin_target: &BinTarget) -> &'static str { let template = include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/assets/web/index.html" diff --git a/src/run/mod.rs b/src/run/mod.rs index c671f4d..626a468 100644 --- a/src/run/mod.rs +++ b/src/run/mod.rs @@ -1,6 +1,8 @@ use std::path::PathBuf; +use anyhow::Context; use args::RunSubcommands; +use bundle::create_web_bundle; use crate::{ build::ensure_web_setup, @@ -39,6 +41,14 @@ pub fn run(args: &RunArgs) -> anyhow::Result<()> { )?; wasm_bindgen::bundle(&bin_target)?; + let web_bundle = create_web_bundle( + &metadata, + args.profile(), + bin_target, + web_args.create_bundle, + ) + .context("Failed to create web bundle")?; + let port = web_args.port; let url = format!("http://localhost:{port}"); @@ -54,7 +64,7 @@ pub fn run(args: &RunArgs) -> anyhow::Result<()> { println!("Open your app at <{url}>!"); } - serve::serve(bin_target, port)?; + serve::serve(web_bundle, port)?; } else { // For native builds, wrap `cargo run` cargo::run::command().args(cargo_args).ensure_status()?; diff --git a/src/run/serve.rs b/src/run/serve.rs index daf1367..e4ed2ec 100644 --- a/src/run/serve.rs +++ b/src/run/serve.rs @@ -1,8 +1,7 @@ //! Serving the app locally for the browser. use actix_web::{rt, web, App, HttpResponse, HttpServer, Responder}; -use std::path::Path; -use super::{bundle::default_index, BinTarget}; +use super::bundle::{LinkedBundle, PackedBundle, WebBundle}; /// Serve a static HTML file with the given content. async fn serve_static_html(content: &'static str) -> impl Responder { @@ -16,41 +15,51 @@ async fn serve_static_html(content: &'static str) -> impl Responder { } /// Launch a web server running the Bevy app. -pub(crate) fn serve(bin_target: BinTarget, port: u16) -> anyhow::Result<()> { - let index_html = default_index(&bin_target); - +pub(crate) fn serve(web_bundle: WebBundle, port: u16) -> anyhow::Result<()> { rt::System::new().block_on( HttpServer::new(move || { let mut app = App::new(); - let bin_target = bin_target.clone(); - - // Serve the build artifacts at the `/build/*` route - // A custom `index.html` will have to call `/build/{bin_name}.js` - app = app.service( - actix_files::Files::new("/build", bin_target.artifact_directory.clone()) - // This potentially includes artifacts which we will not need, - // but we can't add the bin name to the check due to lifetime requirements - .path_filter(move |path, _| { - path.file_stem().is_some_and(|stem| { - // Using `.starts_with` instead of equality, because of the `_bg` suffix - // of the WASM bindings - stem.to_string_lossy().starts_with(&bin_target.bin_name) - }) && (path.extension().is_some_and(|ext| ext == "js") - || path.extension().is_some_and(|ext| ext == "wasm")) - }), - ); - - // If the app has an assets folder, serve it under `/assets` - if Path::new("assets").exists() { - app = app.service(actix_files::Files::new("/assets", "./assets")) - } - if Path::new("web").exists() { - // Serve the contents of the `web` folder under `/`, if it exists - app = app.service(actix_files::Files::new("/", "./web").index_file("index.html")); - } else { - // If the user doesn't provide a custom web setup, serve a default `index.html` - app = app.route("/", web::get().to(|| serve_static_html(index_html))) + match web_bundle.clone() { + WebBundle::Packed(PackedBundle { path }) => { + app = app.service(actix_files::Files::new("/", path).index_file("index.html")); + } + WebBundle::Linked(LinkedBundle { + build_artifact_path, + wasm_file_name, + js_file_name, + index, + assets_path, + }) => { + // Serve the build artifacts at the `/build/*` route + // A custom `index.html` will have to call `/build/{bin_name}.js` + app = app.service( + actix_files::Files::new("/build", build_artifact_path) + // This potentially includes artifacts which we will not need, + // but we can't add the bin name to the check due to lifetime + // requirements + .path_filter(move |path, _| { + path.file_name() == Some(&js_file_name) + || path.file_name() == Some(&wasm_file_name) + }), + ); + + // If the app has an assets folder, serve it under `/assets` + if let Some(assets_path) = assets_path { + app = app.service(actix_files::Files::new("/assets", assets_path)) + } + + match index { + super::bundle::Index::Folder(path) => { + app = app.service( + actix_files::Files::new("/", path).index_file("index.html"), + ); + } + super::bundle::Index::Static(contents) => { + app = app.route("/", web::get().to(move || serve_static_html(contents))) + } + } + } } app From 211a5589e54c31f6a12eb4d6f3e555ba1388e482 Mon Sep 17 00:00:00 2001 From: Tim Jentzsch Date: Mon, 23 Dec 2024 12:18:57 +0100 Subject: [PATCH 06/15] Handle custom web folder --- src/run/bundle.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/run/bundle.rs b/src/run/bundle.rs index 1725e62..a2ff537 100644 --- a/src/run/bundle.rs +++ b/src/run/bundle.rs @@ -8,21 +8,29 @@ use super::{cargo::metadata::Metadata, BinTarget}; #[derive(Debug, Clone)] pub enum Index { + /// The folder containing a custom index.html file. Folder(PathBuf), + /// A static string representing the index.html file. Static(&'static str), } #[derive(Debug, Clone)] pub struct LinkedBundle { + /// The path to the folder containing the WASM and JS build artifacts. pub build_artifact_path: PathBuf, + /// The name of the WASM artifact, in the build folder. pub wasm_file_name: OsString, + /// The name of the JS artifact, in the build folder. pub js_file_name: OsString, + /// The path to the Bevy assets folder, if it exists. pub assets_path: Option, + /// The index file to serve. pub index: Index, } #[derive(Debug, Clone)] pub struct PackedBundle { + /// The path to the folder containing the packed web bundle. pub path: PathBuf, } @@ -44,6 +52,8 @@ pub fn create_web_bundle( let wasm_file_name = OsString::from(format!("{}_bg.wasm", bin_target.bin_name)); let js_file_name = OsString::from(format!("{}.js", bin_target.bin_name)); + let custom_web_folder = Path::new("web"); + let linked = LinkedBundle { build_artifact_path: bin_target.artifact_directory.clone(), wasm_file_name, @@ -53,8 +63,11 @@ pub fn create_web_bundle( } else { None }, - // TODO: Determine if index is customized - index: Index::Static(default_index(&bin_target)), + index: if custom_web_folder.join("index.html").exists() { + Index::Folder(custom_web_folder.to_path_buf()) + } else { + Index::Static(default_index(&bin_target)) + }, }; if !packed { @@ -69,6 +82,7 @@ pub fn create_web_bundle( // Build artifacts fs::create_dir_all(base_path.join("build"))?; + println!("Create build folder"); fs::copy( linked.build_artifact_path.join(&linked.wasm_file_name), base_path.join("build").join(&linked.wasm_file_name), From bb3627f8ff6e7dc996204dba44271be291d685aa Mon Sep 17 00:00:00 2001 From: Tim Jentzsch Date: Mon, 23 Dec 2024 14:34:34 +0100 Subject: [PATCH 07/15] Add additional documentation --- src/run/bundle.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/run/bundle.rs b/src/run/bundle.rs index a2ff537..f9f5aaa 100644 --- a/src/run/bundle.rs +++ b/src/run/bundle.rs @@ -34,12 +34,23 @@ pub struct PackedBundle { pub path: PathBuf, } +/// A bundle of all the files needed to serve the app in the web. #[derive(Debug, Clone)] pub enum WebBundle { + /// A bundle that needs to be linked together, keeping the files at their original places. + /// Most useful during development, to avoid additional copy operations and duplication. Linked(LinkedBundle), + /// A bundle packed into a single folder, ready to be deployed on a web server. Packed(PackedBundle), } +/// Create a bundle of all the files needed for serving the app in the web. +/// +/// If `packed` is set to `true`, the files will be packed together in a single folder. +/// Use this option e.g. to upload it to a web server. +/// +/// Otherwise, the assets and build artifacts will be kept at their original place +/// to avoid duplication. pub fn create_web_bundle( metadata: &Metadata, profile: &str, From 953e833b024f94abde2c70fd3c1a421c3675b15a Mon Sep 17 00:00:00 2001 From: Tim Jentzsch Date: Mon, 23 Dec 2024 14:41:02 +0100 Subject: [PATCH 08/15] Move bundle into web utility module --- src/lib.rs | 1 + src/run/mod.rs | 3 +-- src/run/serve.rs | 6 +++--- src/{run => web}/bundle.rs | 2 +- src/web/mod.rs | 3 +++ 5 files changed, 9 insertions(+), 6 deletions(-) rename src/{run => web}/bundle.rs (98%) create mode 100644 src/web/mod.rs diff --git a/src/lib.rs b/src/lib.rs index c56abc8..f7eab7a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,3 +5,4 @@ pub mod external_cli; pub mod lint; pub mod run; pub mod template; +pub(crate) mod web; diff --git a/src/run/mod.rs b/src/run/mod.rs index 626a468..32543d4 100644 --- a/src/run/mod.rs +++ b/src/run/mod.rs @@ -2,7 +2,6 @@ use std::path::PathBuf; use anyhow::Context; use args::RunSubcommands; -use bundle::create_web_bundle; use crate::{ build::ensure_web_setup, @@ -10,12 +9,12 @@ use crate::{ cargo::{self, metadata::Metadata}, wasm_bindgen, CommandHelpers, }, + web::bundle::create_web_bundle, }; pub use self::args::RunArgs; mod args; -mod bundle; mod serve; pub fn run(args: &RunArgs) -> anyhow::Result<()> { diff --git a/src/run/serve.rs b/src/run/serve.rs index e4ed2ec..1dcc808 100644 --- a/src/run/serve.rs +++ b/src/run/serve.rs @@ -1,7 +1,7 @@ //! Serving the app locally for the browser. use actix_web::{rt, web, App, HttpResponse, HttpServer, Responder}; -use super::bundle::{LinkedBundle, PackedBundle, WebBundle}; +use crate::web::bundle::{Index, LinkedBundle, PackedBundle, WebBundle}; /// Serve a static HTML file with the given content. async fn serve_static_html(content: &'static str) -> impl Responder { @@ -50,12 +50,12 @@ pub(crate) fn serve(web_bundle: WebBundle, port: u16) -> anyhow::Result<()> { } match index { - super::bundle::Index::Folder(path) => { + Index::Folder(path) => { app = app.service( actix_files::Files::new("/", path).index_file("index.html"), ); } - super::bundle::Index::Static(contents) => { + Index::Static(contents) => { app = app.route("/", web::get().to(move || serve_static_html(contents))) } } diff --git a/src/run/bundle.rs b/src/web/bundle.rs similarity index 98% rename from src/run/bundle.rs rename to src/web/bundle.rs index f9f5aaa..459b139 100644 --- a/src/run/bundle.rs +++ b/src/web/bundle.rs @@ -4,7 +4,7 @@ use std::{ path::{Path, PathBuf}, }; -use super::{cargo::metadata::Metadata, BinTarget}; +use crate::{external_cli::cargo::metadata::Metadata, run::BinTarget}; #[derive(Debug, Clone)] pub enum Index { diff --git a/src/web/mod.rs b/src/web/mod.rs new file mode 100644 index 0000000..d027d20 --- /dev/null +++ b/src/web/mod.rs @@ -0,0 +1,3 @@ +//! Utilities for building and running the app in the browser. + +pub(crate) mod bundle; From 70b55fc7568381388c4d48b68e546a8f0e08ce01 Mon Sep 17 00:00:00 2001 From: Tim Jentzsch Date: Mon, 23 Dec 2024 14:58:43 +0100 Subject: [PATCH 09/15] Add bundle option to build web command --- src/build/args.rs | 13 ++++++++++--- src/build/mod.rs | 13 ++++++++++++- src/run/args.rs | 2 +- src/run/mod.rs | 8 ++++++-- 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/build/args.rs b/src/build/args.rs index 06efcad..04ff4b3 100644 --- a/src/build/args.rs +++ b/src/build/args.rs @@ -1,4 +1,4 @@ -use clap::{Args, Subcommand}; +use clap::{ArgAction, Args, Subcommand}; use crate::external_cli::{arg_builder::ArgBuilder, cargo::build::CargoBuildArgs}; @@ -16,7 +16,7 @@ pub struct BuildArgs { impl BuildArgs { /// Determine if the app is being built for the web. pub(crate) fn is_web(&self) -> bool { - matches!(self.subcommand, Some(BuildSubcommands::Web)) + matches!(self.subcommand, Some(BuildSubcommands::Web(_))) } /// The profile used to compile the app. @@ -38,5 +38,12 @@ impl BuildArgs { #[derive(Debug, Subcommand)] pub enum BuildSubcommands { /// Build your app for the browser. - Web, + Web(BuildWebArgs), +} + +#[derive(Debug, Args)] +pub struct BuildWebArgs { + // Bundle all web artifacts into a single folder. + #[arg(short = 'b', long = "bundle", action = ArgAction::SetTrue, default_value_t = false)] + pub create_packed_bundle: bool, } diff --git a/src/build/mod.rs b/src/build/mod.rs index 55f4ceb..b9083cb 100644 --- a/src/build/mod.rs +++ b/src/build/mod.rs @@ -1,6 +1,9 @@ +use args::BuildSubcommands; + use crate::{ external_cli::{cargo, rustup, wasm_bindgen, CommandHelpers}, run::select_run_binary, + web::bundle::{create_web_bundle, PackedBundle, WebBundle}, }; pub use self::args::BuildArgs; @@ -10,7 +13,7 @@ mod args; pub fn build(args: &BuildArgs) -> anyhow::Result<()> { let cargo_args = args.cargo_args_builder(); - if args.is_web() { + if let Some(BuildSubcommands::Web(web_args)) = &args.subcommand { ensure_web_setup()?; let metadata = cargo::metadata::metadata_with_args(["--no-deps"])?; @@ -28,6 +31,14 @@ pub fn build(args: &BuildArgs) -> anyhow::Result<()> { args.profile(), )?; wasm_bindgen::bundle(&bin_target)?; + + if web_args.create_packed_bundle { + let web_bundle = create_web_bundle(&metadata, args.profile(), bin_target, true)?; + + if let WebBundle::Packed(PackedBundle { path }) = &web_bundle { + println!("Created bundle at file://{}", path.display()); + } + } } else { cargo::build::command().args(cargo_args).ensure_status()?; } diff --git a/src/run/args.rs b/src/run/args.rs index 73dda22..080f85f 100644 --- a/src/run/args.rs +++ b/src/run/args.rs @@ -53,5 +53,5 @@ pub struct RunWebArgs { // Bundle all web artifacts into a single folder. #[arg(short = 'b', long = "bundle", action = ArgAction::SetTrue, default_value_t = false)] - pub create_bundle: bool, + pub create_packed_bundle: bool, } diff --git a/src/run/mod.rs b/src/run/mod.rs index 32543d4..355a41b 100644 --- a/src/run/mod.rs +++ b/src/run/mod.rs @@ -9,7 +9,7 @@ use crate::{ cargo::{self, metadata::Metadata}, wasm_bindgen, CommandHelpers, }, - web::bundle::create_web_bundle, + web::bundle::{create_web_bundle, PackedBundle, WebBundle}, }; pub use self::args::RunArgs; @@ -44,10 +44,14 @@ pub fn run(args: &RunArgs) -> anyhow::Result<()> { &metadata, args.profile(), bin_target, - web_args.create_bundle, + web_args.create_packed_bundle, ) .context("Failed to create web bundle")?; + if let WebBundle::Packed(PackedBundle { path }) = &web_bundle { + println!("Created bundle at file://{}", path.display()); + } + let port = web_args.port; let url = format!("http://localhost:{port}"); From 5954cbd03005d455e90eda2216c31cb690cb05a9 Mon Sep 17 00:00:00 2001 From: Tim Jentzsch Date: Mon, 23 Dec 2024 15:06:10 +0100 Subject: [PATCH 10/15] Clean up previous bundle if it exists --- src/web/bundle.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/web/bundle.rs b/src/web/bundle.rs index 459b139..cbad07e 100644 --- a/src/web/bundle.rs +++ b/src/web/bundle.rs @@ -91,9 +91,13 @@ pub fn create_web_bundle( .join(profile) .join(bin_target.bin_name); + // Remove the previous bundle + // The error can be ignored, because the folder doesn't need to exist yet + // and the files will also be overwritten if they already exist + let _ = fs::remove_dir_all(&base_path); + // Build artifacts fs::create_dir_all(base_path.join("build"))?; - println!("Create build folder"); fs::copy( linked.build_artifact_path.join(&linked.wasm_file_name), base_path.join("build").join(&linked.wasm_file_name), From 31993e67e57ea58309fc3fef17724486af155345 Mon Sep 17 00:00:00 2001 From: Tim Jentzsch Date: Mon, 23 Dec 2024 16:23:18 +0100 Subject: [PATCH 11/15] Fix bundling of assets folder --- src/web/bundle.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/web/bundle.rs b/src/web/bundle.rs index cbad07e..ec550f8 100644 --- a/src/web/bundle.rs +++ b/src/web/bundle.rs @@ -4,6 +4,8 @@ use std::{ path::{Path, PathBuf}, }; +use anyhow::Context; + use crate::{external_cli::cargo::metadata::Metadata, run::BinTarget}; #[derive(Debug, Clone)] @@ -101,32 +103,37 @@ pub fn create_web_bundle( fs::copy( linked.build_artifact_path.join(&linked.wasm_file_name), base_path.join("build").join(&linked.wasm_file_name), - )?; + ) + .context("failed to copy WASM artifact")?; fs::copy( linked.build_artifact_path.join(&linked.js_file_name), base_path.join("build").join(&linked.js_file_name), - )?; + ) + .context("failed to copy JS artifact")?; // Assets if let Some(assets_path) = linked.assets_path { + let new_assets_path = base_path.join("assets"); + fs::create_dir_all(&new_assets_path)?; fs_extra::dir::copy( assets_path, - base_path.join("assets"), + &new_assets_path, &fs_extra::dir::CopyOptions { overwrite: true, ..Default::default() }, - )?; + ) + .context("failed to copy assets")?; } // Index let index_path = base_path.join("index.html"); match linked.index { Index::Folder(path) => { - fs::copy(path, index_path)?; + fs::copy(path, index_path).context("failed to copy custom web assets")?; } Index::Static(contents) => { - fs::write(index_path, contents)?; + fs::write(index_path, contents).context("failed to create index.html")?; } } From bdec74373a9a72879f58925f2ea0c4d48b7927e3 Mon Sep 17 00:00:00 2001 From: Tim Jentzsch Date: Mon, 23 Dec 2024 17:23:57 +0100 Subject: [PATCH 12/15] Fix bundling of assets and custom web assets --- src/web/bundle.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/web/bundle.rs b/src/web/bundle.rs index ec550f8..b05bcde 100644 --- a/src/web/bundle.rs +++ b/src/web/bundle.rs @@ -113,11 +113,9 @@ pub fn create_web_bundle( // Assets if let Some(assets_path) = linked.assets_path { - let new_assets_path = base_path.join("assets"); - fs::create_dir_all(&new_assets_path)?; fs_extra::dir::copy( assets_path, - &new_assets_path, + &base_path, &fs_extra::dir::CopyOptions { overwrite: true, ..Default::default() @@ -130,7 +128,16 @@ pub fn create_web_bundle( let index_path = base_path.join("index.html"); match linked.index { Index::Folder(path) => { - fs::copy(path, index_path).context("failed to copy custom web assets")?; + fs_extra::dir::copy( + path, + &base_path, + &fs_extra::dir::CopyOptions { + overwrite: true, + content_only: true, + ..Default::default() + }, + ) + .context("failed to copy custom web assets")?; } Index::Static(contents) => { fs::write(index_path, contents).context("failed to create index.html")?; From 4a9c69f5b387bc74714ac51e8f935259f31b8f3b Mon Sep 17 00:00:00 2001 From: Tim Jentzsch Date: Mon, 23 Dec 2024 22:36:10 +0100 Subject: [PATCH 13/15] Add message when default web assets are used --- src/web/bundle.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/web/bundle.rs b/src/web/bundle.rs index b05bcde..c44a149 100644 --- a/src/web/bundle.rs +++ b/src/web/bundle.rs @@ -79,6 +79,7 @@ pub fn create_web_bundle( index: if custom_web_folder.join("index.html").exists() { Index::Folder(custom_web_folder.to_path_buf()) } else { + println!("No custom `web` folder found, using defaults."); Index::Static(default_index(&bin_target)) }, }; From 331f2c5f7172104fce29f19bad63122c068d0b49 Mon Sep 17 00:00:00 2001 From: Tim Jentzsch Date: Sun, 29 Dec 2024 18:48:23 +0100 Subject: [PATCH 14/15] Add comment about name of JS entrypoint --- assets/web/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/assets/web/index.html b/assets/web/index.html index 476d79a..b7de1fc 100644 --- a/assets/web/index.html +++ b/assets/web/index.html @@ -65,6 +65,7 @@