Skip to content

Commit

Permalink
Serve app from bundle info
Browse files Browse the repository at this point in the history
  • Loading branch information
TimJentzsch committed Dec 23, 2024
1 parent 7e00d35 commit 2ed77bf
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 48 deletions.
38 changes: 24 additions & 14 deletions src/run/bundle.rs
Original file line number Diff line number Diff line change
@@ -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<PathBuf>,
index: Index,
pub build_artifact_path: PathBuf,
pub wasm_file_name: OsString,
pub js_file_name: OsString,
pub assets_path: Option<PathBuf>,
pub index: Index,
}

#[derive(Debug, Clone)]
pub struct PackedBundle {
path: PathBuf,
pub path: PathBuf,
}

#[derive(Debug, Clone)]
pub enum WebBundle {
Linked(LinkedBundle),
Packed(PackedBundle),
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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) => {
Expand Down
12 changes: 11 additions & 1 deletion src/run/mod.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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}");

Expand All @@ -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()?;
Expand Down
75 changes: 42 additions & 33 deletions src/run/serve.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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
Expand Down

0 comments on commit 2ed77bf

Please sign in to comment.