From 7e533daa9bb9ed025209fdb21a417d81fd519ae1 Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Sat, 11 Jun 2022 10:16:15 +1000 Subject: [PATCH 01/37] feat: made the engine code functional in the user's code No support for serving yet and there's still Wasm binary bloat. --- examples/core/basic/.gitignore | 1 + examples/core/basic/Cargo.toml | 15 +- examples/core/basic/src/lib.rs | 18 +- packages/perseus/Cargo.toml | 5 + packages/perseus/src/engine/build.rs | 70 +++++++ packages/perseus/src/engine/dflt_engine.rs | 71 +++++++ packages/perseus/src/engine/export.rs | 188 ++++++++++++++++++ .../perseus/src/engine/export_error_page.rs | 64 ++++++ packages/perseus/src/engine/get_op.rs | 28 +++ packages/perseus/src/engine/mod.rs | 16 ++ packages/perseus/src/engine/tinker.rs | 16 ++ packages/perseus/src/errors.rs | 39 ++++ packages/perseus/src/init.rs | 14 ++ packages/perseus/src/lib.rs | 13 +- packages/perseus/src/plugins/functional.rs | 40 ++-- packages/perseus/src/stores/immutable.rs | 9 +- 16 files changed, 584 insertions(+), 23 deletions(-) create mode 100644 examples/core/basic/.gitignore create mode 100644 packages/perseus/src/engine/build.rs create mode 100644 packages/perseus/src/engine/dflt_engine.rs create mode 100644 packages/perseus/src/engine/export.rs create mode 100644 packages/perseus/src/engine/export_error_page.rs create mode 100644 packages/perseus/src/engine/get_op.rs create mode 100644 packages/perseus/src/engine/mod.rs create mode 100644 packages/perseus/src/engine/tinker.rs diff --git a/examples/core/basic/.gitignore b/examples/core/basic/.gitignore new file mode 100644 index 0000000000..849ddff3b7 --- /dev/null +++ b/examples/core/basic/.gitignore @@ -0,0 +1 @@ +dist/ diff --git a/examples/core/basic/Cargo.toml b/examples/core/basic/Cargo.toml index 6791130c3f..4bc148e999 100644 --- a/examples/core/basic/Cargo.toml +++ b/examples/core/basic/Cargo.toml @@ -6,11 +6,22 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -perseus = { path = "../../../packages/perseus", features = [ "hydrate" ] } +perseus = { path = "../../../packages/perseus", features = [ "hydrate", "builder", "dflt-engine" ] } sycamore = "=0.8.0-beta.6" serde = { version = "1", features = ["derive"] } serde_json = "1" [dev-dependencies] fantoccini = "0.17" -tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] } + +# New Perseus metadata begins here +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } + +[lib] +name = "lib" +path = "src/lib.rs" + +[[bin]] +name = "perseus-example-basic" +path = "src/lib.rs" diff --git a/examples/core/basic/src/lib.rs b/examples/core/basic/src/lib.rs index ee3e6e1a79..58c86c6027 100644 --- a/examples/core/basic/src/lib.rs +++ b/examples/core/basic/src/lib.rs @@ -2,11 +2,25 @@ mod error_pages; mod templates; use perseus::{Html, PerseusApp}; +use perseus::builder::{get_op, run_dflt_engine}; -#[perseus::main] -pub fn main() -> PerseusApp { +pub fn get_app() -> PerseusApp { PerseusApp::new() .template(crate::templates::index::get_template) .template(crate::templates::about::get_template) .error_pages(crate::error_pages::get_error_pages) } + +#[cfg(not(target_arch = "wasm32"))] +#[allow(dead_code)] +#[tokio::main] +async fn main() { + let op = get_op().unwrap(); + let exit_code = run_dflt_engine(op, get_app()).await; + std::process::exit(exit_code); +} + +#[cfg(target_arch = "wasm32")] +fn main() { + +} diff --git a/packages/perseus/Cargo.toml b/packages/perseus/Cargo.toml index 2f0218cf5a..86af8ff01c 100644 --- a/packages/perseus/Cargo.toml +++ b/packages/perseus/Cargo.toml @@ -37,6 +37,7 @@ intl-memoizer = { version = "0.5", optional = true } tokio = { version = "1", features = [ "fs", "io-util" ] } rexie = { version = "0.2", optional = true } js-sys = { version = "0.3", optional = true } +fs_extra = { version = "1", optional = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] regex = "1" @@ -51,6 +52,10 @@ tinker-plugins = [] # This feature enables server-side-only features, which should be used on both the server and in the builder # This prevents leakage of server-side code server-side = [] +# This feature enables code required only in the builder systems on the server-side +builder = [ "server-side", "fs_extra" ] +# This feature enable support for functions that make using the default engine configuration much easier. +dflt-engine = [ "builder" ] # This feature changes a few defaults so that Perseus works seemlessly when deployed with the `.perseus/` structure (this is activated automatically by `perseus deploy`, and should not be invoked manually!) standalone = [] # This feature enables Sycamore hydration by default (Sycamore hydration feature is always activated though) diff --git a/packages/perseus/src/engine/build.rs b/packages/perseus/src/engine/build.rs new file mode 100644 index 0000000000..25ea003d93 --- /dev/null +++ b/packages/perseus/src/engine/build.rs @@ -0,0 +1,70 @@ +use crate::{PerseusAppBase, PluginAction, SsrNode, errors::{ServerError, EngineError}, i18n::TranslationsManager, stores::MutableStore}; +use crate::build::{build_app, BuildProps}; +use std::rc::Rc; + +/// Builds the app, calling all necessary plugin opportunities. This works solely with the properties provided in the given `PerseusApp`, so this is entirely engine-agnostic. +/// +/// Note that this expects to be run in the root of the project. +pub async fn build(app: PerseusAppBase) -> Result<(), Rc> { + let plugins = app.get_plugins(); + + plugins + .functional_actions + .build_actions + .before_build + .run((), plugins.get_plugin_data()); + + let immutable_store = app.get_immutable_store(); + let mutable_store = app.get_mutable_store(); + let locales = app.get_locales(); + // Generate the global state + let gsc = app.get_global_state_creator(); + let global_state = match gsc.get_build_state().await { + Ok(global_state) => global_state, + Err(err) => { + let err: Rc = Rc::new(ServerError::GlobalStateError(err).into()); + plugins + .functional_actions + .build_actions + .after_failed_global_state_creation + .run(err.clone(), plugins.get_plugin_data()); + return Err(err); + } + }; + + // Build the site for all the common locales (done in parallel) + // All these parameters can be modified by `PerseusApp` and plugins, so there's no point in having a plugin opportunity here + let templates_map = app.get_templates_map(); + + // We have to get the translations manager last, because it consumes everything + let translations_manager = app.get_translations_manager().await; + + let res = build_app(BuildProps { + templates: &templates_map, + locales: &locales, + immutable_store: &immutable_store, + mutable_store: &mutable_store, + translations_manager: &translations_manager, + global_state: &global_state, + exporting: false, + }) + .await; + if let Err(err) = res { + let err: Rc = Rc::new(err.into()); + plugins + .functional_actions + .build_actions + .after_failed_build + .run(err.clone(), plugins.get_plugin_data()); + + Err(err) + } else { + plugins + .functional_actions + .build_actions + .after_successful_build + .run((), plugins.get_plugin_data()); + + Ok(()) + } +} diff --git a/packages/perseus/src/engine/dflt_engine.rs b/packages/perseus/src/engine/dflt_engine.rs new file mode 100644 index 0000000000..ffff047e33 --- /dev/null +++ b/packages/perseus/src/engine/dflt_engine.rs @@ -0,0 +1,71 @@ +// This file contains functions exclusive to the default engine systems + +use crate::{PerseusAppBase, i18n::TranslationsManager, stores::MutableStore, SsrNode}; +use super::EngineOperation; +use fmterr::fmt_err; +use std::env; + +/// A convenience function that automatically runs the necessary engine operation based on the given directive. This provides almost no options for customization, and is +/// usually elided by a macro. More advanced use-cases should bypass this and call the functions this calls manually, with their own configurations. +/// +/// If the action is to export a single error page, the HTTP status code of the error page to export and the output will be read as the first and second arguments +/// to the binary invocation. If this is not the desired behavior, you should handle the `EngineOperation::ExportErrorPage` case manually. +/// +/// This returns an exit code, which should be returned from the process. Any hanlded errors will be printed to the console. +pub async fn run_dflt_engine(op: EngineOperation, app: PerseusAppBase) -> i32 { + match op { + EngineOperation::Build => match super::engine_build(app).await { + Ok(_) => 0, + Err(err) => { + eprintln!("{}", fmt_err(&*err)); + 1 + } + }, + EngineOperation::Export => match super::engine_export(app).await { + Ok(_) => 0, + Err(err) => { + eprintln!("{}", fmt_err(&*err)); + 1 + } + }, + EngineOperation::ExportErrorPage => { + // Get the HTTP status code to build from the arguments to this executable + // We print errors directly here because we can, and because this behavior is unique to the default engine + let args = env::args().collect::>(); + let code = match args.get(1) { + Some(arg) => match arg.parse::() { + Ok(err_code) => err_code, + Err(_) => { + eprintln!("HTTP status code for error page exporting must be a valid integer."); + return 1; + } + }, + None => { + eprintln!("Error page exporting requires an HTTP status code for which to export the error page."); + return 1; + } + }; + // Get the output to write to from the second argument + let output = match args.get(2) { + Some(output) => output, + None => { + eprintln!("Error page exporting requires an output location."); + return 1; + } + }; + match super::engine_export_error_page(app, code, output).await { + Ok(_) => 0, + Err(err) => { + eprintln!("{}", fmt_err(&*err)); + 1 + } + } + }, + EngineOperation::Serve => todo!(), + EngineOperation::Tinker => { + // This is infallible (though plugins could panic) + super::engine_tinker(app); + 0 + }, + } +} diff --git a/packages/perseus/src/engine/export.rs b/packages/perseus/src/engine/export.rs new file mode 100644 index 0000000000..7eb897b419 --- /dev/null +++ b/packages/perseus/src/engine/export.rs @@ -0,0 +1,188 @@ +use fs_extra::dir::{copy as copy_dir, CopyOptions}; +use crate::errors::ServerError; +use crate::{PerseusApp, PluginAction, Plugins, SsrNode, internal::get_path_prefix_server}; +use crate::build::{build_app, BuildProps}; +use crate::export::{export_app, ExportProps}; +use std::fs; +use std::path::PathBuf; +use std::collections::HashMap; +use std::rc::Rc; + +use crate::{PerseusAppBase, i18n::TranslationsManager, stores::MutableStore}; +use crate::errors::*; + + +/// Exports the app to static files, given a `PerseusApp`. This is engine-agnostic, using the `exported` subfolder in the immutable store as a destination directory. By default +/// this will end up at `dist/exported/` (customizable through `PerseusApp`). +/// +/// Note that this expects to be run in the root of the project. +pub async fn export(app: PerseusAppBase) -> Result<(), Rc> { + let plugins = app.get_plugins(); + let static_aliases = app.get_static_aliases(); + // This won't have any trailing slashes (they're stripped by the immutable store initializer) + let dest = format!("{}/exported", app.get_immutable_store().get_path()); + let static_dir = app.get_static_dir(); + + build_and_export(app).await?; + // After that's done, we can do two copy operations in parallel at least + copy_static_aliases(&plugins, &static_aliases, &dest)?; + copy_static_dir(&plugins, &static_dir, &dest)?; + + plugins + .functional_actions + .export_actions + .after_successful_export + .run((), plugins.get_plugin_data()); + + Ok(()) +} + +/// Performs the building and exporting processes using the given app. This is fully engine-agnostic, using only the data provided in the given `PerseusApp`. +async fn build_and_export(app: PerseusAppBase) -> Result<(), Rc> { + let plugins = app.get_plugins(); + + plugins + .functional_actions + .build_actions + .before_build + .run((), plugins.get_plugin_data()); + + let immutable_store = app.get_immutable_store(); + // We don't need this in exporting, but the build process does + let mutable_store = app.get_mutable_store(); + let locales = app.get_locales(); + // Generate the global state + let gsc = app.get_global_state_creator(); + let global_state = match gsc.get_build_state().await { + Ok(global_state) => global_state, + Err(err) => { + let err: Rc = Rc::new(ServerError::GlobalStateError(err).into()); + plugins + .functional_actions + .export_actions + .after_failed_global_state_creation + .run(err.clone(), plugins.get_plugin_data()); + return Err(err); + } + }; + let templates_map = app.get_templates_map(); + let index_view_str = app.get_index_view_str(); + let root_id = app.get_root(); + // This consumes `self`, so we get it finally + let translations_manager = app.get_translations_manager().await; + + // Build the site for all the common locales (done in parallel), denying any non-exportable features + // We need to build and generate those artifacts before we can proceed on to exporting + let build_res = build_app(BuildProps { + templates: &templates_map, + locales: &locales, + immutable_store: &immutable_store, + mutable_store: &mutable_store, + translations_manager: &translations_manager, + global_state: &global_state, + exporting: true, + }) + .await; + if let Err(err) = build_res { + let err: Rc = Rc::new(err.into()); + plugins + .functional_actions + .export_actions + .after_failed_build + .run(err.clone(), plugins.get_plugin_data()); + return Err(err); + } + plugins + .functional_actions + .export_actions + .after_successful_build + .run((), plugins.get_plugin_data()); + // The app has now been built, so we can safely instantiate the HTML shell (which needs access to the render config, generated in the above build step) + // It doesn't matter if the type parameters here are wrong, this function doesn't use them + let index_view = + PerseusApp::get_html_shell(index_view_str, &root_id, &immutable_store, &plugins).await; + // Turn the build artifacts into self-contained static files + let export_res = export_app(ExportProps { + templates: &templates_map, + html_shell: index_view, + locales: &locales, + immutable_store: &immutable_store, + translations_manager: &translations_manager, + path_prefix: get_path_prefix_server(), + global_state: &global_state, + }) + .await; + if let Err(err) = export_res { + let err: Rc = Rc::new(err.into()); + plugins + .functional_actions + .export_actions + .after_failed_export + .run(err.clone(), plugins.get_plugin_data()); + return Err(err); + } + + Ok(()) +} + +/// Copies the static aliases into a distribution directory at `dest` (no trailing `/`). This should be the root of the destination directory for the exported files. +/// Because this provides a customizable destination, it is fully engine-agnostic. +/// +/// The error type here is a tuple of the location the asset was copied from, the location it was copied to, and the error in that process (which could be from `io` or +/// `fs_extra`). +fn copy_static_aliases(plugins: &Plugins, static_aliases: &HashMap, dest: &str) -> Result<(), Rc> { + // Loop through any static aliases and copy them in too + // Unlike with the server, these could override pages! + // We'll copy from the alias to the path (it could be a directory or a file) + // Remember: `alias` has a leading `/`! + for (alias, path) in static_aliases { + let from = PathBuf::from(path); + let to = format!("{}{}", dest, alias); + + if from.is_dir() { + if let Err(err) = copy_dir(&from, &to, &CopyOptions::new()) { + let err = EngineError::CopyStaticAliasDirErr { source: err, to: to.to_string(), from: path.to_string() }; + let err = Rc::new(err); + plugins + .functional_actions + .export_actions + .after_failed_static_alias_dir_copy + .run(err.clone(), plugins.get_plugin_data()); + return Err(err); + } + } else if let Err(err) = fs::copy(&from, &to) { + let err = EngineError::CopyStaticAliasFileError { source: err, to: to.to_string(), from: path.to_string() }; + let err = Rc::new(err); + plugins + .functional_actions + .export_actions + .after_failed_static_alias_file_copy + .run(err.clone(), plugins.get_plugin_data()); + return Err(err); + } + } + + Ok(()) +} + +/// Copies the directory containing static data to be put in `/.perseus/static/` (URL). This takes in both the location of the static directory and the destination +/// directory for exported files. +fn copy_static_dir(plugins: &Plugins, static_dir_raw: &str, dest: &str) -> Result<(), Rc> { + // Copy the `static` directory into the export package if it exists + // If the user wants extra, they can use static aliases, plugins are unnecessary here + let static_dir = PathBuf::from(static_dir_raw); + if static_dir.exists() { + if let Err(err) = copy_dir(&static_dir, format!("{}/.perseus/", dest), &CopyOptions::new()) { + let err = EngineError::CopyStaticDirError{ source: err, path: static_dir_raw.to_string(), dest: dest.to_string() }; + let err = Rc::new(err); + plugins + .functional_actions + .export_actions + .after_failed_static_copy + .run(err.clone(), plugins.get_plugin_data()); + return Err(err); + } + } + + Ok(()) +} diff --git a/packages/perseus/src/engine/export_error_page.rs b/packages/perseus/src/engine/export_error_page.rs new file mode 100644 index 0000000000..312c7d9ff4 --- /dev/null +++ b/packages/perseus/src/engine/export_error_page.rs @@ -0,0 +1,64 @@ +use crate::{PerseusApp, PerseusAppBase, PluginAction, SsrNode, errors::EngineError, i18n::TranslationsManager, internal::serve::build_error_page, stores::MutableStore}; +use std::{fs, rc::Rc}; + +/// Exports a single error page for the given HTTP status code to the given output location. If the status code doesn't exist or isn't handled, then the fallback page will be +/// exported. +/// +/// This expects to run in the root of the project. +/// +/// This can only return IO errors from failures to write to the given output location. (Wrapped in an `Rc` so they can be sent to plugins as well.) +pub async fn export_error_page(app: PerseusAppBase, code: u16, output: &str) -> Result<(), Rc> { + let plugins = app.get_plugins(); + + let error_pages = app.get_error_pages(); + // Prepare the HTML shell + let index_view_str = app.get_index_view_str(); + let root_id = app.get_root(); + let immutable_store = app.get_immutable_store(); + // We assume the app has already been built before running this (so the render config must be available) + // It doesn't matter if the type parameters here are wrong, this function doesn't use them + let html_shell = + PerseusApp::get_html_shell(index_view_str, &root_id, &immutable_store, &plugins).await; + + plugins + .functional_actions + .export_error_page_actions + .before_export_error_page + .run( + (code, output.to_string()), + plugins.get_plugin_data(), + ); + + // Build that error page as the server does + let err_page_str = build_error_page( + "", + code, + "", + None, + &error_pages, + &html_shell, + ); + + // Write that to the given output location + match fs::write(&output, err_page_str) { + Ok(_) => (), + Err(err) => { + let err = EngineError::WriteErrorPageError { source: err, dest: output.to_string() }; + let err = Rc::new(err); + plugins + .functional_actions + .export_error_page_actions + .after_failed_write + .run(err.clone(), plugins.get_plugin_data()); + return Err(err); + } + }; + + plugins + .functional_actions + .export_error_page_actions + .after_successful_export_error_page + .run((), plugins.get_plugin_data()); + + Ok(()) +} diff --git a/packages/perseus/src/engine/get_op.rs b/packages/perseus/src/engine/get_op.rs new file mode 100644 index 0000000000..481e5b5c33 --- /dev/null +++ b/packages/perseus/src/engine/get_op.rs @@ -0,0 +1,28 @@ +use std::env; + +/// Determines the engine operation to be performed by examining environment variables (set automatically by the CLI as appropriate). +pub fn get_op() -> Option { + let var = env::var("PERSEUS_ENGINE_OPERATION").ok()?; + match var.as_str() { + "serve" => Some(EngineOperation::Serve), + "build" => Some(EngineOperation::Build), + "export" => Some(EngineOperation::Export), + "export_error_page" => Some(EngineOperation::ExportErrorPage), + "tinker" => Some(EngineOperation::Tinker), + _ => None + } +} + +/// A representation of the server-side engine operations that can be performed. +pub enum EngineOperation { + /// Run the server for the app. This assumes the app has already been built. + Serve, + /// Build the app. This process involves statically generating HTML and the like to be sent to the client. + Build, + /// Export the app by building it and also creating a file layout suitable for static file serving. + Export, + /// Export a single error page to a single file. + ExportErrorPage, + /// Run the tinker plugin actions. + Tinker, +} diff --git a/packages/perseus/src/engine/mod.rs b/packages/perseus/src/engine/mod.rs new file mode 100644 index 0000000000..8fd5a22412 --- /dev/null +++ b/packages/perseus/src/engine/mod.rs @@ -0,0 +1,16 @@ +mod build; +mod export; +mod export_error_page; +mod tinker; +pub use build::build as engine_build; +pub use export::export as engine_export; +pub use export_error_page::export_error_page as engine_export_error_page; +pub use tinker::tinker as engine_tinker; + +#[cfg(feature = "dflt-engine")] +mod dflt_engine; +#[cfg(feature = "dflt-engine")] +pub use dflt_engine::run_dflt_engine; + +mod get_op; +pub use get_op::{get_op, EngineOperation}; diff --git a/packages/perseus/src/engine/tinker.rs b/packages/perseus/src/engine/tinker.rs new file mode 100644 index 0000000000..3357a64cdb --- /dev/null +++ b/packages/perseus/src/engine/tinker.rs @@ -0,0 +1,16 @@ +use crate::{plugins::PluginAction, SsrNode, PerseusAppBase}; +use crate::{i18n::TranslationsManager, stores::MutableStore}; + +/// Runs tinker plugin actions. +/// +/// Note that this expects to be run in the root of the project. +pub fn tinker(app: PerseusAppBase) { + let plugins = app.get_plugins(); + // Run all the tinker actions + // Note: this is deliberately synchronous, tinker actions that need a multithreaded async runtime should probably + // be making their own engines! + plugins + .functional_actions + .tinker + .run((), plugins.get_plugin_data()); +} diff --git a/packages/perseus/src/errors.rs b/packages/perseus/src/errors.rs index 8c0988d0aa..45ccdebe4d 100644 --- a/packages/perseus/src/errors.rs +++ b/packages/perseus/src/errors.rs @@ -10,6 +10,45 @@ pub enum Error { ClientError(#[from] ClientError), #[error(transparent)] ServerError(#[from] ServerError), + #[cfg(feature = "builder")] + #[error(transparent)] + EngineError(#[from] EngineError), +} + +/// Errors that can occur in the server-side engine system (responsible for building the app). +#[cfg(feature = "builder")] +#[derive(Error, Debug)] +pub enum EngineError { + // Many of the build/export processes return these more generic errors + #[error(transparent)] + ServerError(#[from] ServerError), + #[error("couldn't copy static directory at '{path}' to '{dest}'")] + CopyStaticDirError { + #[source] + source: fs_extra::error::Error, + path: String, + dest: String, + }, + #[error("couldn't copy static alias file from '{from}' to '{to}'")] + CopyStaticAliasFileError { + #[source] + source: std::io::Error, + from: String, + to: String, + }, + #[error("couldn't copy static alias directory from '{from}' to '{to}'")] + CopyStaticAliasDirErr { + #[source] + source: fs_extra::error::Error, + from: String, + to: String + }, + #[error("couldn't write the generated error page to '{dest}'")] + WriteErrorPageError { + #[source] + source: std::io::Error, + dest: String, + } } /// Errors that can occur in the browser. diff --git a/packages/perseus/src/init.rs b/packages/perseus/src/init.rs index 61874f1472..4ec4ac05ae 100644 --- a/packages/perseus/src/init.rs +++ b/packages/perseus/src/init.rs @@ -108,6 +108,9 @@ pub struct PerseusAppBase { /// The app's translations manager, expressed as a function yielding a `Future`. This is only ever needed on the server-side, and can't be set up properly on the client-side because /// we can't use futures in the app initialization in Wasm. translations_manager: Tm, + /// The location of the directory to use for static assets that will placed under the URL `/.perseus/static/`. By default, this is the `static/` directory at the root + /// of your project. Note that the directory set here will only be used if it exists. + static_dir: String } // The usual implementation in which the default mutable store is used @@ -199,6 +202,7 @@ impl PerseusAppBase { translations_manager: Tm::Dummy(T::new_dummy()), // Many users won't need anything fancy in the index view, so we provide a default index_view: DFLT_INDEX_VIEW.to_string(), + static_dir: "./static".to_string() } } @@ -208,6 +212,11 @@ impl PerseusAppBase { self.root = val.to_string(); self } + /// Sets the location of the directory storing static assets to be hosted under the URL `/.perseus/static/`. + pub fn static_dir(mut self, val: &str) -> Self { + self.static_dir = val.to_string(); + self + } /// Sets all the app's templates. This takes a vector of boxed functions that return templates. pub fn templates(mut self, val: Vec Template>>) -> Self { self.template_getters.0 = val; @@ -334,6 +343,11 @@ impl PerseusAppBase { .run((), self.plugins.get_plugin_data()) .unwrap_or_else(|| self.root.to_string()) } + /// Gets the directory containing static assets to be hosted under the URL `/.perseus/static/`. + // TODO Plugin action for this? + pub fn get_static_dir(&self) -> String { + self.static_dir.to_string() + } /// Gets the index view as a string, without generating an HTML shell (pass this into `::get_html_shell()` to do that). /// /// Note that this automatically adds `` to the start of the HTMl shell produced, which can only be overriden with a control plugin (though you should really never do this diff --git a/packages/perseus/src/lib.rs b/packages/perseus/src/lib.rs index f1196fb037..7b4d327527 100644 --- a/packages/perseus/src/lib.rs +++ b/packages/perseus/src/lib.rs @@ -35,8 +35,8 @@ pub mod state; pub mod stores; mod build; -mod error_pages; mod export; +mod error_pages; mod i18n; mod init; mod macros; @@ -46,6 +46,8 @@ mod shell; mod template; mod translator; mod utils; +#[cfg(feature = "builder")] +mod engine; // The rest of this file is devoted to module structuring // Re-exports @@ -75,6 +77,11 @@ pub mod templates { pub use crate::router::{RouterLoadState, RouterState}; pub use crate::template::*; } +/// Utilities for building an app. +#[cfg(feature = "builder")] +pub mod builder { + pub use crate::engine::*; +} /// A series of exports that should be unnecessary for nearly all uses of Perseus. These are used principally in developing alternative /// engines. pub mod internal { @@ -102,11 +109,11 @@ pub mod internal { pub mod shell { pub use crate::shell::*; } - /// Internal utilities for building. + /// Internal utilities for building apps at a very low level. pub mod build { pub use crate::build::*; } - /// Internal utilities for exporting. + /// Internal utilities for exporting apps at a very low level. pub mod export { pub use crate::export::*; } diff --git a/packages/perseus/src/plugins/functional.rs b/packages/perseus/src/plugins/functional.rs index ef811b8f0e..3d0d8dbcc1 100644 --- a/packages/perseus/src/plugins/functional.rs +++ b/packages/perseus/src/plugins/functional.rs @@ -1,7 +1,9 @@ +use crate::errors::EngineError; use crate::plugins::*; use crate::Html; use std::any::Any; use std::collections::HashMap; +use std::rc::Rc; /// An action which can be taken by many plugins. When run, a functional action will return a map of plugin names to their return types. pub struct FunctionalPluginAction { @@ -69,10 +71,13 @@ pub struct FunctionalPluginActions { /// Actions pertaining to the modification of settings created with `PerseusApp`. pub settings_actions: FunctionalPluginSettingsActions, /// Actions pertaining to the build process. + #[cfg(feature = "builder")] pub build_actions: FunctionalPluginBuildActions, /// Actions pertaining to the export process. + #[cfg(feature = "builder")] pub export_actions: FunctionalPluginExportActions, /// Actions pertaining to the process of exporting an error page. + #[cfg(feature = "builder")] pub export_error_page_actions: FunctionalPluginExportErrorPageActions, /// Actions pertaining to the server. pub server_actions: FunctionalPluginServerActions, @@ -84,8 +89,11 @@ impl Default for FunctionalPluginActions { Self { tinker: FunctionalPluginAction::default(), settings_actions: FunctionalPluginSettingsActions::::default(), + #[cfg(feature = "builder")] build_actions: FunctionalPluginBuildActions::default(), + #[cfg(feature = "builder")] export_actions: FunctionalPluginExportActions::default(), + #[cfg(feature = "builder")] export_error_page_actions: FunctionalPluginExportErrorPageActions::default(), server_actions: FunctionalPluginServerActions::default(), client_actions: FunctionalPluginClientActions::default(), @@ -143,6 +151,7 @@ pub struct FunctionalPluginHtmlShellActions { /// Functional actions that pertain to the build process. Note that these actions are not available for the build /// stage of the export process, and those should be registered separately. +#[cfg(feature = "builder")] #[derive(Default, Debug)] pub struct FunctionalPluginBuildActions { /// Runs before the build process. @@ -150,12 +159,13 @@ pub struct FunctionalPluginBuildActions { /// Runs after the build process if it completes successfully. pub after_successful_build: FunctionalPluginAction<(), ()>, /// Runs after the build process if it fails. - pub after_failed_build: FunctionalPluginAction, + pub after_failed_build: FunctionalPluginAction, ()>, /// Runs after the build process if it failed to generate global state. pub after_failed_global_state_creation: - FunctionalPluginAction, + FunctionalPluginAction, ()>, } /// Functional actions that pertain to the export process. +#[cfg(feature = "builder")] #[derive(Default, Debug)] pub struct FunctionalPluginExportActions { /// Runs before the export process. @@ -163,24 +173,24 @@ pub struct FunctionalPluginExportActions { /// Runs after the build stage in the export process if it completes successfully. pub after_successful_build: FunctionalPluginAction<(), ()>, /// Runs after the build stage in the export process if it fails. - pub after_failed_build: FunctionalPluginAction, + pub after_failed_build: FunctionalPluginAction, ()>, /// Runs after the export process if it fails. - pub after_failed_export: FunctionalPluginAction, - /// Runs if copying the static directory failed. The error type here is likely from a third-party library, so it's - /// provided as a string (otherwise we'd be hauling in an extra library for one type). - pub after_failed_static_copy: FunctionalPluginAction, - /// Runs if copying a static alias that was a directory failed. The error type here is likely from a third-party library, so it's - /// provided as a string (otherwise we'd be hauling in an extra library for one type). - pub after_failed_static_alias_dir_copy: FunctionalPluginAction, - /// Runs if copying a static alias that was a file failed. - pub after_failed_static_alias_file_copy: FunctionalPluginAction, + pub after_failed_export: FunctionalPluginAction, ()>, + /// Runs if copying the static directory failed. + pub after_failed_static_copy: FunctionalPluginAction, ()>, + /// Runs if copying a static alias that was a directory failed. The argument to this is a tuple of the from and to locations of the copy, along with the error. + pub after_failed_static_alias_dir_copy: FunctionalPluginAction, ()>, + /// Runs if copying a static alias that was a file failed. The argument to this is a tuple of the from and to locations of the copy, along with the error. + pub after_failed_static_alias_file_copy: FunctionalPluginAction, ()>, /// Runs after the export process if it completes successfully. pub after_successful_export: FunctionalPluginAction<(), ()>, - /// Runs after the export process if it failed to generate global state. + /// Runs after the export process if it failed to generate global state. Note that the error here will always be a `GlobalStateError`, but it must be processed as a + /// `ServerError` due to ownership constraints. pub after_failed_global_state_creation: - FunctionalPluginAction, + FunctionalPluginAction, ()>, } /// Functional actions that pertain to the process of exporting an error page. +#[cfg(feature = "builder")] #[derive(Default, Debug)] pub struct FunctionalPluginExportErrorPageActions { /// Runs before the process of exporting an error page, providing the HTTP status code to be exported and the output filename (relative to the root of the project, not to `.perseus/`). @@ -188,7 +198,7 @@ pub struct FunctionalPluginExportErrorPageActions { /// Runs after a error page was exported successfully. pub after_successful_export_error_page: FunctionalPluginAction<(), ()>, /// Runs if writing to the output file failed. Error and filename are given. - pub after_failed_write: FunctionalPluginAction<(std::io::Error, String), ()>, + pub after_failed_write: FunctionalPluginAction, ()>, } /// Functional actions that pertain to the server. #[derive(Default, Debug)] diff --git a/packages/perseus/src/stores/immutable.rs b/packages/perseus/src/stores/immutable.rs index 700ab87730..23933ff055 100644 --- a/packages/perseus/src/stores/immutable.rs +++ b/packages/perseus/src/stores/immutable.rs @@ -14,10 +14,17 @@ pub struct ImmutableStore { root_path: String, } impl ImmutableStore { - /// Creates a new immutable store. You should provide a path like `dist/` here. + /// Creates a new immutable store. You should provide a path like `dist` here. Note that any trailing slashes will be automatically stripped. pub fn new(root_path: String) -> Self { + let root_path = root_path.strip_prefix('/').unwrap_or(&root_path).to_string(); Self { root_path } } + /// Gets the filesystem path used for this immutable store. + /// + /// This is designed to be used in particular by the engine to work out where to put static assets and the like when exporting. + pub fn get_path(&self) -> &str { + &self.root_path + } /// Reads the given asset from the filesystem asynchronously. pub async fn read(&self, name: &str) -> Result { let asset_path = format!("{}/{}", self.root_path, name); From fced589d5250b0da34dd091875cbc49bff5de4b9 Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Sat, 11 Jun 2022 11:47:58 +1000 Subject: [PATCH 02/37] feat: added support for serving Still a lot of Wasm binary bloat. --- examples/core/basic/Cargo.toml | 1 + examples/core/basic/src/lib.rs | 2 +- packages/perseus-actix-web/Cargo.toml | 4 + packages/perseus-actix-web/src/dflt_server.rs | 30 ++++++ packages/perseus-actix-web/src/lib.rs | 4 + packages/perseus-axum/Cargo.toml | 4 + packages/perseus-axum/src/dflt_server.rs | 20 ++++ packages/perseus-axum/src/lib.rs | 4 + packages/perseus-warp/Cargo.toml | 4 + packages/perseus-warp/src/dflt_server.rs | 17 +++ packages/perseus-warp/src/lib.rs | 4 + packages/perseus/src/engine/dflt_engine.rs | 15 ++- packages/perseus/src/engine/mod.rs | 3 + packages/perseus/src/engine/serve.rs | 101 ++++++++++++++++++ packages/perseus/src/macros.rs | 7 +- 15 files changed, 211 insertions(+), 9 deletions(-) create mode 100644 packages/perseus-actix-web/src/dflt_server.rs create mode 100644 packages/perseus-axum/src/dflt_server.rs create mode 100644 packages/perseus-warp/src/dflt_server.rs create mode 100644 packages/perseus/src/engine/serve.rs diff --git a/examples/core/basic/Cargo.toml b/examples/core/basic/Cargo.toml index 4bc148e999..812e0cc8d0 100644 --- a/examples/core/basic/Cargo.toml +++ b/examples/core/basic/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] perseus = { path = "../../../packages/perseus", features = [ "hydrate", "builder", "dflt-engine" ] } +perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } sycamore = "=0.8.0-beta.6" serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/examples/core/basic/src/lib.rs b/examples/core/basic/src/lib.rs index 58c86c6027..a2df7d8368 100644 --- a/examples/core/basic/src/lib.rs +++ b/examples/core/basic/src/lib.rs @@ -16,7 +16,7 @@ pub fn get_app() -> PerseusApp { #[tokio::main] async fn main() { let op = get_op().unwrap(); - let exit_code = run_dflt_engine(op, get_app()).await; + let exit_code = run_dflt_engine(op, get_app(), perseus_warp::dflt_server).await; std::process::exit(exit_code); } diff --git a/packages/perseus-actix-web/Cargo.toml b/packages/perseus-actix-web/Cargo.toml index ff3020e8c9..fc2ab9deb3 100644 --- a/packages/perseus-actix-web/Cargo.toml +++ b/packages/perseus-actix-web/Cargo.toml @@ -26,3 +26,7 @@ thiserror = "1" fmterr = "0.1" futures = "0.3" sycamore = { version = "=0.8.0-beta.6", features = ["ssr"] } + +[features] +# Enables the default server configuration, which provides a convenience function if you're not adding any extra routes +dflt-server = [ "perseus/builder" ] diff --git a/packages/perseus-actix-web/src/dflt_server.rs b/packages/perseus-actix-web/src/dflt_server.rs new file mode 100644 index 0000000000..e23eec23ac --- /dev/null +++ b/packages/perseus-actix-web/src/dflt_server.rs @@ -0,0 +1,30 @@ +use actix_web::{App, HttpServer}; +use perseus::{PerseusAppBase, SsrNode, internal::i18n::TranslationsManager, stores::MutableStore, builder::{get_standalone_and_act, get_host_and_port, get_props}}; +use crate::configurer; +use futures::executor::block_on; + +/// Creates and starts the default Perseus server using Actix Web. This should be run in a `main()` function annotated with `#[tokio::main]` (which requires the `macros` and +/// `rt-multi-thread` features on the `tokio` dependency). +/// +/// Note that this takes a function that generates your `PerseusApp`, which is due to significant lifetime and thread constraints within Actix. +pub async fn dflt_server(app: impl Fn() -> PerseusAppBase + 'static + Send + Sync + Clone) { + get_standalone_and_act(); + let (host, port) = get_host_and_port(); + + HttpServer::new(move || + App::new() + .configure( + block_on( + configurer( + get_props( + app() + ) + ) + ) + ) + ) + .bind((host, port)) + .expect("Couldn't bind to given address. Maybe something is already running on the selected port?") + .run() + .await +} diff --git a/packages/perseus-actix-web/src/lib.rs b/packages/perseus-actix-web/src/lib.rs index e68f19fe89..3dd19902d2 100644 --- a/packages/perseus-actix-web/src/lib.rs +++ b/packages/perseus-actix-web/src/lib.rs @@ -14,6 +14,10 @@ pub mod errors; mod initial_load; mod page_data; mod translations; +#[cfg(feature = "dflt-server")] +mod dflt_server; pub use crate::configurer::configurer; pub use perseus::internal::serve::ServerOptions; +#[cfg(feature = "dflt-server")] +pub use dflt_server::dflt_server; diff --git a/packages/perseus-axum/Cargo.toml b/packages/perseus-axum/Cargo.toml index 16e8bfc8e0..1c21ee9592 100644 --- a/packages/perseus-axum/Cargo.toml +++ b/packages/perseus-axum/Cargo.toml @@ -26,3 +26,7 @@ fmterr = "0.1" futures = "0.3" sycamore = { version = "=0.8.0-beta.6", features = ["ssr"] } closure = "0.3" + +[features] +# Enables the default server configuration, which provides a convenience function if you're not adding any extra routes +dflt-server = [ "perseus/builder" ] diff --git a/packages/perseus-axum/src/dflt_server.rs b/packages/perseus-axum/src/dflt_server.rs new file mode 100644 index 0000000000..264f3c7306 --- /dev/null +++ b/packages/perseus-axum/src/dflt_server.rs @@ -0,0 +1,20 @@ +use perseus::{PerseusAppBase, SsrNode, internal::i18n::TranslationsManager, stores::MutableStore, builder::{get_standalone_and_act, get_host_and_port, get_props}}; +use crate::get_router; +use std::net::SocketAddr; +use futures::executor::block_on; + +/// Creates and starts the default Perseus server with Axum. This should be run in a `main` function annotated with `#[tokio::main]` (which requires the `macros` and +/// `rt-multi-thread` features on the `tokio` dependency). +pub async fn dflt_server(app: PerseusAppBase) { + get_standalone_and_act(); + let props = get_props(app); + let (host, port) = get_host_and_port(); + let addr: SocketAddr = format!("{}:{}", host, port) + .parse() + .expect("Invalid address provided to bind to."); + let app = block_on(get_router(props)); + axum::Server::bind(&addr) + .serve(app.into_make_service()) + .await + .unwrap(); +} diff --git a/packages/perseus-axum/src/lib.rs b/packages/perseus-axum/src/lib.rs index 3b07eba330..0a39370ff5 100644 --- a/packages/perseus-axum/src/lib.rs +++ b/packages/perseus-axum/src/lib.rs @@ -13,6 +13,10 @@ mod initial_load; mod page_data; mod router; mod translations; +#[cfg(feature = "dflt-server")] +mod dflt_server; pub use crate::router::get_router; pub use perseus::internal::serve::ServerOptions; +#[cfg(feature = "dflt-server")] +pub use dflt_server::dflt_server; diff --git a/packages/perseus-warp/Cargo.toml b/packages/perseus-warp/Cargo.toml index c93415843c..00447bd777 100644 --- a/packages/perseus-warp/Cargo.toml +++ b/packages/perseus-warp/Cargo.toml @@ -24,3 +24,7 @@ thiserror = "1" fmterr = "0.1" futures = "0.3" sycamore = { version = "=0.8.0-beta.6", features = ["ssr"] } + +[features] +# Enables the default server configuration, which provides a convenience function if you're not adding any extra routes +dflt-server = [ "perseus/builder" ] diff --git a/packages/perseus-warp/src/dflt_server.rs b/packages/perseus-warp/src/dflt_server.rs new file mode 100644 index 0000000000..637fcbcf5e --- /dev/null +++ b/packages/perseus-warp/src/dflt_server.rs @@ -0,0 +1,17 @@ +use perseus::{PerseusAppBase, SsrNode, internal::i18n::TranslationsManager, stores::MutableStore, builder::{get_standalone_and_act, get_host_and_port, get_props}}; +use crate::perseus_routes; +use std::net::SocketAddr; +use futures::executor::block_on; + +/// Creates and starts the default Perseus server with Warp. This should be run in a `main` function annotated with `#[tokio::main]` (which requires the `macros` and +/// `rt-multi-thread` features on the `tokio` dependency). +pub async fn dflt_server(app: PerseusAppBase) { + get_standalone_and_act(); + let props = get_props(app); + let (host, port) = get_host_and_port(); + let addr: SocketAddr = format!("{}:{}", host, port) + .parse() + .expect("Invalid address provided to bind to."); + let routes = block_on(perseus_routes(props)); + warp::serve(routes).run(addr).await; +} diff --git a/packages/perseus-warp/src/lib.rs b/packages/perseus-warp/src/lib.rs index b2bdd1f506..245137a5b3 100644 --- a/packages/perseus-warp/src/lib.rs +++ b/packages/perseus-warp/src/lib.rs @@ -14,6 +14,10 @@ mod page_data; mod perseus_routes; mod static_content; mod translations; +#[cfg(feature = "dflt-server")] +mod dflt_server; pub use crate::perseus_routes::perseus_routes; pub use perseus::internal::serve::ServerOptions; +#[cfg(feature = "dflt-server")] +pub use dflt_server::dflt_server; diff --git a/packages/perseus/src/engine/dflt_engine.rs b/packages/perseus/src/engine/dflt_engine.rs index ffff047e33..0abe34fb31 100644 --- a/packages/perseus/src/engine/dflt_engine.rs +++ b/packages/perseus/src/engine/dflt_engine.rs @@ -3,16 +3,24 @@ use crate::{PerseusAppBase, i18n::TranslationsManager, stores::MutableStore, SsrNode}; use super::EngineOperation; use fmterr::fmt_err; +use futures::Future; use std::env; /// A convenience function that automatically runs the necessary engine operation based on the given directive. This provides almost no options for customization, and is /// usually elided by a macro. More advanced use-cases should bypass this and call the functions this calls manually, with their own configurations. /// +/// The third argument to this is a function to produce a server. In simple cases, this will be the `dflt_server` export from your server integration of choice (which is +/// assumed to use a Tokio 1.x runtime). This function must be infallible (any errors should be panics, as they *will* be treated as unrecoverable). +/// /// If the action is to export a single error page, the HTTP status code of the error page to export and the output will be read as the first and second arguments /// to the binary invocation. If this is not the desired behavior, you should handle the `EngineOperation::ExportErrorPage` case manually. /// /// This returns an exit code, which should be returned from the process. Any hanlded errors will be printed to the console. -pub async fn run_dflt_engine(op: EngineOperation, app: PerseusAppBase) -> i32 { +pub async fn run_dflt_engine>( + op: EngineOperation, + app: PerseusAppBase, + serve_fn: impl Fn(PerseusAppBase) -> F +) -> i32 { match op { EngineOperation::Build => match super::engine_build(app).await { Ok(_) => 0, @@ -61,7 +69,10 @@ pub async fn run_dflt_engine(op: EngineOperation, app: PerseusAppBase todo!(), + EngineOperation::Serve => { + serve_fn(app).await; + 0 + }, EngineOperation::Tinker => { // This is infallible (though plugins could panic) super::engine_tinker(app); diff --git a/packages/perseus/src/engine/mod.rs b/packages/perseus/src/engine/mod.rs index 8fd5a22412..62aadde60b 100644 --- a/packages/perseus/src/engine/mod.rs +++ b/packages/perseus/src/engine/mod.rs @@ -14,3 +14,6 @@ pub use dflt_engine::run_dflt_engine; mod get_op; pub use get_op::{get_op, EngineOperation}; + +mod serve; +pub use serve::{get_host_and_port, get_standalone_and_act, get_props}; diff --git a/packages/perseus/src/engine/serve.rs b/packages/perseus/src/engine/serve.rs new file mode 100644 index 0000000000..00a685d837 --- /dev/null +++ b/packages/perseus/src/engine/serve.rs @@ -0,0 +1,101 @@ +use futures::executor::block_on; +use sycamore::web::SsrNode; +use std::env; +use std::fs; +use crate::PerseusAppBase; +use crate::i18n::TranslationsManager; +use crate::server::{ServerOptions, ServerProps}; +use crate::stores::MutableStore; +use crate::plugins::PluginAction; + +// TODO Can we unify the two modes of server execution now? +// This server executable can be run in two modes: +// dev: at the root of the project, works with that file structure +// prod: as a standalone executable with a `dist/` directory as a sibling (also present with the dev file structure) + +// Note: the default servers for integrations are now stored in the crates of those integrations + +/// Determines whether or not we're operating in standalone mode, and acts accordingly. This MUST be executed in the parent thread, as it switches the current directory. +pub fn get_standalone_and_act() -> bool { + // So we don't have to define a different `FsConfigManager` just for the server, we shift the execution context to the same level as everything else + // The server has to be a separate crate because otherwise the dependencies don't work with Wasm bundling + // If we're not running as a standalone binary, assume we're running in dev mode at the root of the user's project + if cfg!(feature = "standalone") { + // If we are running as a standalone binary, we have no idea where we're being executed from (#63), so we should set the working directory to be the same as the binary location + let binary_loc = env::current_exe().unwrap(); + let binary_dir = binary_loc.parent().unwrap(); // It's a file, there's going to be a parent if we're working on anything close to sanity + env::set_current_dir(binary_dir).unwrap(); + true + } else { + false + } +} + +/// Gets the host and port to serve on based on environment variables, which are universally used for configuration regardless of engine. +pub fn get_host_and_port() -> (String, u16) { + // We have to use two sets of environment variables until v0.4.0 + let host = env::var("PERSEUS_HOST"); + let port = env::var("PERSEUS_PORT"); + + let host = host.unwrap_or_else(|_| "127.0.0.1".to_string()); + let port = port + .unwrap_or_else(|_| "8080".to_string()) + .parse::() + .expect("Port must be a number."); + + (host, port) +} + +/// Gets the properties to pass to the server, invoking plugin opportunities as necessary. This is entirely engine-agnostic. +pub fn get_props(app: PerseusAppBase) -> ServerProps { + let plugins = app.get_plugins(); + + plugins + .functional_actions + .server_actions + .before_serve + .run((), plugins.get_plugin_data()); + + let static_dir_path = app.get_static_dir().to_string(); + + let app_root = app.get_root().to_string(); + let immutable_store = app.get_immutable_store(); + let index_view_str = app.get_index_view_str(); + // By the time this binary is being run, the app has already been built be the CLI (hopefully!), so we can depend on access to the render config + let index_view = block_on(PerseusAppBase::::get_html_shell( + index_view_str, + &app_root, + &immutable_store, + &plugins, + )); + + let opts = ServerOptions { + // We don't support setting some attributes from `wasm-pack` through plugins/`PerseusApp` because that would require CLI changes as well (a job for an alternative engine) + html_shell: index_view, + js_bundle: "dist/pkg/perseus_engine.js".to_string(), + // Our crate has the same name, so this will be predictable + wasm_bundle: "dist/pkg/perseus_engine_bg.wasm".to_string(), + // This probably won't exist, but on the off chance that the user needs to support older browsers, we'll provide it anyway + wasm_js_bundle: "dist/pkg/perseus_engine_bg.wasm.js".to_string(), + templates_map: app.get_atomic_templates_map(), + locales: app.get_locales(), + root_id: app_root, + snippets: "dist/pkg/snippets".to_string(), + error_pages: app.get_error_pages(), + // This will be available directly at `/.perseus/static` + static_dir: if fs::metadata(&static_dir_path).is_ok() { + Some(static_dir_path) + } else { + None + }, + static_aliases: app.get_static_aliases(), + }; + + ServerProps { + opts, + immutable_store, + mutable_store: app.get_mutable_store(), + global_state_creator: app.get_global_state_creator(), + translations_manager: block_on(app.get_translations_manager()), + } +} diff --git a/packages/perseus/src/macros.rs b/packages/perseus/src/macros.rs index 7fc5ad4c76..52459f6d2c 100644 --- a/packages/perseus/src/macros.rs +++ b/packages/perseus/src/macros.rs @@ -14,14 +14,9 @@ macro_rules! add_translations_manager { }; } -#[cfg(feature = "standalone")] -#[doc(hidden)] -/// The default translations directory when we're running as a standalone binary. -pub static DFLT_TRANSLATIONS_DIR: &str = "./translations"; -#[cfg(not(feature = "standalone"))] #[doc(hidden)] /// The default translations directory when we're running with the `.perseus/` support structure. -pub static DFLT_TRANSLATIONS_DIR: &str = "../translations"; +pub static DFLT_TRANSLATIONS_DIR: &str = "./translations"; /// Defines the components to create an entrypoint for the app. The actual entrypoint is created in the `.perseus/` crate (where we can /// get all the dependencies without driving the user's `Cargo.toml` nuts). This also defines the template map. This is intended to make From a50875c7999546eef983ee7de6f637b1b4c78245 Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Sun, 12 Jun 2022 08:37:53 +1000 Subject: [PATCH 03/37] feat: integrated client-side code This is untested as yet, I'll update the CLI first. --- Cargo.toml | 9 ++ examples/core/basic/src/lib.rs | 8 +- packages/perseus-actix-web/src/dflt_server.rs | 13 ++- packages/perseus-actix-web/src/lib.rs | 6 +- packages/perseus-axum/src/dflt_server.rs | 13 ++- packages/perseus-axum/src/lib.rs | 6 +- packages/perseus-warp/src/dflt_server.rs | 13 ++- packages/perseus-warp/src/lib.rs | 6 +- packages/perseus/Cargo.toml | 5 +- packages/perseus/src/client.rs | 53 ++++++++++ packages/perseus/src/engine/build.rs | 11 +- packages/perseus/src/engine/dflt_engine.rs | 24 +++-- packages/perseus/src/engine/export.rs | 55 +++++++--- .../perseus/src/engine/export_error_page.rs | 30 +++--- packages/perseus/src/engine/get_op.rs | 2 +- packages/perseus/src/engine/mod.rs | 2 +- packages/perseus/src/engine/serve.rs | 16 +-- packages/perseus/src/engine/tinker.rs | 2 +- packages/perseus/src/errors.rs | 4 +- packages/perseus/src/init.rs | 4 +- packages/perseus/src/lib.rs | 10 +- packages/perseus/src/plugins/functional.rs | 6 +- packages/perseus/src/router/app_route.rs | 100 ++++++++++++------ .../perseus/src/router/router_component.rs | 4 +- packages/perseus/src/stores/immutable.rs | 5 +- 25 files changed, 287 insertions(+), 120 deletions(-) create mode 100644 packages/perseus/src/client.rs diff --git a/Cargo.toml b/Cargo.toml index b577e95e14..92bf5b5366 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,12 @@ members = [ "examples/core/basic/.perseus/server", "examples/core/basic/.perseus/builder" ] + +[patch.crates-io] +sycamore = { git = "https://github.com/arctic-hen7/sycamore" } +sycamore-router = { git = "https://github.com/arctic-hen7/sycamore" } +sycamore-router-macro = { git = "https://github.com/arctic-hen7/sycamore" } +sycamore-macro = { git = "https://github.com/arctic-hen7/sycamore" } +sycamore-core = { git = "https://github.com/arctic-hen7/sycamore" } +sycamore-reactive = { git = "https://github.com/arctic-hen7/sycamore" } +sycamore-web = { git = "https://github.com/arctic-hen7/sycamore" } diff --git a/examples/core/basic/src/lib.rs b/examples/core/basic/src/lib.rs index a2df7d8368..236905930a 100644 --- a/examples/core/basic/src/lib.rs +++ b/examples/core/basic/src/lib.rs @@ -2,7 +2,6 @@ mod error_pages; mod templates; use perseus::{Html, PerseusApp}; -use perseus::builder::{get_op, run_dflt_engine}; pub fn get_app() -> PerseusApp { PerseusApp::new() @@ -15,12 +14,17 @@ pub fn get_app() -> PerseusApp { #[allow(dead_code)] #[tokio::main] async fn main() { + use perseus::builder::{get_op, run_dflt_engine}; + let op = get_op().unwrap(); let exit_code = run_dflt_engine(op, get_app(), perseus_warp::dflt_server).await; std::process::exit(exit_code); } #[cfg(target_arch = "wasm32")] -fn main() { +#[wasm_bindgen] +fn main() -> perseus::ClientReturn { + use perseus::run_client; + run_client(get_app()) } diff --git a/packages/perseus-actix-web/src/dflt_server.rs b/packages/perseus-actix-web/src/dflt_server.rs index e23eec23ac..444fc4c03a 100644 --- a/packages/perseus-actix-web/src/dflt_server.rs +++ b/packages/perseus-actix-web/src/dflt_server.rs @@ -1,13 +1,20 @@ -use actix_web::{App, HttpServer}; -use perseus::{PerseusAppBase, SsrNode, internal::i18n::TranslationsManager, stores::MutableStore, builder::{get_standalone_and_act, get_host_and_port, get_props}}; use crate::configurer; +use actix_web::{App, HttpServer}; use futures::executor::block_on; +use perseus::{ + builder::{get_host_and_port, get_props, get_standalone_and_act}, + internal::i18n::TranslationsManager, + stores::MutableStore, + PerseusAppBase, SsrNode, +}; /// Creates and starts the default Perseus server using Actix Web. This should be run in a `main()` function annotated with `#[tokio::main]` (which requires the `macros` and /// `rt-multi-thread` features on the `tokio` dependency). /// /// Note that this takes a function that generates your `PerseusApp`, which is due to significant lifetime and thread constraints within Actix. -pub async fn dflt_server(app: impl Fn() -> PerseusAppBase + 'static + Send + Sync + Clone) { +pub async fn dflt_server( + app: impl Fn() -> PerseusAppBase + 'static + Send + Sync + Clone, +) { get_standalone_and_act(); let (host, port) = get_host_and_port(); diff --git a/packages/perseus-actix-web/src/lib.rs b/packages/perseus-actix-web/src/lib.rs index 3dd19902d2..fe70fa6211 100644 --- a/packages/perseus-actix-web/src/lib.rs +++ b/packages/perseus-actix-web/src/lib.rs @@ -10,14 +10,14 @@ documentation, and this should mostly be used as a secondary reference source. Y mod configurer; mod conv_req; +#[cfg(feature = "dflt-server")] +mod dflt_server; pub mod errors; mod initial_load; mod page_data; mod translations; -#[cfg(feature = "dflt-server")] -mod dflt_server; pub use crate::configurer::configurer; -pub use perseus::internal::serve::ServerOptions; #[cfg(feature = "dflt-server")] pub use dflt_server::dflt_server; +pub use perseus::internal::serve::ServerOptions; diff --git a/packages/perseus-axum/src/dflt_server.rs b/packages/perseus-axum/src/dflt_server.rs index 264f3c7306..7ad5c8a5d1 100644 --- a/packages/perseus-axum/src/dflt_server.rs +++ b/packages/perseus-axum/src/dflt_server.rs @@ -1,11 +1,18 @@ -use perseus::{PerseusAppBase, SsrNode, internal::i18n::TranslationsManager, stores::MutableStore, builder::{get_standalone_and_act, get_host_and_port, get_props}}; use crate::get_router; -use std::net::SocketAddr; use futures::executor::block_on; +use perseus::{ + builder::{get_host_and_port, get_props, get_standalone_and_act}, + internal::i18n::TranslationsManager, + stores::MutableStore, + PerseusAppBase, SsrNode, +}; +use std::net::SocketAddr; /// Creates and starts the default Perseus server with Axum. This should be run in a `main` function annotated with `#[tokio::main]` (which requires the `macros` and /// `rt-multi-thread` features on the `tokio` dependency). -pub async fn dflt_server(app: PerseusAppBase) { +pub async fn dflt_server( + app: PerseusAppBase, +) { get_standalone_and_act(); let props = get_props(app); let (host, port) = get_host_and_port(); diff --git a/packages/perseus-axum/src/lib.rs b/packages/perseus-axum/src/lib.rs index 0a39370ff5..3133791dd3 100644 --- a/packages/perseus-axum/src/lib.rs +++ b/packages/perseus-axum/src/lib.rs @@ -9,14 +9,14 @@ documentation, and this should mostly be used as a secondary reference source. Y #![deny(missing_docs)] // This integration doesn't need to convert request types, because we can get them straight out of Axum and then just delete the bodies +#[cfg(feature = "dflt-server")] +mod dflt_server; mod initial_load; mod page_data; mod router; mod translations; -#[cfg(feature = "dflt-server")] -mod dflt_server; pub use crate::router::get_router; -pub use perseus::internal::serve::ServerOptions; #[cfg(feature = "dflt-server")] pub use dflt_server::dflt_server; +pub use perseus::internal::serve::ServerOptions; diff --git a/packages/perseus-warp/src/dflt_server.rs b/packages/perseus-warp/src/dflt_server.rs index 637fcbcf5e..ccc6b01d56 100644 --- a/packages/perseus-warp/src/dflt_server.rs +++ b/packages/perseus-warp/src/dflt_server.rs @@ -1,11 +1,18 @@ -use perseus::{PerseusAppBase, SsrNode, internal::i18n::TranslationsManager, stores::MutableStore, builder::{get_standalone_and_act, get_host_and_port, get_props}}; use crate::perseus_routes; -use std::net::SocketAddr; use futures::executor::block_on; +use perseus::{ + builder::{get_host_and_port, get_props, get_standalone_and_act}, + internal::i18n::TranslationsManager, + stores::MutableStore, + PerseusAppBase, SsrNode, +}; +use std::net::SocketAddr; /// Creates and starts the default Perseus server with Warp. This should be run in a `main` function annotated with `#[tokio::main]` (which requires the `macros` and /// `rt-multi-thread` features on the `tokio` dependency). -pub async fn dflt_server(app: PerseusAppBase) { +pub async fn dflt_server( + app: PerseusAppBase, +) { get_standalone_and_act(); let props = get_props(app); let (host, port) = get_host_and_port(); diff --git a/packages/perseus-warp/src/lib.rs b/packages/perseus-warp/src/lib.rs index 245137a5b3..c7e2828d77 100644 --- a/packages/perseus-warp/src/lib.rs +++ b/packages/perseus-warp/src/lib.rs @@ -9,15 +9,15 @@ documentation, and this should mostly be used as a secondary reference source. Y #![deny(missing_docs)] mod conv_req; +#[cfg(feature = "dflt-server")] +mod dflt_server; mod initial_load; mod page_data; mod perseus_routes; mod static_content; mod translations; -#[cfg(feature = "dflt-server")] -mod dflt_server; pub use crate::perseus_routes::perseus_routes; -pub use perseus::internal::serve::ServerOptions; #[cfg(feature = "dflt-server")] pub use dflt_server::dflt_server; +pub use perseus::internal::serve::ServerOptions; diff --git a/packages/perseus/Cargo.toml b/packages/perseus/Cargo.toml index 86af8ff01c..f687891385 100644 --- a/packages/perseus/Cargo.toml +++ b/packages/perseus/Cargo.toml @@ -38,6 +38,7 @@ tokio = { version = "1", features = [ "fs", "io-util" ] } rexie = { version = "0.2", optional = true } js-sys = { version = "0.3", optional = true } fs_extra = { version = "1", optional = true } +console_error_panic_hook = { version = "0.1.6", optional = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] regex = "1" @@ -45,7 +46,7 @@ regex = "1" [features] # Live reloading will only take effect in development, and won't impact production # BUG This adds 1.9kB to the production bundle (that's without size optimizations though) -default = [ "live-reload", "hsr" ] +default = [ "live-reload", "hsr", "builder", "client-helpers" ] translator-fluent = ["fluent-bundle", "unic-langid", "intl-memoizer"] # This feature makes tinker-only plugins be registered (this flag is enabled internally in the engine) tinker-plugins = [] @@ -56,6 +57,8 @@ server-side = [] builder = [ "server-side", "fs_extra" ] # This feature enable support for functions that make using the default engine configuration much easier. dflt-engine = [ "builder" ] +# This features enables client-side helpers designed to be run in the browser. +client-helpers = [ "console_error_panic_hook" ] # This feature changes a few defaults so that Perseus works seemlessly when deployed with the `.perseus/` structure (this is activated automatically by `perseus deploy`, and should not be invoked manually!) standalone = [] # This feature enables Sycamore hydration by default (Sycamore hydration feature is always activated though) diff --git a/packages/perseus/src/client.rs b/packages/perseus/src/client.rs new file mode 100644 index 0000000000..7e89539895 --- /dev/null +++ b/packages/perseus/src/client.rs @@ -0,0 +1,53 @@ +use crate::{ + checkpoint, + internal::router::{perseus_router, PerseusRouterProps}, + plugins::PluginAction, +}; +use sycamore::web::DomNode; +use wasm_bindgen::JsValue; + +use crate::{i18n::TranslationsManager, stores::MutableStore, PerseusAppBase}; + +/// The entrypoint into the app itself. This will be compiled to Wasm and actually executed, rendering the rest of the app. +/// Runs the app in the browser on the client-side. This is designed to be executed in a function annotated with `#[wasm_bindgen]`. +/// +/// This is entirely engine-agnostic, using only the properties from the given `PerseusApp`. +pub fn run_client( + app: PerseusAppBase, +) -> Result<(), JsValue> { + let plugins = app.get_plugins(); + + checkpoint("begin"); + // Panics should always go to the console + std::panic::set_hook(Box::new(console_error_panic_hook::hook)); + + plugins + .functional_actions + .client_actions + .start + .run((), plugins.get_plugin_data()); + checkpoint("initial_plugins_complete"); + + // Get the root we'll be injecting the router into + let root = web_sys::window() + .unwrap() + .document() + .unwrap() + .query_selector(&format!("#{}", app.get_root())) + .unwrap() + .unwrap(); + + // Set up the properties we'll pass to the router + let router_props = PerseusRouterProps { + locales: app.get_locales(), + error_pages: app.get_error_pages(), + }; + + // This top-level context is what we use for everything, allowing page state to be registered and stored for the lifetime of the app + sycamore::render_to(move |cx| perseus_router(cx, router_props), &root); + + Ok(()) +} + +/// A convenience type wrapper for the type returned by nearly all client-side entrypoints. +pub type ClientReturn = Result<(), JsValue>; diff --git a/packages/perseus/src/engine/build.rs b/packages/perseus/src/engine/build.rs index 25ea003d93..22d7c4717c 100644 --- a/packages/perseus/src/engine/build.rs +++ b/packages/perseus/src/engine/build.rs @@ -1,11 +1,18 @@ -use crate::{PerseusAppBase, PluginAction, SsrNode, errors::{ServerError, EngineError}, i18n::TranslationsManager, stores::MutableStore}; use crate::build::{build_app, BuildProps}; +use crate::{ + errors::{EngineError, ServerError}, + i18n::TranslationsManager, + stores::MutableStore, + PerseusAppBase, PluginAction, SsrNode, +}; use std::rc::Rc; /// Builds the app, calling all necessary plugin opportunities. This works solely with the properties provided in the given `PerseusApp`, so this is entirely engine-agnostic. /// /// Note that this expects to be run in the root of the project. -pub async fn build(app: PerseusAppBase) -> Result<(), Rc> { +pub async fn build( + app: PerseusAppBase, +) -> Result<(), Rc> { let plugins = app.get_plugins(); plugins diff --git a/packages/perseus/src/engine/dflt_engine.rs b/packages/perseus/src/engine/dflt_engine.rs index 0abe34fb31..25b234d4d4 100644 --- a/packages/perseus/src/engine/dflt_engine.rs +++ b/packages/perseus/src/engine/dflt_engine.rs @@ -1,7 +1,7 @@ // This file contains functions exclusive to the default engine systems -use crate::{PerseusAppBase, i18n::TranslationsManager, stores::MutableStore, SsrNode}; use super::EngineOperation; +use crate::{i18n::TranslationsManager, stores::MutableStore, PerseusAppBase, SsrNode}; use fmterr::fmt_err; use futures::Future; use std::env; @@ -19,7 +19,7 @@ use std::env; pub async fn run_dflt_engine>( op: EngineOperation, app: PerseusAppBase, - serve_fn: impl Fn(PerseusAppBase) -> F + serve_fn: impl Fn(PerseusAppBase) -> F, ) -> i32 { match op { EngineOperation::Build => match super::engine_build(app).await { @@ -41,13 +41,15 @@ pub async fn run_dflt_engine>(); let code = match args.get(1) { - Some(arg) => match arg.parse::() { - Ok(err_code) => err_code, - Err(_) => { - eprintln!("HTTP status code for error page exporting must be a valid integer."); - return 1; + Some(arg) => { + match arg.parse::() { + Ok(err_code) => err_code, + Err(_) => { + eprintln!("HTTP status code for error page exporting must be a valid integer."); + return 1; + } } - }, + } None => { eprintln!("Error page exporting requires an HTTP status code for which to export the error page."); return 1; @@ -68,15 +70,15 @@ pub async fn run_dflt_engine { serve_fn(app).await; 0 - }, + } EngineOperation::Tinker => { // This is infallible (though plugins could panic) super::engine_tinker(app); 0 - }, + } } } diff --git a/packages/perseus/src/engine/export.rs b/packages/perseus/src/engine/export.rs index 7eb897b419..a1cc89501c 100644 --- a/packages/perseus/src/engine/export.rs +++ b/packages/perseus/src/engine/export.rs @@ -1,22 +1,23 @@ -use fs_extra::dir::{copy as copy_dir, CopyOptions}; -use crate::errors::ServerError; -use crate::{PerseusApp, PluginAction, Plugins, SsrNode, internal::get_path_prefix_server}; use crate::build::{build_app, BuildProps}; +use crate::errors::ServerError; use crate::export::{export_app, ExportProps}; +use crate::{internal::get_path_prefix_server, PerseusApp, PluginAction, Plugins, SsrNode}; +use fs_extra::dir::{copy as copy_dir, CopyOptions}; +use std::collections::HashMap; use std::fs; use std::path::PathBuf; -use std::collections::HashMap; use std::rc::Rc; -use crate::{PerseusAppBase, i18n::TranslationsManager, stores::MutableStore}; use crate::errors::*; - +use crate::{i18n::TranslationsManager, stores::MutableStore, PerseusAppBase}; /// Exports the app to static files, given a `PerseusApp`. This is engine-agnostic, using the `exported` subfolder in the immutable store as a destination directory. By default /// this will end up at `dist/exported/` (customizable through `PerseusApp`). /// /// Note that this expects to be run in the root of the project. -pub async fn export(app: PerseusAppBase) -> Result<(), Rc> { +pub async fn export( + app: PerseusAppBase, +) -> Result<(), Rc> { let plugins = app.get_plugins(); let static_aliases = app.get_static_aliases(); // This won't have any trailing slashes (they're stripped by the immutable store initializer) @@ -38,7 +39,9 @@ pub async fn export(app: PerseusAppBase } /// Performs the building and exporting processes using the given app. This is fully engine-agnostic, using only the data provided in the given `PerseusApp`. -async fn build_and_export(app: PerseusAppBase) -> Result<(), Rc> { +async fn build_and_export( + app: PerseusAppBase, +) -> Result<(), Rc> { let plugins = app.get_plugins(); plugins @@ -130,7 +133,11 @@ async fn build_and_export(app: PerseusA /// /// The error type here is a tuple of the location the asset was copied from, the location it was copied to, and the error in that process (which could be from `io` or /// `fs_extra`). -fn copy_static_aliases(plugins: &Plugins, static_aliases: &HashMap, dest: &str) -> Result<(), Rc> { +fn copy_static_aliases( + plugins: &Plugins, + static_aliases: &HashMap, + dest: &str, +) -> Result<(), Rc> { // Loop through any static aliases and copy them in too // Unlike with the server, these could override pages! // We'll copy from the alias to the path (it could be a directory or a file) @@ -141,7 +148,11 @@ fn copy_static_aliases(plugins: &Plugins, static_aliases: &HashMap, static_aliases: &HashMap, static_aliases: &HashMap, static_dir_raw: &str, dest: &str) -> Result<(), Rc> { +fn copy_static_dir( + plugins: &Plugins, + static_dir_raw: &str, + dest: &str, +) -> Result<(), Rc> { // Copy the `static` directory into the export package if it exists // If the user wants extra, they can use static aliases, plugins are unnecessary here let static_dir = PathBuf::from(static_dir_raw); if static_dir.exists() { - if let Err(err) = copy_dir(&static_dir, format!("{}/.perseus/", dest), &CopyOptions::new()) { - let err = EngineError::CopyStaticDirError{ source: err, path: static_dir_raw.to_string(), dest: dest.to_string() }; + if let Err(err) = copy_dir( + &static_dir, + format!("{}/.perseus/", dest), + &CopyOptions::new(), + ) { + let err = EngineError::CopyStaticDirError { + source: err, + path: static_dir_raw.to_string(), + dest: dest.to_string(), + }; let err = Rc::new(err); plugins .functional_actions diff --git a/packages/perseus/src/engine/export_error_page.rs b/packages/perseus/src/engine/export_error_page.rs index 312c7d9ff4..a561ef9c6a 100644 --- a/packages/perseus/src/engine/export_error_page.rs +++ b/packages/perseus/src/engine/export_error_page.rs @@ -1,4 +1,7 @@ -use crate::{PerseusApp, PerseusAppBase, PluginAction, SsrNode, errors::EngineError, i18n::TranslationsManager, internal::serve::build_error_page, stores::MutableStore}; +use crate::{ + errors::EngineError, i18n::TranslationsManager, internal::serve::build_error_page, + stores::MutableStore, PerseusApp, PerseusAppBase, PluginAction, SsrNode, +}; use std::{fs, rc::Rc}; /// Exports a single error page for the given HTTP status code to the given output location. If the status code doesn't exist or isn't handled, then the fallback page will be @@ -7,7 +10,11 @@ use std::{fs, rc::Rc}; /// This expects to run in the root of the project. /// /// This can only return IO errors from failures to write to the given output location. (Wrapped in an `Rc` so they can be sent to plugins as well.) -pub async fn export_error_page(app: PerseusAppBase, code: u16, output: &str) -> Result<(), Rc> { +pub async fn export_error_page( + app: PerseusAppBase, + code: u16, + output: &str, +) -> Result<(), Rc> { let plugins = app.get_plugins(); let error_pages = app.get_error_pages(); @@ -24,26 +31,19 @@ pub async fn export_error_page(app: PerseusAppBase (), Err(err) => { - let err = EngineError::WriteErrorPageError { source: err, dest: output.to_string() }; + let err = EngineError::WriteErrorPageError { + source: err, + dest: output.to_string(), + }; let err = Rc::new(err); plugins .functional_actions diff --git a/packages/perseus/src/engine/get_op.rs b/packages/perseus/src/engine/get_op.rs index 481e5b5c33..d987e29778 100644 --- a/packages/perseus/src/engine/get_op.rs +++ b/packages/perseus/src/engine/get_op.rs @@ -9,7 +9,7 @@ pub fn get_op() -> Option { "export" => Some(EngineOperation::Export), "export_error_page" => Some(EngineOperation::ExportErrorPage), "tinker" => Some(EngineOperation::Tinker), - _ => None + _ => None, } } diff --git a/packages/perseus/src/engine/mod.rs b/packages/perseus/src/engine/mod.rs index 62aadde60b..f9906c01a3 100644 --- a/packages/perseus/src/engine/mod.rs +++ b/packages/perseus/src/engine/mod.rs @@ -16,4 +16,4 @@ mod get_op; pub use get_op::{get_op, EngineOperation}; mod serve; -pub use serve::{get_host_and_port, get_standalone_and_act, get_props}; +pub use serve::{get_host_and_port, get_props, get_standalone_and_act}; diff --git a/packages/perseus/src/engine/serve.rs b/packages/perseus/src/engine/serve.rs index 00a685d837..4008c5b5c0 100644 --- a/packages/perseus/src/engine/serve.rs +++ b/packages/perseus/src/engine/serve.rs @@ -1,12 +1,12 @@ -use futures::executor::block_on; -use sycamore::web::SsrNode; -use std::env; -use std::fs; -use crate::PerseusAppBase; use crate::i18n::TranslationsManager; +use crate::plugins::PluginAction; use crate::server::{ServerOptions, ServerProps}; use crate::stores::MutableStore; -use crate::plugins::PluginAction; +use crate::PerseusAppBase; +use futures::executor::block_on; +use std::env; +use std::fs; +use sycamore::web::SsrNode; // TODO Can we unify the two modes of server execution now? // This server executable can be run in two modes: @@ -47,7 +47,9 @@ pub fn get_host_and_port() -> (String, u16) { } /// Gets the properties to pass to the server, invoking plugin opportunities as necessary. This is entirely engine-agnostic. -pub fn get_props(app: PerseusAppBase) -> ServerProps { +pub fn get_props( + app: PerseusAppBase, +) -> ServerProps { let plugins = app.get_plugins(); plugins diff --git a/packages/perseus/src/engine/tinker.rs b/packages/perseus/src/engine/tinker.rs index 3357a64cdb..fac2464963 100644 --- a/packages/perseus/src/engine/tinker.rs +++ b/packages/perseus/src/engine/tinker.rs @@ -1,5 +1,5 @@ -use crate::{plugins::PluginAction, SsrNode, PerseusAppBase}; use crate::{i18n::TranslationsManager, stores::MutableStore}; +use crate::{plugins::PluginAction, PerseusAppBase, SsrNode}; /// Runs tinker plugin actions. /// diff --git a/packages/perseus/src/errors.rs b/packages/perseus/src/errors.rs index 45ccdebe4d..2fdfd0d5d3 100644 --- a/packages/perseus/src/errors.rs +++ b/packages/perseus/src/errors.rs @@ -41,14 +41,14 @@ pub enum EngineError { #[source] source: fs_extra::error::Error, from: String, - to: String + to: String, }, #[error("couldn't write the generated error page to '{dest}'")] WriteErrorPageError { #[source] source: std::io::Error, dest: String, - } + }, } /// Errors that can occur in the browser. diff --git a/packages/perseus/src/init.rs b/packages/perseus/src/init.rs index 4ec4ac05ae..801abf3f5b 100644 --- a/packages/perseus/src/init.rs +++ b/packages/perseus/src/init.rs @@ -110,7 +110,7 @@ pub struct PerseusAppBase { translations_manager: Tm, /// The location of the directory to use for static assets that will placed under the URL `/.perseus/static/`. By default, this is the `static/` directory at the root /// of your project. Note that the directory set here will only be used if it exists. - static_dir: String + static_dir: String, } // The usual implementation in which the default mutable store is used @@ -202,7 +202,7 @@ impl PerseusAppBase { translations_manager: Tm::Dummy(T::new_dummy()), // Many users won't need anything fancy in the index view, so we provide a default index_view: DFLT_INDEX_VIEW.to_string(), - static_dir: "./static".to_string() + static_dir: "./static".to_string(), } } diff --git a/packages/perseus/src/lib.rs b/packages/perseus/src/lib.rs index 7b4d327527..bddfb0c782 100644 --- a/packages/perseus/src/lib.rs +++ b/packages/perseus/src/lib.rs @@ -35,8 +35,12 @@ pub mod state; pub mod stores; mod build; -mod export; +#[cfg(feature = "client-helpers")] +mod client; +#[cfg(feature = "builder")] +mod engine; mod error_pages; +mod export; mod i18n; mod init; mod macros; @@ -46,8 +50,6 @@ mod shell; mod template; mod translator; mod utils; -#[cfg(feature = "builder")] -mod engine; // The rest of this file is devoted to module structuring // Re-exports @@ -56,6 +58,8 @@ pub use http::Request as HttpRequest; pub use sycamore_futures::spawn_local_scoped; /// All HTTP requests use empty bodies for simplicity of passing them around. They'll never need payloads (value in path requested). pub type Request = HttpRequest<()>; +#[cfg(feature = "client-helpers")] +pub use client::{run_client, ClientReturn}; pub use perseus_macro::{autoserde, head, main, make_rx, template, template_rx, test}; pub use sycamore::prelude::{DomNode, Html, HydrateNode, SsrNode}; pub use sycamore_router::{navigate, navigate_replace, Route}; // TODO Should we be exporting `Route` anymore? diff --git a/packages/perseus/src/plugins/functional.rs b/packages/perseus/src/plugins/functional.rs index 3d0d8dbcc1..3879da94c1 100644 --- a/packages/perseus/src/plugins/functional.rs +++ b/packages/perseus/src/plugins/functional.rs @@ -161,8 +161,7 @@ pub struct FunctionalPluginBuildActions { /// Runs after the build process if it fails. pub after_failed_build: FunctionalPluginAction, ()>, /// Runs after the build process if it failed to generate global state. - pub after_failed_global_state_creation: - FunctionalPluginAction, ()>, + pub after_failed_global_state_creation: FunctionalPluginAction, ()>, } /// Functional actions that pertain to the export process. #[cfg(feature = "builder")] @@ -186,8 +185,7 @@ pub struct FunctionalPluginExportActions { pub after_successful_export: FunctionalPluginAction<(), ()>, /// Runs after the export process if it failed to generate global state. Note that the error here will always be a `GlobalStateError`, but it must be processed as a /// `ServerError` due to ownership constraints. - pub after_failed_global_state_creation: - FunctionalPluginAction, ()>, + pub after_failed_global_state_creation: FunctionalPluginAction, ()>, } /// Functional actions that pertain to the process of exporting an error page. #[cfg(feature = "builder")] diff --git a/packages/perseus/src/router/app_route.rs b/packages/perseus/src/router/app_route.rs index 30002610f5..546948bfbd 100644 --- a/packages/perseus/src/router/app_route.rs +++ b/packages/perseus/src/router/app_route.rs @@ -1,39 +1,73 @@ -use crate::Html; +use super::{match_route, RouteVerdict}; +use crate::{i18n::Locales, templates::TemplateMap, Html}; +use std::collections::HashMap; use sycamore_router::Route; -use super::RouteVerdict; +// /// Creates an app-specific routing `struct`. Sycamore expects an `enum` to do this, so we create a `struct` that behaves similarly. If +// /// we don't do this, we can't get the information necessary for routing into the `enum` at all (context and global variables don't suit +// /// this particular case). +// #[macro_export] +// macro_rules! create_app_route { +// { +// name => $name:ident, +// render_cfg => $render_cfg:expr, +// templates => $templates:expr, +// locales => $locales:expr +// } => { +// /// The route type for the app, with all routing logic inbuilt through the generation macro. +// #[derive(::std::clone::Clone)] +// struct $name($crate::internal::router::RouteVerdict); +// impl $crate::internal::router::PerseusRoute for $name { +// fn get_verdict(&self) -> &$crate::internal::router::RouteVerdict { +// &self.0 +// } +// } +// impl ::sycamore_router::Route for $name { +// fn match_route(path: &[&str]) -> Self { +// let verdict = $crate::internal::router::match_route(path, $render_cfg, $templates, $locales); +// Self(verdict) +// } +// } +// }; +// } -/// Creates an app-specific routing `struct`. Sycamore expects an `enum` to do this, so we create a `struct` that behaves similarly. If -/// we don't do this, we can't get the information necessary for routing into the `enum` at all (context and global variables don't suit -/// this particular case). -#[macro_export] -macro_rules! create_app_route { - { - name => $name:ident, - render_cfg => $render_cfg:expr, - templates => $templates:expr, - locales => $locales:expr - } => { - /// The route type for the app, with all routing logic inbuilt through the generation macro. - #[derive(::std::clone::Clone)] - struct $name($crate::internal::router::RouteVerdict); - impl $crate::internal::router::PerseusRoute for $name { - fn get_verdict(&self) -> &$crate::internal::router::RouteVerdict { - &self.0 - } - } - impl ::sycamore_router::Route for $name { - fn match_route(path: &[&str]) -> Self { - let verdict = $crate::internal::router::match_route(path, $render_cfg, $templates, $locales); - Self(verdict) - } +/// The Perseus route system, which implements Sycamore `Route`, but adds additional data for Perseus' processing system. +pub struct PerseusRoute { + verdict: RouteVerdict, + render_cfg: HashMap, + templates: TemplateMap, + locales: Locales, +} +// Sycamore would only use this if we were processing dynamic routes, which we're not +// In other words, it's fine that these values would break everything if they were used, they're just compiler appeasement +impl Default for PerseusRoute { + fn default() -> Self { + Self { + verdict: RouteVerdict::NotFound, + render_cfg: HashMap::default(), + templates: TemplateMap::default(), + locales: Locales { + default: String::default(), + other: Vec::default(), + using_i18n: bool::default(), + }, } - }; + } } - -/// A trait for the routes in Perseus apps. This should be used almost exclusively internally, and you should never need to touch -/// it unless you're building a custom engine. -pub trait PerseusRoute: Route + Clone { - /// Gets the route verdict for the current route. - fn get_verdict(&self) -> &RouteVerdict; +impl PerseusRoute { + /// Gets the current route verdict. + pub fn get_verdict(&self) -> &RouteVerdict { + &self.verdict + } +} +impl Route for PerseusRoute { + fn match_route(&self, path: &[&str]) -> Self { + let verdict = match_route(path, &self.render_cfg, &self.templates, &self.locales); + Self { + verdict, + render_cfg: self.render_cfg.clone(), + templates: self.templates.clone(), + locales: self.locales.clone(), + } + } } diff --git a/packages/perseus/src/router/router_component.rs b/packages/perseus/src/router/router_component.rs index f4c74cf19c..bd055525d9 100644 --- a/packages/perseus/src/router/router_component.rs +++ b/packages/perseus/src/router/router_component.rs @@ -141,7 +141,7 @@ pub struct PerseusRouterProps { /// Note: this deliberately has a snake case name, and should be called directly with `cx` as the first argument, allowing the `AppRoute` generic /// creates with `create_app_root!` to be provided easily. That given `cx` property will be used for all context registration in the app. #[component] -pub fn perseus_router + 'static>( +pub fn perseus_router( cx: Scope, PerseusRouterProps { error_pages, @@ -265,7 +265,7 @@ pub fn perseus_router + 'stati view! { cx, Router { integration: HistoryIntegration::new(), - view: move |cx, route: &ReadSignal| { + view: move |cx, route: &ReadSignal>| { // Sycamore's reactivity is broken by a future, so we need to explicitly add the route to the reactive dependencies here // We do need the future though (otherwise `container_rx` doesn't link to anything until it's too late) create_effect(cx, move || { diff --git a/packages/perseus/src/stores/immutable.rs b/packages/perseus/src/stores/immutable.rs index 23933ff055..83cc969c66 100644 --- a/packages/perseus/src/stores/immutable.rs +++ b/packages/perseus/src/stores/immutable.rs @@ -16,7 +16,10 @@ pub struct ImmutableStore { impl ImmutableStore { /// Creates a new immutable store. You should provide a path like `dist` here. Note that any trailing slashes will be automatically stripped. pub fn new(root_path: String) -> Self { - let root_path = root_path.strip_prefix('/').unwrap_or(&root_path).to_string(); + let root_path = root_path + .strip_prefix('/') + .unwrap_or(&root_path) + .to_string(); Self { root_path } } /// Gets the filesystem path used for this immutable store. From 814078bc59718252ab78a36adb1911b9f6161078 Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Sun, 12 Jun 2022 15:21:23 +1000 Subject: [PATCH 04/37] feat: updated the cli and brought everything together This is still untested because of a Cargo dependency unification issue. --- Cargo.toml | 6 +- .../{.perseus => .perseus.old}/.gitignore | 0 .../{.perseus => .perseus.old}/Cargo.toml | 0 .../builder/Cargo.toml | 0 .../builder/src/bin/build.rs | 0 .../builder/src/bin/export.rs | 0 .../builder/src/bin/export_error_page.rs | 0 .../builder/src/bin/tinker.rs | 0 .../server/Cargo.toml | 0 .../server/src/main.rs | 0 .../{.perseus => .perseus.old}/src/lib.rs | 0 examples/core/basic/Cargo.toml | 14 +- examples/core/basic/src/lib.rs | 15 +- examples/core/basic/src/main.rs | 12 + examples/core/basic/tree.txt | 189 +++++++++ examples/core/basic/tree1.txt | 364 ++++++++++++++++++ packages/perseus-cli/Cargo.toml | 3 - packages/perseus-cli/build.rs | 105 ----- packages/perseus-cli/src/bin/main.rs | 55 +-- packages/perseus-cli/src/build.rs | 53 ++- packages/perseus-cli/src/cmd.rs | 9 +- packages/perseus-cli/src/deploy.rs | 25 +- packages/perseus-cli/src/eject.rs | 52 --- packages/perseus-cli/src/errors.rs | 83 +--- packages/perseus-cli/src/export.rs | 18 +- packages/perseus-cli/src/export_error_page.rs | 5 +- packages/perseus-cli/src/extraction.rs | 30 -- packages/perseus-cli/src/lib.rs | 25 +- packages/perseus-cli/src/parse.rs | 64 +-- packages/perseus-cli/src/prepare.rs | 220 +---------- packages/perseus-cli/src/serve.rs | 41 +- packages/perseus-cli/src/snoop.rs | 18 +- packages/perseus-cli/src/tinker.rs | 9 +- packages/perseus-macro/src/autoserde.rs | 20 + packages/perseus-macro/src/head.rs | 8 + packages/perseus-macro/src/template_rx.rs | 14 +- packages/perseus/Cargo.toml | 35 +- packages/perseus/src/error_pages.rs | 10 +- packages/perseus/src/errors.rs | 12 +- packages/perseus/src/export.rs | 4 +- packages/perseus/src/i18n/mod.rs | 4 + .../perseus/src/i18n/translations_manager.rs | 35 +- packages/perseus/src/init.rs | 168 ++++++-- packages/perseus/src/lib.rs | 33 +- .../perseus/src/{server => }/page_data.rs | 0 packages/perseus/src/plugins/functional.rs | 20 +- packages/perseus/src/router/mod.rs | 2 + packages/perseus/src/server/html_shell.rs | 2 +- packages/perseus/src/server/mod.rs | 2 - packages/perseus/src/server/render.rs | 2 +- packages/perseus/src/shell.rs | 2 +- packages/perseus/src/state/mod.rs | 14 +- packages/perseus/src/stores/immutable.rs | 8 + packages/perseus/src/stores/mutable.rs | 14 + packages/perseus/src/template/core.rs | 221 +++++++---- packages/perseus/src/template/mod.rs | 4 + packages/perseus/src/utils/mod.rs | 6 +- packages/perseus/src/utils/path_prefix.rs | 11 +- 58 files changed, 1150 insertions(+), 916 deletions(-) rename examples/core/basic/{.perseus => .perseus.old}/.gitignore (100%) rename examples/core/basic/{.perseus => .perseus.old}/Cargo.toml (100%) rename examples/core/basic/{.perseus => .perseus.old}/builder/Cargo.toml (100%) rename examples/core/basic/{.perseus => .perseus.old}/builder/src/bin/build.rs (100%) rename examples/core/basic/{.perseus => .perseus.old}/builder/src/bin/export.rs (100%) rename examples/core/basic/{.perseus => .perseus.old}/builder/src/bin/export_error_page.rs (100%) rename examples/core/basic/{.perseus => .perseus.old}/builder/src/bin/tinker.rs (100%) rename examples/core/basic/{.perseus => .perseus.old}/server/Cargo.toml (100%) rename examples/core/basic/{.perseus => .perseus.old}/server/src/main.rs (100%) rename examples/core/basic/{.perseus => .perseus.old}/src/lib.rs (100%) create mode 100644 examples/core/basic/src/main.rs create mode 100644 examples/core/basic/tree.txt create mode 100644 examples/core/basic/tree1.txt delete mode 100644 packages/perseus-cli/build.rs delete mode 100644 packages/perseus-cli/src/eject.rs delete mode 100644 packages/perseus-cli/src/extraction.rs rename packages/perseus/src/{server => }/page_data.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 92bf5b5366..cab401af78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,9 +7,9 @@ members = [ "website", # We have the CLI subcrates as workspace members so we can actively develop on them # They also can't be a workspace until nested workspaces are supported - "examples/core/basic/.perseus", - "examples/core/basic/.perseus/server", - "examples/core/basic/.perseus/builder" + # "examples/core/basic/.perseus", + # "examples/core/basic/.perseus/server", + # "examples/core/basic/.perseus/builder" ] [patch.crates-io] diff --git a/examples/core/basic/.perseus/.gitignore b/examples/core/basic/.perseus.old/.gitignore similarity index 100% rename from examples/core/basic/.perseus/.gitignore rename to examples/core/basic/.perseus.old/.gitignore diff --git a/examples/core/basic/.perseus/Cargo.toml b/examples/core/basic/.perseus.old/Cargo.toml similarity index 100% rename from examples/core/basic/.perseus/Cargo.toml rename to examples/core/basic/.perseus.old/Cargo.toml diff --git a/examples/core/basic/.perseus/builder/Cargo.toml b/examples/core/basic/.perseus.old/builder/Cargo.toml similarity index 100% rename from examples/core/basic/.perseus/builder/Cargo.toml rename to examples/core/basic/.perseus.old/builder/Cargo.toml diff --git a/examples/core/basic/.perseus/builder/src/bin/build.rs b/examples/core/basic/.perseus.old/builder/src/bin/build.rs similarity index 100% rename from examples/core/basic/.perseus/builder/src/bin/build.rs rename to examples/core/basic/.perseus.old/builder/src/bin/build.rs diff --git a/examples/core/basic/.perseus/builder/src/bin/export.rs b/examples/core/basic/.perseus.old/builder/src/bin/export.rs similarity index 100% rename from examples/core/basic/.perseus/builder/src/bin/export.rs rename to examples/core/basic/.perseus.old/builder/src/bin/export.rs diff --git a/examples/core/basic/.perseus/builder/src/bin/export_error_page.rs b/examples/core/basic/.perseus.old/builder/src/bin/export_error_page.rs similarity index 100% rename from examples/core/basic/.perseus/builder/src/bin/export_error_page.rs rename to examples/core/basic/.perseus.old/builder/src/bin/export_error_page.rs diff --git a/examples/core/basic/.perseus/builder/src/bin/tinker.rs b/examples/core/basic/.perseus.old/builder/src/bin/tinker.rs similarity index 100% rename from examples/core/basic/.perseus/builder/src/bin/tinker.rs rename to examples/core/basic/.perseus.old/builder/src/bin/tinker.rs diff --git a/examples/core/basic/.perseus/server/Cargo.toml b/examples/core/basic/.perseus.old/server/Cargo.toml similarity index 100% rename from examples/core/basic/.perseus/server/Cargo.toml rename to examples/core/basic/.perseus.old/server/Cargo.toml diff --git a/examples/core/basic/.perseus/server/src/main.rs b/examples/core/basic/.perseus.old/server/src/main.rs similarity index 100% rename from examples/core/basic/.perseus/server/src/main.rs rename to examples/core/basic/.perseus.old/server/src/main.rs diff --git a/examples/core/basic/.perseus/src/lib.rs b/examples/core/basic/.perseus.old/src/lib.rs similarity index 100% rename from examples/core/basic/.perseus/src/lib.rs rename to examples/core/basic/.perseus.old/src/lib.rs diff --git a/examples/core/basic/Cargo.toml b/examples/core/basic/Cargo.toml index 812e0cc8d0..305d4bb42b 100644 --- a/examples/core/basic/Cargo.toml +++ b/examples/core/basic/Cargo.toml @@ -6,23 +6,27 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -perseus = { path = "../../../packages/perseus", features = [ "hydrate", "builder", "dflt-engine" ] } -perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } +# perseus = { path = "../../../packages/perseus", features = [ "hydrate", "builder", "dflt-engine" ] } sycamore = "=0.8.0-beta.6" serde = { version = "1", features = ["derive"] } serde_json = "1" -[dev-dependencies] -fantoccini = "0.17" +[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] +# fantoccini = "0.17" # New Perseus metadata begins here [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } +perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = "0.2" [lib] name = "lib" path = "src/lib.rs" +crate-type = [ "cdylib", "rlib" ] [[bin]] name = "perseus-example-basic" -path = "src/lib.rs" +path = "src/main.rs" diff --git a/examples/core/basic/src/lib.rs b/examples/core/basic/src/lib.rs index 236905930a..2f7240d521 100644 --- a/examples/core/basic/src/lib.rs +++ b/examples/core/basic/src/lib.rs @@ -10,20 +10,9 @@ pub fn get_app() -> PerseusApp { .error_pages(crate::error_pages::get_error_pages) } -#[cfg(not(target_arch = "wasm32"))] -#[allow(dead_code)] -#[tokio::main] -async fn main() { - use perseus::builder::{get_op, run_dflt_engine}; - - let op = get_op().unwrap(); - let exit_code = run_dflt_engine(op, get_app(), perseus_warp::dflt_server).await; - std::process::exit(exit_code); -} - #[cfg(target_arch = "wasm32")] -#[wasm_bindgen] -fn main() -> perseus::ClientReturn { +#[wasm_bindgen::prelude::wasm_bindgen] +pub fn main() -> perseus::ClientReturn { use perseus::run_client; run_client(get_app()) diff --git a/examples/core/basic/src/main.rs b/examples/core/basic/src/main.rs new file mode 100644 index 0000000000..ec4bbda0db --- /dev/null +++ b/examples/core/basic/src/main.rs @@ -0,0 +1,12 @@ +#[cfg(not(target_arch = "wasm32"))] +#[tokio::main] +async fn main() { + use perseus::builder::{get_op, run_dflt_engine}; + + let op = get_op().unwrap(); + let exit_code = run_dflt_engine(op, lib::get_app(), perseus_warp::dflt_server).await; + std::process::exit(exit_code); +} + +#[cfg(target_arch = "wasm32")] +fn main() {} diff --git a/examples/core/basic/tree.txt b/examples/core/basic/tree.txt new file mode 100644 index 0000000000..a3b329d250 --- /dev/null +++ b/examples/core/basic/tree.txt @@ -0,0 +1,189 @@ +perseus-example-basic v0.4.0-beta.1 (/home/arctic_hen7/Coding/Projects/perseus/examples/core/basic) +├── perseus v0.4.0-beta.1 (/home/arctic_hen7/Coding/Projects/perseus/packages/perseus) +│ ├── async-trait v0.1.52 (proc-macro) +│ │ ├── proc-macro2 v1.0.36 +│ │ │ └── unicode-xid v0.2.2 +│ │ ├── quote v1.0.15 +│ │ │ └── proc-macro2 v1.0.36 (*) +│ │ └── syn v1.0.86 +│ │ ├── proc-macro2 v1.0.36 (*) +│ │ ├── quote v1.0.15 (*) +│ │ └── unicode-xid v0.2.2 +│ ├── chrono v0.4.19 +│ │ ├── libc v0.2.117 +│ │ ├── num-integer v0.1.44 +│ │ │ └── num-traits v0.2.14 +│ │ │ [build-dependencies] +│ │ │ └── autocfg v1.1.0 +│ │ │ [build-dependencies] +│ │ │ └── autocfg v1.1.0 +│ │ ├── num-traits v0.2.14 (*) +│ │ └── time v0.1.43 +│ │ └── libc v0.2.117 +│ ├── fmterr v0.1.1 +│ ├── fs_extra v1.2.0 +│ ├── futures v0.3.21 +│ │ ├── futures-channel v0.3.21 +│ │ │ ├── futures-core v0.3.21 +│ │ │ └── futures-sink v0.3.21 +│ │ ├── futures-core v0.3.21 +│ │ ├── futures-executor v0.3.21 +│ │ │ ├── futures-core v0.3.21 +│ │ │ ├── futures-task v0.3.21 +│ │ │ └── futures-util v0.3.21 +│ │ │ ├── futures-channel v0.3.21 (*) +│ │ │ ├── futures-core v0.3.21 +│ │ │ ├── futures-io v0.3.21 +│ │ │ ├── futures-macro v0.3.21 (proc-macro) +│ │ │ │ ├── proc-macro2 v1.0.36 (*) +│ │ │ │ ├── quote v1.0.15 (*) +│ │ │ │ └── syn v1.0.86 (*) +│ │ │ ├── futures-sink v0.3.21 +│ │ │ ├── futures-task v0.3.21 +│ │ │ ├── memchr v2.4.1 +│ │ │ ├── pin-project-lite v0.2.8 +│ │ │ ├── pin-utils v0.1.0 +│ │ │ └── slab v0.4.5 +│ │ ├── futures-io v0.3.21 +│ │ ├── futures-sink v0.3.21 +│ │ ├── futures-task v0.3.21 +│ │ └── futures-util v0.3.21 (*) +│ ├── http v0.2.6 +│ │ ├── bytes v1.1.0 +│ │ ├── fnv v1.0.7 +│ │ └── itoa v1.0.1 +│ ├── perseus-macro v0.4.0-beta.1 (proc-macro) (/home/arctic_hen7/Coding/Projects/perseus/packages/perseus-macro) +│ │ ├── darling v0.13.1 +│ │ │ ├── darling_core v0.13.1 +│ │ │ │ ├── fnv v1.0.7 +│ │ │ │ ├── ident_case v1.0.1 +│ │ │ │ ├── proc-macro2 v1.0.36 (*) +│ │ │ │ ├── quote v1.0.15 (*) +│ │ │ │ ├── strsim v0.10.0 +│ │ │ │ └── syn v1.0.86 (*) +│ │ │ └── darling_macro v0.13.1 (proc-macro) +│ │ │ ├── darling_core v0.13.1 (*) +│ │ │ ├── quote v1.0.15 (*) +│ │ │ └── syn v1.0.86 (*) +│ │ ├── proc-macro2 v1.0.36 (*) +│ │ ├── quote v1.0.15 (*) +│ │ ├── regex v1.5.4 +│ │ │ ├── aho-corasick v0.7.18 +│ │ │ │ └── memchr v2.4.1 +│ │ │ ├── memchr v2.4.1 +│ │ │ └── regex-syntax v0.6.25 +│ │ ├── serde_json v1.0.79 +│ │ │ ├── itoa v1.0.1 +│ │ │ ├── ryu v1.0.9 +│ │ │ └── serde v1.0.136 +│ │ │ └── serde_derive v1.0.136 (proc-macro) +│ │ │ ├── proc-macro2 v1.0.36 (*) +│ │ │ ├── quote v1.0.15 (*) +│ │ │ └── syn v1.0.86 (*) +│ │ ├── sycamore-reactive v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) +│ │ │ ├── ahash v0.7.6 +│ │ │ │ ├── getrandom v0.2.4 +│ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ └── libc v0.2.117 +│ │ │ │ └── once_cell v1.10.0 +│ │ │ │ [build-dependencies] +│ │ │ │ └── version_check v0.9.4 +│ │ │ ├── bumpalo v3.9.1 +│ │ │ ├── indexmap v1.8.1 +│ │ │ │ └── hashbrown v0.11.2 +│ │ │ │ [build-dependencies] +│ │ │ │ └── autocfg v1.1.0 +│ │ │ ├── slotmap v1.0.6 +│ │ │ │ [build-dependencies] +│ │ │ │ └── version_check v0.9.4 +│ │ │ └── smallvec v1.8.0 +│ │ └── syn v1.0.86 (*) +│ ├── regex v1.5.4 (*) +│ ├── serde v1.0.136 (*) +│ ├── serde_json v1.0.79 (*) +│ ├── sycamore v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) +│ │ ├── ahash v0.7.6 (*) +│ │ ├── html-escape v0.2.11 +│ │ │ └── utf8-width v0.1.5 +│ │ ├── indexmap v1.8.1 (*) +│ │ ├── js-sys v0.3.57 +│ │ │ └── wasm-bindgen v0.2.80 +│ │ │ ├── cfg-if v1.0.0 +│ │ │ └── wasm-bindgen-macro v0.2.80 (proc-macro) +│ │ │ ├── quote v1.0.15 (*) +│ │ │ └── wasm-bindgen-macro-support v0.2.80 +│ │ │ ├── proc-macro2 v1.0.36 (*) +│ │ │ ├── quote v1.0.15 (*) +│ │ │ ├── syn v1.0.86 (*) +│ │ │ ├── wasm-bindgen-backend v0.2.80 +│ │ │ │ ├── bumpalo v3.9.1 +│ │ │ │ ├── lazy_static v1.4.0 +│ │ │ │ ├── log v0.4.14 +│ │ │ │ │ └── cfg-if v1.0.0 +│ │ │ │ ├── proc-macro2 v1.0.36 (*) +│ │ │ │ ├── quote v1.0.15 (*) +│ │ │ │ ├── syn v1.0.86 (*) +│ │ │ │ └── wasm-bindgen-shared v0.2.80 +│ │ │ └── wasm-bindgen-shared v0.2.80 +│ │ ├── once_cell v1.10.0 +│ │ ├── paste v1.0.6 (proc-macro) +│ │ ├── sycamore-core v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) +│ │ │ ├── ahash v0.7.6 (*) +│ │ │ └── sycamore-reactive v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) (*) +│ │ ├── sycamore-macro v0.8.0-beta.6 (proc-macro) (https://github.com/arctic-hen7/sycamore#2e9d62ae) +│ │ │ ├── once_cell v1.10.0 +│ │ │ ├── proc-macro2 v1.0.36 (*) +│ │ │ ├── quote v1.0.15 (*) +│ │ │ └── syn v1.0.86 (*) +│ │ ├── sycamore-reactive v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) (*) +│ │ ├── sycamore-web v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) +│ │ │ ├── html-escape v0.2.11 (*) +│ │ │ ├── indexmap v1.8.1 (*) +│ │ │ ├── js-sys v0.3.57 (*) +│ │ │ ├── once_cell v1.10.0 +│ │ │ ├── sycamore-core v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) (*) +│ │ │ ├── sycamore-reactive v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) (*) +│ │ │ ├── wasm-bindgen v0.2.80 (*) +│ │ │ └── web-sys v0.3.57 +│ │ │ ├── js-sys v0.3.57 (*) +│ │ │ └── wasm-bindgen v0.2.80 (*) +│ │ ├── wasm-bindgen v0.2.80 (*) +│ │ └── web-sys v0.3.57 (*) +│ ├── sycamore-futures v0.8.0-beta.6 +│ │ ├── futures v0.3.21 (*) +│ │ ├── sycamore-reactive v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) (*) +│ │ └── tokio v1.17.0 +│ │ ├── bytes v1.1.0 +│ │ ├── memchr v2.4.1 +│ │ ├── num_cpus v1.13.1 +│ │ │ └── libc v0.2.117 +│ │ ├── pin-project-lite v0.2.8 +│ │ └── tokio-macros v1.7.0 (proc-macro) +│ │ ├── proc-macro2 v1.0.36 (*) +│ │ ├── quote v1.0.15 (*) +│ │ └── syn v1.0.86 (*) +│ ├── sycamore-router v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) +│ │ ├── sycamore v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) (*) +│ │ ├── sycamore-router-macro v0.8.0-beta.6 (proc-macro) (https://github.com/arctic-hen7/sycamore#2e9d62ae) +│ │ │ ├── nom v7.1.0 +│ │ │ │ ├── memchr v2.4.1 +│ │ │ │ └── minimal-lexical v0.2.1 +│ │ │ │ [build-dependencies] +│ │ │ │ └── version_check v0.9.4 +│ │ │ ├── proc-macro2 v1.0.36 (*) +│ │ │ ├── quote v1.0.15 (*) +│ │ │ ├── syn v1.0.86 (*) +│ │ │ └── unicode-xid v0.2.2 +│ │ ├── wasm-bindgen v0.2.80 (*) +│ │ └── web-sys v0.3.57 (*) +│ ├── thiserror v1.0.30 +│ │ └── thiserror-impl v1.0.30 (proc-macro) +│ │ ├── proc-macro2 v1.0.36 (*) +│ │ ├── quote v1.0.15 (*) +│ │ └── syn v1.0.86 (*) +│ ├── tokio v1.17.0 (*) +│ └── urlencoding v2.1.0 +├── serde v1.0.136 (*) +├── serde_json v1.0.79 (*) +├── sycamore v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) (*) +└── tokio v1.17.0 (*) diff --git a/examples/core/basic/tree1.txt b/examples/core/basic/tree1.txt new file mode 100644 index 0000000000..d6591b1414 --- /dev/null +++ b/examples/core/basic/tree1.txt @@ -0,0 +1,364 @@ +perseus-example-basic v0.4.0-beta.1 (/home/arctic_hen7/Coding/Projects/perseus/examples/core/basic) +├── perseus v0.4.0-beta.1 (/home/arctic_hen7/Coding/Projects/perseus/packages/perseus) +│ ├── async-trait v0.1.52 (proc-macro) +│ │ ├── proc-macro2 v1.0.36 +│ │ │ └── unicode-xid v0.2.2 +│ │ ├── quote v1.0.15 +│ │ │ └── proc-macro2 v1.0.36 (*) +│ │ └── syn v1.0.86 +│ │ ├── proc-macro2 v1.0.36 (*) +│ │ ├── quote v1.0.15 (*) +│ │ └── unicode-xid v0.2.2 +│ ├── chrono v0.4.19 +│ │ ├── libc v0.2.117 +│ │ ├── num-integer v0.1.44 +│ │ │ └── num-traits v0.2.14 +│ │ │ [build-dependencies] +│ │ │ └── autocfg v1.1.0 +│ │ │ [build-dependencies] +│ │ │ └── autocfg v1.1.0 +│ │ ├── num-traits v0.2.14 (*) +│ │ └── time v0.1.43 +│ │ └── libc v0.2.117 +│ ├── fmterr v0.1.1 +│ ├── fs_extra v1.2.0 +│ ├── futures v0.3.21 +│ │ ├── futures-channel v0.3.21 +│ │ │ ├── futures-core v0.3.21 +│ │ │ └── futures-sink v0.3.21 +│ │ ├── futures-core v0.3.21 +│ │ ├── futures-executor v0.3.21 +│ │ │ ├── futures-core v0.3.21 +│ │ │ ├── futures-task v0.3.21 +│ │ │ └── futures-util v0.3.21 +│ │ │ ├── futures-channel v0.3.21 (*) +│ │ │ ├── futures-core v0.3.21 +│ │ │ ├── futures-io v0.3.21 +│ │ │ ├── futures-macro v0.3.21 (proc-macro) +│ │ │ │ ├── proc-macro2 v1.0.36 (*) +│ │ │ │ ├── quote v1.0.15 (*) +│ │ │ │ └── syn v1.0.86 (*) +│ │ │ ├── futures-sink v0.3.21 +│ │ │ ├── futures-task v0.3.21 +│ │ │ ├── memchr v2.4.1 +│ │ │ ├── pin-project-lite v0.2.8 +│ │ │ ├── pin-utils v0.1.0 +│ │ │ └── slab v0.4.5 +│ │ ├── futures-io v0.3.21 +│ │ ├── futures-sink v0.3.21 +│ │ ├── futures-task v0.3.21 +│ │ └── futures-util v0.3.21 (*) +│ ├── http v0.2.6 +│ │ ├── bytes v1.1.0 +│ │ ├── fnv v1.0.7 +│ │ └── itoa v1.0.1 +│ ├── perseus-macro v0.4.0-beta.1 (proc-macro) (/home/arctic_hen7/Coding/Projects/perseus/packages/perseus-macro) +│ │ ├── darling v0.13.1 +│ │ │ ├── darling_core v0.13.1 +│ │ │ │ ├── fnv v1.0.7 +│ │ │ │ ├── ident_case v1.0.1 +│ │ │ │ ├── proc-macro2 v1.0.36 (*) +│ │ │ │ ├── quote v1.0.15 (*) +│ │ │ │ ├── strsim v0.10.0 +│ │ │ │ └── syn v1.0.86 (*) +│ │ │ └── darling_macro v0.13.1 (proc-macro) +│ │ │ ├── darling_core v0.13.1 (*) +│ │ │ ├── quote v1.0.15 (*) +│ │ │ └── syn v1.0.86 (*) +│ │ ├── proc-macro2 v1.0.36 (*) +│ │ ├── quote v1.0.15 (*) +│ │ ├── regex v1.5.4 +│ │ │ ├── aho-corasick v0.7.18 +│ │ │ │ └── memchr v2.4.1 +│ │ │ ├── memchr v2.4.1 +│ │ │ └── regex-syntax v0.6.25 +│ │ ├── serde_json v1.0.79 +│ │ │ ├── itoa v1.0.1 +│ │ │ ├── ryu v1.0.9 +│ │ │ └── serde v1.0.136 +│ │ │ └── serde_derive v1.0.136 (proc-macro) +│ │ │ ├── proc-macro2 v1.0.36 (*) +│ │ │ ├── quote v1.0.15 (*) +│ │ │ └── syn v1.0.86 (*) +│ │ ├── sycamore-reactive v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) +│ │ │ ├── ahash v0.7.6 +│ │ │ │ ├── getrandom v0.2.4 +│ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ └── libc v0.2.117 +│ │ │ │ └── once_cell v1.10.0 +│ │ │ │ [build-dependencies] +│ │ │ │ └── version_check v0.9.4 +│ │ │ ├── bumpalo v3.9.1 +│ │ │ ├── indexmap v1.8.1 +│ │ │ │ └── hashbrown v0.11.2 +│ │ │ │ [build-dependencies] +│ │ │ │ └── autocfg v1.1.0 +│ │ │ ├── slotmap v1.0.6 +│ │ │ │ [build-dependencies] +│ │ │ │ └── version_check v0.9.4 +│ │ │ └── smallvec v1.8.0 +│ │ └── syn v1.0.86 (*) +│ ├── regex v1.5.4 (*) +│ ├── serde v1.0.136 (*) +│ ├── serde_json v1.0.79 (*) +│ ├── sycamore v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) +│ │ ├── ahash v0.7.6 (*) +│ │ ├── html-escape v0.2.11 +│ │ │ └── utf8-width v0.1.5 +│ │ ├── indexmap v1.8.1 (*) +│ │ ├── js-sys v0.3.57 +│ │ │ └── wasm-bindgen v0.2.80 +│ │ │ ├── cfg-if v1.0.0 +│ │ │ └── wasm-bindgen-macro v0.2.80 (proc-macro) +│ │ │ ├── quote v1.0.15 (*) +│ │ │ └── wasm-bindgen-macro-support v0.2.80 +│ │ │ ├── proc-macro2 v1.0.36 (*) +│ │ │ ├── quote v1.0.15 (*) +│ │ │ ├── syn v1.0.86 (*) +│ │ │ ├── wasm-bindgen-backend v0.2.80 +│ │ │ │ ├── bumpalo v3.9.1 +│ │ │ │ ├── lazy_static v1.4.0 +│ │ │ │ ├── log v0.4.14 +│ │ │ │ │ └── cfg-if v1.0.0 +│ │ │ │ ├── proc-macro2 v1.0.36 (*) +│ │ │ │ ├── quote v1.0.15 (*) +│ │ │ │ ├── syn v1.0.86 (*) +│ │ │ │ └── wasm-bindgen-shared v0.2.80 +│ │ │ └── wasm-bindgen-shared v0.2.80 +│ │ ├── once_cell v1.10.0 +│ │ ├── paste v1.0.6 (proc-macro) +│ │ ├── sycamore-core v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) +│ │ │ ├── ahash v0.7.6 (*) +│ │ │ └── sycamore-reactive v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) (*) +│ │ ├── sycamore-macro v0.8.0-beta.6 (proc-macro) (https://github.com/arctic-hen7/sycamore#2e9d62ae) +│ │ │ ├── once_cell v1.10.0 +│ │ │ ├── proc-macro2 v1.0.36 (*) +│ │ │ ├── quote v1.0.15 (*) +│ │ │ └── syn v1.0.86 (*) +│ │ ├── sycamore-reactive v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) (*) +│ │ ├── sycamore-web v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) +│ │ │ ├── html-escape v0.2.11 (*) +│ │ │ ├── indexmap v1.8.1 (*) +│ │ │ ├── js-sys v0.3.57 (*) +│ │ │ ├── once_cell v1.10.0 +│ │ │ ├── sycamore-core v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) (*) +│ │ │ ├── sycamore-reactive v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) (*) +│ │ │ ├── wasm-bindgen v0.2.80 (*) +│ │ │ └── web-sys v0.3.57 +│ │ │ ├── js-sys v0.3.57 (*) +│ │ │ └── wasm-bindgen v0.2.80 (*) +│ │ ├── wasm-bindgen v0.2.80 (*) +│ │ └── web-sys v0.3.57 (*) +│ ├── sycamore-futures v0.8.0-beta.6 +│ │ ├── futures v0.3.21 (*) +│ │ ├── sycamore-reactive v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) (*) +│ │ └── tokio v1.17.0 +│ │ ├── bytes v1.1.0 +│ │ ├── libc v0.2.117 +│ │ ├── memchr v2.4.1 +│ │ ├── mio v0.8.0 +│ │ │ ├── libc v0.2.117 +│ │ │ └── log v0.4.14 (*) +│ │ ├── num_cpus v1.13.1 +│ │ │ └── libc v0.2.117 +│ │ ├── pin-project-lite v0.2.8 +│ │ ├── socket2 v0.4.4 +│ │ │ └── libc v0.2.117 +│ │ └── tokio-macros v1.7.0 (proc-macro) +│ │ ├── proc-macro2 v1.0.36 (*) +│ │ ├── quote v1.0.15 (*) +│ │ └── syn v1.0.86 (*) +│ ├── sycamore-router v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) +│ │ ├── sycamore v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) (*) +│ │ ├── sycamore-router-macro v0.8.0-beta.6 (proc-macro) (https://github.com/arctic-hen7/sycamore#2e9d62ae) +│ │ │ ├── nom v7.1.0 +│ │ │ │ ├── memchr v2.4.1 +│ │ │ │ └── minimal-lexical v0.2.1 +│ │ │ │ [build-dependencies] +│ │ │ │ └── version_check v0.9.4 +│ │ │ ├── proc-macro2 v1.0.36 (*) +│ │ │ ├── quote v1.0.15 (*) +│ │ │ ├── syn v1.0.86 (*) +│ │ │ └── unicode-xid v0.2.2 +│ │ ├── wasm-bindgen v0.2.80 (*) +│ │ └── web-sys v0.3.57 (*) +│ ├── thiserror v1.0.30 +│ │ └── thiserror-impl v1.0.30 (proc-macro) +│ │ ├── proc-macro2 v1.0.36 (*) +│ │ ├── quote v1.0.15 (*) +│ │ └── syn v1.0.86 (*) +│ ├── tokio v1.17.0 (*) +│ └── urlencoding v2.1.0 +├── perseus-warp v0.4.0-beta.1 (/home/arctic_hen7/Coding/Projects/perseus/packages/perseus-warp) +│ ├── fmterr v0.1.1 +│ ├── futures v0.3.21 (*) +│ ├── perseus v0.4.0-beta.1 (/home/arctic_hen7/Coding/Projects/perseus/packages/perseus) (*) +│ ├── serde v1.0.136 (*) +│ ├── serde_json v1.0.79 (*) +│ ├── sycamore v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) (*) +│ ├── thiserror v1.0.30 (*) +│ ├── tokio v1.17.0 (*) +│ ├── urlencoding v2.1.0 +│ └── warp-fix-171 v0.3.2 +│ ├── bytes v1.1.0 +│ ├── futures-channel v0.3.21 (*) +│ ├── futures-util v0.3.21 (*) +│ ├── headers v0.3.7 +│ │ ├── base64 v0.13.0 +│ │ ├── bitflags v1.3.2 +│ │ ├── bytes v1.1.0 +│ │ ├── headers-core v0.2.0 +│ │ │ └── http v0.2.6 (*) +│ │ ├── http v0.2.6 (*) +│ │ ├── httpdate v1.0.2 +│ │ ├── mime v0.3.16 +│ │ └── sha-1 v0.10.0 +│ │ ├── cfg-if v1.0.0 +│ │ ├── cpufeatures v0.2.1 +│ │ └── digest v0.10.2 +│ │ ├── block-buffer v0.10.2 +│ │ │ └── generic-array v0.14.5 +│ │ │ └── typenum v1.15.0 +│ │ │ [build-dependencies] +│ │ │ └── version_check v0.9.4 +│ │ └── crypto-common v0.1.2 +│ │ └── generic-array v0.14.5 (*) +│ ├── http v0.2.6 (*) +│ ├── hyper v0.14.17 +│ │ ├── bytes v1.1.0 +│ │ ├── futures-channel v0.3.21 (*) +│ │ ├── futures-core v0.3.21 +│ │ ├── futures-util v0.3.21 (*) +│ │ ├── h2 v0.3.11 +│ │ │ ├── bytes v1.1.0 +│ │ │ ├── fnv v1.0.7 +│ │ │ ├── futures-core v0.3.21 +│ │ │ ├── futures-sink v0.3.21 +│ │ │ ├── futures-util v0.3.21 (*) +│ │ │ ├── http v0.2.6 (*) +│ │ │ ├── indexmap v1.8.1 (*) +│ │ │ ├── slab v0.4.5 +│ │ │ ├── tokio v1.17.0 (*) +│ │ │ ├── tokio-util v0.6.9 +│ │ │ │ ├── bytes v1.1.0 +│ │ │ │ ├── futures-core v0.3.21 +│ │ │ │ ├── futures-sink v0.3.21 +│ │ │ │ ├── log v0.4.14 (*) +│ │ │ │ ├── pin-project-lite v0.2.8 +│ │ │ │ └── tokio v1.17.0 (*) +│ │ │ └── tracing v0.1.30 +│ │ │ ├── cfg-if v1.0.0 +│ │ │ ├── log v0.4.14 (*) +│ │ │ ├── pin-project-lite v0.2.8 +│ │ │ └── tracing-core v0.1.22 +│ │ │ └── lazy_static v1.4.0 +│ │ ├── http v0.2.6 (*) +│ │ ├── http-body v0.4.4 +│ │ │ ├── bytes v1.1.0 +│ │ │ ├── http v0.2.6 (*) +│ │ │ └── pin-project-lite v0.2.8 +│ │ ├── httparse v1.6.0 +│ │ ├── httpdate v1.0.2 +│ │ ├── itoa v1.0.1 +│ │ ├── pin-project-lite v0.2.8 +│ │ ├── socket2 v0.4.4 (*) +│ │ ├── tokio v1.17.0 (*) +│ │ ├── tower-service v0.3.1 +│ │ ├── tracing v0.1.30 (*) +│ │ └── want v0.3.0 +│ │ ├── log v0.4.14 (*) +│ │ └── try-lock v0.2.3 +│ ├── log v0.4.14 (*) +│ ├── mime v0.3.16 +│ ├── mime_guess v2.0.3 +│ │ ├── mime v0.3.16 +│ │ └── unicase v2.6.0 +│ │ [build-dependencies] +│ │ └── version_check v0.9.4 +│ │ [build-dependencies] +│ │ └── unicase v2.6.0 (*) +│ ├── multipart v0.18.0 +│ │ ├── buf_redux v0.8.4 +│ │ │ ├── memchr v2.4.1 +│ │ │ └── safemem v0.3.3 +│ │ ├── httparse v1.6.0 +│ │ ├── log v0.4.14 (*) +│ │ ├── mime v0.3.16 +│ │ ├── mime_guess v2.0.3 (*) +│ │ ├── quick-error v1.2.3 +│ │ ├── rand v0.8.5 +│ │ │ ├── libc v0.2.117 +│ │ │ ├── rand_chacha v0.3.1 +│ │ │ │ ├── ppv-lite86 v0.2.16 +│ │ │ │ └── rand_core v0.6.3 +│ │ │ │ └── getrandom v0.2.4 (*) +│ │ │ └── rand_core v0.6.3 (*) +│ │ ├── safemem v0.3.3 +│ │ ├── tempfile v3.3.0 +│ │ │ ├── cfg-if v1.0.0 +│ │ │ ├── fastrand v1.7.0 +│ │ │ ├── libc v0.2.117 +│ │ │ └── remove_dir_all v0.5.3 +│ │ └── twoway v0.1.8 +│ │ └── memchr v2.4.1 +│ ├── percent-encoding v2.1.0 +│ ├── pin-project v1.0.10 +│ │ └── pin-project-internal v1.0.10 (proc-macro) +│ │ ├── proc-macro2 v1.0.36 (*) +│ │ ├── quote v1.0.15 (*) +│ │ └── syn v1.0.86 (*) +│ ├── scoped-tls v1.0.0 +│ ├── serde v1.0.136 (*) +│ ├── serde_json v1.0.79 (*) +│ ├── serde_urlencoded v0.7.1 +│ │ ├── form_urlencoded v1.0.1 +│ │ │ ├── matches v0.1.9 +│ │ │ └── percent-encoding v2.1.0 +│ │ ├── itoa v1.0.1 +│ │ ├── ryu v1.0.9 +│ │ └── serde v1.0.136 (*) +│ ├── tokio v1.17.0 (*) +│ ├── tokio-stream v0.1.8 +│ │ ├── futures-core v0.3.21 +│ │ ├── pin-project-lite v0.2.8 +│ │ └── tokio v1.17.0 (*) +│ ├── tokio-tungstenite v0.15.0 +│ │ ├── futures-util v0.3.21 (*) +│ │ ├── log v0.4.14 (*) +│ │ ├── pin-project v1.0.10 (*) +│ │ ├── tokio v1.17.0 (*) +│ │ └── tungstenite v0.14.0 +│ │ ├── base64 v0.13.0 +│ │ ├── byteorder v1.4.3 +│ │ ├── bytes v1.1.0 +│ │ ├── http v0.2.6 (*) +│ │ ├── httparse v1.6.0 +│ │ ├── log v0.4.14 (*) +│ │ ├── rand v0.8.5 (*) +│ │ ├── sha-1 v0.9.8 +│ │ │ ├── block-buffer v0.9.0 +│ │ │ │ └── generic-array v0.14.5 (*) +│ │ │ ├── cfg-if v1.0.0 +│ │ │ ├── cpufeatures v0.2.1 +│ │ │ ├── digest v0.9.0 +│ │ │ │ └── generic-array v0.14.5 (*) +│ │ │ └── opaque-debug v0.3.0 +│ │ ├── thiserror v1.0.30 (*) +│ │ ├── url v2.2.2 +│ │ │ ├── form_urlencoded v1.0.1 (*) +│ │ │ ├── idna v0.2.3 +│ │ │ │ ├── matches v0.1.9 +│ │ │ │ ├── unicode-bidi v0.3.7 +│ │ │ │ └── unicode-normalization v0.1.19 +│ │ │ │ └── tinyvec v1.5.1 +│ │ │ │ └── tinyvec_macros v0.1.0 +│ │ │ ├── matches v0.1.9 +│ │ │ └── percent-encoding v2.1.0 +│ │ └── utf-8 v0.7.6 +│ ├── tokio-util v0.6.9 (*) +│ ├── tower-service v0.3.1 +│ └── tracing v0.1.30 (*) +├── serde v1.0.136 (*) +├── serde_json v1.0.79 (*) +├── sycamore v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) (*) +└── tokio v1.17.0 (*) diff --git a/packages/perseus-cli/Cargo.toml b/packages/perseus-cli/Cargo.toml index 2ff9369071..6e261e19b0 100644 --- a/packages/perseus-cli/Cargo.toml +++ b/packages/perseus-cli/Cargo.toml @@ -39,9 +39,6 @@ futures = "0.3" tokio-stream = "0.1" ureq = "2" -[build-dependencies] -fs_extra = "1" - [lib] name = "perseus_cli" diff --git a/packages/perseus-cli/build.rs b/packages/perseus-cli/build.rs deleted file mode 100644 index a8f16b5a76..0000000000 --- a/packages/perseus-cli/build.rs +++ /dev/null @@ -1,105 +0,0 @@ -// This build script copies the `examples/core/basic/.perseus/` directory into `packages/perseus-cli/` for use in compilation -// Having this as a build script rather than an external script allows the CLI to be installed with `cargo install` from any commit hash - -use std::fs; -use std::path::PathBuf; - -// All this is run relative to the `packages/perseus-cli/` directory -fn main() { - // Tell Cargo that this needs to be re-run if the direcotry storing the engine code has changed - println!("cargo:rerun-if-changed=../../examples/core/basic/.perseus"); - - let dest = PathBuf::from("."); - let engine_dir = PathBuf::from("../../examples/core/basic/.perseus"); - - // Replace the current `.perseus/` directory here with the latest version - let _ = fs::remove_dir_all(dest.join(".perseus")); // It's fine if this doesn't exist - fs_extra::dir::copy(engine_dir, &dest, &fs_extra::dir::CopyOptions::new()).unwrap(); - // Rename the manifests for appropriate usage - fs::rename( - dest.join(".perseus/Cargo.toml"), - dest.join(".perseus/Cargo.toml.old"), - ) - .unwrap(); - fs::rename( - dest.join(".perseus/server/Cargo.toml"), - dest.join(".perseus/server/Cargo.toml.old"), - ) - .unwrap(); - fs::rename( - dest.join(".perseus/builder/Cargo.toml"), - dest.join(".perseus/builder/Cargo.toml.old"), - ) - .unwrap(); - // Remove distribution artifacts so they don't clog up the final bundle - fs::remove_dir_all(dest.join(".perseus/dist")).unwrap(); - // But we need to create the basic directory structure for outputs - fs::create_dir(dest.join(".perseus/dist")).unwrap(); - fs::create_dir(dest.join(".perseus/dist/static")).unwrap(); - fs::create_dir(dest.join(".perseus/dist/exported")).unwrap(); - // Replace the example's package name with a token the CLI can use (compatible with alternative engines as well) - // We only need to do this in the root package, the others depend on it - // While we're at it, we'll update the dependencies to be tokens that can be replaced by the CLI (removing relative path references) - let updated_root_manifest = fs::read_to_string(dest.join(".perseus/Cargo.toml.old")) - .unwrap() - .replace("perseus-example-basic", "USER_PKG_NAME") - .replace("path = \"../../../../packages/perseus\"", "PERSEUS_VERSION"); - fs::write(dest.join(".perseus/Cargo.toml.old"), updated_root_manifest).unwrap(); - let updated_builder_manifest = fs::read_to_string(dest.join(".perseus/builder/Cargo.toml.old")) - .unwrap() - .replace( - "path = \"../../../../../packages/perseus\"", - "PERSEUS_VERSION", - ); - fs::write( - dest.join(".perseus/builder/Cargo.toml.old"), - updated_builder_manifest, - ) - .unwrap(); - let updated_server_manifest = fs::read_to_string(dest.join(".perseus/server/Cargo.toml.old")) - .unwrap() - .replace( - "path = \"../../../../../packages/perseus\"", - "PERSEUS_VERSION", - ) - .replace( - "path = \"../../../../../packages/perseus-actix-web\"", - "PERSEUS_ACTIX_WEB_VERSION", - ) - .replace( - "path = \"../../../../../packages/perseus-warp\"", - "PERSEUS_WARP_VERSION", - ); - fs::write( - dest.join(".perseus/server/Cargo.toml.old"), - updated_server_manifest, - ) - .unwrap(); -} - -/* -[ - # The CLI needs the `.perseus/` directory copied in for packaging (and we need to rename `Cargo.toml` to `Cargo.toml.old`) - "cd packages/perseus-cli", - "rm -rf ./.perseus", - "cp -r ../../examples/core/basic/.perseus/ .perseus/", - "mv .perseus/Cargo.toml .perseus/Cargo.toml.old", - "mv .perseus/server/Cargo.toml .perseus/server/Cargo.toml.old", - "mv .perseus/builder/Cargo.toml .perseus/builder/Cargo.toml.old", - # Remove distribution artifacts (they clog up the final bundle) - "rm -rf .perseus/dist", - "mkdir -p .perseus/dist", - "mkdir -p .perseus/dist/static", - "mkdir -p .perseus/dist/exported", - # Replace the example's package name with a token the CLI can use (compatible with alternative engines as well) - # We only need to do this in the root package, the others depend on it - "sed -i 's/perseus-example-basic/USER_PKG_NAME/' .perseus/Cargo.toml.old", - # Replace the relative path references with tokens too - "sed -i 's/path = \"\\.\\.\\/\\.\\.\\/\\.\\.\\/\\.\\.\\/packages\\/perseus\"/PERSEUS_VERSION/' .perseus/Cargo.toml.old", - "sed -i 's/path = \"\\.\\.\\/\\.\\.\\/\\.\\.\\/\\.\\.\\/\\.\\.\\/packages\\/perseus\"/PERSEUS_VERSION/' .perseus/builder/Cargo.toml.old", - # These will need to be updated as more integrations are added - "sed -i 's/path = \"\\.\\.\\/\\.\\.\\/\\.\\.\\/\\.\\.\\/\\.\\.\\/packages\\/perseus\"/PERSEUS_VERSION/' .perseus/server/Cargo.toml.old", - "sed -i 's/path = \"\\.\\.\\/\\.\\.\\/\\.\\.\\/\\.\\.\\/\\.\\.\\/packages\\/perseus-actix-web\"/PERSEUS_ACTIX_WEB_VERSION/' .perseus/server/Cargo.toml.old", - "sed -i 's/path = \"\\.\\.\\/\\.\\.\\/\\.\\.\\/\\.\\.\\/\\.\\.\\/packages\\/perseus-warp\"/PERSEUS_WARP_VERSION/' .perseus/server/Cargo.toml.old" -] -*/ diff --git a/packages/perseus-cli/src/bin/main.rs b/packages/perseus-cli/src/bin/main.rs index a0683a73f2..754652bb5a 100644 --- a/packages/perseus-cli/src/bin/main.rs +++ b/packages/perseus-cli/src/bin/main.rs @@ -4,14 +4,11 @@ use fmterr::fmt_err; use notify::{recommended_watcher, RecursiveMode, Watcher}; use perseus_cli::parse::{ExportOpts, ServeOpts, SnoopSubcommand}; use perseus_cli::{ - build, check_env, delete_artifacts, delete_bad_dir, deploy, eject, export, has_ejected, + build, check_env, delete_artifacts, deploy, export, parse::{Opts, Subcommand}, - prepare, serve, serve_exported, tinker, -}; -use perseus_cli::{ - errors::*, export_error_page, order_reload, run_reload_server, snoop_build, snoop_server, - snoop_wasm_build, + serve, serve_exported, tinker, }; +use perseus_cli::{delete_dist, errors::*, export_error_page, order_reload, run_reload_server, snoop_build, snoop_server, snoop_wasm_build}; use std::env; use std::io::Write; use std::path::PathBuf; @@ -39,7 +36,7 @@ async fn real_main() -> i32 { Err(err) => { eprintln!( "{}", - fmt_err(&PrepError::CurrentDirUnavailable { source: err }) + fmt_err(&Error::CurrentDirUnavailable { source: err }) ); return 1; } @@ -50,14 +47,7 @@ async fn real_main() -> i32 { Ok(exit_code) => exit_code, // If something failed, we print the error to `stderr` and return a failure exit code Err(err) => { - let should_cause_deletion = err_should_cause_deletion(&err); eprintln!("{}", fmt_err(&err)); - // Check if the error needs us to delete a partially-formed '.perseus/' directory - if should_cause_deletion { - if let Err(err) = delete_bad_dir(dir) { - eprintln!("{}", fmt_err(&err)); - } - } 1 } } @@ -210,10 +200,6 @@ async fn core(dir: PathBuf) -> Result { } async fn core_watch(dir: PathBuf, opts: Opts) -> Result { - // If we're not cleaning up artifacts, create them if needed - if !matches!(opts.subcmd, Subcommand::Clean(_)) { - prepare(dir.clone(), &opts.engine)?; - } let exit_code = match opts.subcmd { Subcommand::Build(build_opts) => { // Delete old build artifacts @@ -253,21 +239,8 @@ async fn core_watch(dir: PathBuf, opts: Opts) -> Result { let (exit_code, _server_path) = serve(dir, test_opts)?; exit_code } - Subcommand::Clean(clean_opts) => { - if clean_opts.dist { - // The user only wants to remove distribution artifacts - // We don't delete `render_conf.json` because it's literally impossible for that to be the source of a problem right now - delete_artifacts(dir.clone(), "static")?; - delete_artifacts(dir.clone(), "pkg")?; - delete_artifacts(dir, "exported")?; - } else { - // This command deletes the `.perseus/` directory completely, which musn't happen if the user has ejected - if has_ejected(dir.clone()) && !clean_opts.force { - return Err(EjectionError::CleanAfterEject.into()); - } - // Just delete the '.perseus/' directory directly, as we'd do in a corruption - delete_bad_dir(dir)?; - } + Subcommand::Clean => { + delete_dist(dir)?; 0 } Subcommand::Deploy(deploy_opts) => { @@ -276,21 +249,11 @@ async fn core_watch(dir: PathBuf, opts: Opts) -> Result { delete_artifacts(dir.clone(), "pkg")?; deploy(dir, deploy_opts)? } - Subcommand::Eject => { - eject(dir)?; - 0 - } Subcommand::Tinker(tinker_opts) => { - // We shouldn't run arbitrary plugin code designed to alter the engine if the user has made their own changes after ejecting - if has_ejected(dir.clone()) && !tinker_opts.force { - return Err(EjectionError::TinkerAfterEject.into()); - } // Unless we've been told not to, we start with a blank slate // This will remove old tinkerings and eliminate any possible corruptions (which are very likely with tinkering!) if !tinker_opts.no_clean { - delete_bad_dir(dir.clone())?; - // Recreate the '.perseus/' directory - prepare(dir.clone(), &opts.engine)?; + delete_dist(dir.clone())?; } tinker(dir)? } @@ -300,10 +263,6 @@ async fn core_watch(dir: PathBuf, opts: Opts) -> Result { SnoopSubcommand::Serve(snoop_serve_opts) => snoop_server(dir, snoop_serve_opts)?, }, Subcommand::ExportErrorPage(opts) => export_error_page(dir, opts)?, - Subcommand::Prep => { - // The `.perseus/` directory has already been set up in the preliminaries, so we don't need to do anything here - 0 - } }; Ok(exit_code) } diff --git a/packages/perseus-cli/src/build.rs b/packages/perseus-cli/src/build.rs index a8db51fcb8..d632c3ca70 100644 --- a/packages/perseus-cli/src/build.rs +++ b/packages/perseus-cli/src/build.rs @@ -5,8 +5,7 @@ use crate::thread::{spawn_thread, ThreadHandle}; use console::{style, Emoji}; use indicatif::{MultiProgress, ProgressBar}; use std::env; -use std::fs; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; // Emojis for stages static GENERATING: Emoji<'_, '_> = Emoji("🔨", ""); @@ -22,22 +21,22 @@ macro_rules! handle_exit_code { }; } -/// Finalizes the build by renaming some directories. -pub fn finalize(target: &Path) -> Result<(), ExecutionError> { - // Move the `pkg/` directory into `dist/pkg/` - let pkg_dir = target.join("dist/pkg"); - if pkg_dir.exists() { - if let Err(err) = fs::remove_dir_all(&pkg_dir) { - return Err(ExecutionError::MovePkgDirFailed { source: err }); - } - } - // The `fs::rename()` function will fail on Windows if the destination already exists, so this should work (we've just deleted it as per https://github.com/rust-lang/rust/issues/31301#issuecomment-177117325) - if let Err(err) = fs::rename(target.join("pkg"), target.join("dist/pkg")) { - return Err(ExecutionError::MovePkgDirFailed { source: err }); - } +// /// Finalizes the build by renaming some directories. +// pub fn finalize(target: &Path) -> Result<(), ExecutionError> { +// // Move the `pkg/` directory into `dist/pkg/` +// let pkg_dir = target.join("dist/pkg"); +// if pkg_dir.exists() { +// if let Err(err) = fs::remove_dir_all(&pkg_dir) { +// return Err(ExecutionError::MovePkgDirFailed { source: err }); +// } +// } +// // The `fs::rename()` function will fail on Windows if the destination already exists, so this should work (we've just deleted it as per https://github.com/rust-lang/rust/issues/31301#issuecomment-177117325) +// if let Err(err) = fs::rename(target.join("pkg"), target.join("dist/pkg")) { +// return Err(ExecutionError::MovePkgDirFailed { source: err }); +// } - Ok(()) -} +// Ok(()) +// } /// Actually builds the user's code, program arguments having been interpreted. This needs to know how many steps there are in total /// because the serving logic also uses it. This also takes a `MultiProgress` to interact with so it can be used truly atomically. @@ -55,8 +54,6 @@ pub fn build_internal( ), ExecutionError, > { - let target = dir.join(".perseus"); - // Static generation message let sg_msg = format!( "{} {} Generating your app", @@ -74,10 +71,10 @@ pub fn build_internal( // We make sure to add them at the top (the server spinner may have already been instantiated) let sg_spinner = spinners.insert(0, ProgressBar::new_spinner()); let sg_spinner = cfg_spinner(sg_spinner, &sg_msg); - let sg_target = target.join("builder"); // Static generation needs the `perseus-engine-builder` crate + let sg_dir = dir.clone(); let wb_spinner = spinners.insert(1, ProgressBar::new_spinner()); let wb_spinner = cfg_spinner(wb_spinner, &wb_msg); - let wb_target = target; + let wb_dir = dir; let sg_thread = spawn_thread(move || { handle_exit_code!(run_stage( vec![&format!( @@ -85,9 +82,10 @@ pub fn build_internal( env::var("PERSEUS_CARGO_PATH").unwrap_or_else(|_| "cargo".to_string()), if is_release { "--release" } else { "" } )], - &sg_target, + &sg_dir, &sg_spinner, - &sg_msg + &sg_msg, + "build" )?); Ok(0) @@ -95,13 +93,14 @@ pub fn build_internal( let wb_thread = spawn_thread(move || { handle_exit_code!(run_stage( vec![&format!( - "{} build --target web {}", + "{} build --out-dir dist/pkg --target web {}", env::var("PERSEUS_WASM_PACK_PATH").unwrap_or_else(|_| "wasm-pack".to_string()), if is_release { "--release" } else { "--dev" } // If we don't supply `--dev`, another profile will be used )], - &wb_target, + &wb_dir, &wb_spinner, - &wb_msg + &wb_msg, + "" // Not a builder command )?); Ok(0) @@ -131,7 +130,7 @@ pub fn build(dir: PathBuf, opts: BuildOpts) -> Result { // This waits for all the threads and lets the spinners draw to the terminal // spinners.join().map_err(|_| ErrorKind::ThreadWaitFailed)?; // And now we can run the finalization stage - finalize(&dir.join(".perseus"))?; + // finalize(&dir)?; // We've handled errors in the component threads, so the exit code is now zero Ok(0) diff --git a/packages/perseus-cli/src/cmd.rs b/packages/perseus-cli/src/cmd.rs index abffdc7f03..8f654a5a2f 100644 --- a/packages/perseus-cli/src/cmd.rs +++ b/packages/perseus-cli/src/cmd.rs @@ -14,6 +14,8 @@ pub static FAILURE: Emoji<'_, '_> = Emoji("❌", "failed!"); pub fn run_cmd( cmd: String, dir: &Path, + // This is only relevant for builder-related commands, but since that's most things, we may as well (it's only an env var) + op: &str, pre_dump: impl Fn(), ) -> Result<(String, String, i32), ExecutionError> { // We run the command in a shell so that NPM/Yarn binaries can be recognized (see #5) @@ -29,6 +31,7 @@ pub fn run_cmd( // This will NOT pipe output/errors to the console let output = Command::new(shell_exec) .args([shell_param, &cmd]) + .env("PERSEUS_BUILDER_OPERATION", op) .current_dir(dir) .output() .map_err(|err| ExecutionError::CmdExecFailed { cmd, source: err })?; @@ -79,12 +82,13 @@ pub fn run_stage( target: &Path, spinner: &ProgressBar, message: &str, + op: &str ) -> Result<(String, String, i32), ExecutionError> { let mut last_output = (String::new(), String::new()); // Run the commands for cmd in cmds { // We make sure all commands run in the target directory ('.perseus/' itself) - let (stdout, stderr, exit_code) = run_cmd(cmd.to_string(), target, || { + let (stdout, stderr, exit_code) = run_cmd(cmd.to_string(), target, op, || { // This stage has failed fail_spinner(spinner, message); })?; @@ -103,7 +107,7 @@ pub fn run_stage( /// Runs a command directly, piping its output and errors to the streams of this program. This allows the user to investigate the innards of /// Perseus, or just see their own `dbg!` calls. This will return the exit code of the command, which should be passed through to this program. -pub fn run_cmd_directly(cmd: String, dir: &Path) -> Result { +pub fn run_cmd_directly(cmd: String, dir: &Path, op: &str) -> Result { // The shell configurations for Windows and Unix #[cfg(unix)] let shell_exec = "sh"; @@ -117,6 +121,7 @@ pub fn run_cmd_directly(cmd: String, dir: &Path) -> Result let output = Command::new(shell_exec) .args([shell_param, &cmd]) .current_dir(dir) + .env("PERSEUS_BUILDER_OPERATION", op) .stdout(Stdio::inherit()) .stderr(Stdio::inherit()) .output() diff --git a/packages/perseus-cli/src/deploy.rs b/packages/perseus-cli/src/deploy.rs index c5c7426f69..a43324d550 100644 --- a/packages/perseus-cli/src/deploy.rs +++ b/packages/perseus-cli/src/deploy.rs @@ -1,6 +1,5 @@ use crate::errors::*; use crate::export; -use crate::parse::Integration; use crate::parse::{DeployOpts, ExportOpts, ServeOpts}; use crate::serve; use fs_extra::copy_items; @@ -16,7 +15,7 @@ pub fn deploy(dir: PathBuf, opts: DeployOpts) -> Result { let exit_code = if opts.export_static { deploy_export(dir, opts.output)? } else { - deploy_full(dir, opts.output, opts.integration)? + deploy_full(dir, opts.output)? }; Ok(exit_code) @@ -24,7 +23,7 @@ pub fn deploy(dir: PathBuf, opts: DeployOpts) -> Result { /// Deploys the user's app in its entirety, with a bundled server. This can return any kind of error because deploying involves working /// with other subcommands. -fn deploy_full(dir: PathBuf, output: String, integration: Integration) -> Result { +fn deploy_full(dir: PathBuf, output: String) -> Result { // Build everything for production, not running the server let (serve_exit_code, server_path) = serve( dir.clone(), @@ -33,7 +32,6 @@ fn deploy_full(dir: PathBuf, output: String, integration: Integration) -> Result no_build: false, release: true, standalone: true, - integration, watch: false, // These have no impact if `no_run` is `true` (which it is), so we can use the defaults here host: "127.0.0.1".to_string(), @@ -72,17 +70,6 @@ fn deploy_full(dir: PathBuf, output: String, integration: Integration) -> Result } .into()); } - // Copy in the `index.html` file - let from = dir.join("index.html"); - let to = output_path.join("index.html"); - if let Err(err) = fs::copy(&from, &to) { - return Err(DeployError::MoveAssetFailed { - to: to.to_str().map(|s| s.to_string()).unwrap(), - from: from.to_str().map(|s| s.to_string()).unwrap(), - source: err, - } - .into()); - } // Copy in the `static/` directory if it exists let from = dir.join("static"); if from.exists() { @@ -107,8 +94,8 @@ fn deploy_full(dir: PathBuf, output: String, integration: Integration) -> Result .into()); } } - // Copy in the entire `.perseus/dist` directory (it must exist) - let from = dir.join(".perseus/dist"); + // Copy in the entire `dist` directory (it must exist) + let from = dir.join("dist"); if let Err(err) = copy_dir(&from, &output, &CopyOptions::new()) { return Err(DeployError::MoveDirFailed { to: output, @@ -145,9 +132,9 @@ fn deploy_export(dir: PathBuf, output: String) -> Result { if export_exit_code != 0 { return Ok(export_exit_code); } - // That subcommand produces a self-contained static site at `.perseus/dist/exported/` + // That subcommand produces a self-contained static site at `dist/exported/` // Just copy that out to the output directory - let from = dir.join(".perseus/dist/exported"); + let from = dir.join("dist/exported"); let output_path = PathBuf::from(&output); // Delete the output directory if it exists and recreate it if output_path.exists() { diff --git a/packages/perseus-cli/src/eject.rs b/packages/perseus-cli/src/eject.rs deleted file mode 100644 index 7741555980..0000000000 --- a/packages/perseus-cli/src/eject.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::errors::*; -use std::fs; -use std::path::PathBuf; - -/// Ejects the user from the Perseus CLi harness by exposing the internal subcrates to them. All this does is remove `.perseus/` from -/// the user's `.gitignore` and add a file `.ejected` to `.perseus/`. -pub fn eject(dir: PathBuf) -> Result<(), EjectionError> { - // Create a file declaring ejection so `clean` throws errors (we don't want the user to accidentally delete everything) - let ejected = dir.join(".perseus/.ejected"); - fs::write( - &ejected, - "This file signals to Perseus that you've ejected. Do NOT delete it!", - ) - .map_err(|err| EjectionError::GitignoreUpdateFailed { source: err })?; - // Now remove `.perseus/` from the user's `.gitignore` - let gitignore = dir.join(".gitignore"); - if gitignore.exists() { - let content = fs::read_to_string(&gitignore) - .map_err(|err| EjectionError::GitignoreUpdateFailed { source: err })?; - let mut new_content_vec = Vec::new(); - // Remove the line pertaining to Perseus - // We only target the one that's exactly the same as what's automatically injected, anything else can be done manually - let mut have_changed = false; - for line in content.lines() { - if line != ".perseus/" { - new_content_vec.push(line); - } else { - have_changed = true; - } - } - let new_content = new_content_vec.join("\n"); - // Make sure we've actually changed something - if !have_changed { - return Err(EjectionError::GitignoreLineNotPresent); - } - fs::write(&gitignore, new_content) - .map_err(|err| EjectionError::GitignoreUpdateFailed { source: err })?; - - Ok(()) - } else { - // The file wasn't found - Err(EjectionError::GitignoreUpdateFailed { - source: std::io::Error::from(std::io::ErrorKind::NotFound), - }) - } -} - -/// Checks if the user has ejected or not. If they have, commands like `clean` should fail unless `--force` is provided. -pub fn has_ejected(dir: PathBuf) -> bool { - let ejected = dir.join(".perseus/.ejected"); - ejected.exists() -} diff --git a/packages/perseus-cli/src/errors.rs b/packages/perseus-cli/src/errors.rs index 6aa51d2155..ac4de0bedb 100644 --- a/packages/perseus-cli/src/errors.rs +++ b/packages/perseus-cli/src/errors.rs @@ -5,23 +5,6 @@ use thiserror::Error; /// All errors that can be returned by the CLI. #[derive(Error, Debug)] pub enum Error { - #[error(transparent)] - PrepError(#[from] PrepError), - #[error(transparent)] - ExecutionError(#[from] ExecutionError), - #[error(transparent)] - EjectionError(#[from] EjectionError), - #[error(transparent)] - ExportError(#[from] ExportError), - #[error(transparent)] - DeployError(#[from] DeployError), - #[error(transparent)] - WatchError(#[from] WatchError), -} - -/// Errors that can occur while preparing. -#[derive(Error, Debug)] -pub enum PrepError { #[error("prerequisite command execution failed for prerequisite '{cmd}' (set '{env_var}' to another location if you've installed it elsewhere)")] PrereqNotPresent { cmd: String, @@ -34,62 +17,16 @@ pub enum PrepError { #[source] source: std::io::Error, }, - #[error("couldn't extract internal subcrates to '{target_dir:?}' (do you have the necessary permissions?)")] - ExtractionFailed { - target_dir: Option, - #[source] - source: std::io::Error, - }, - #[error("updating gitignore to ignore `.perseus/` failed (`.perseus/` has been automatically deleted)")] - GitignoreUpdateFailed { - #[source] - source: std::io::Error, - }, - #[error("couldn't update internal manifest file at '{target_dir:?}' (`.perseus/` has been automatically deleted)")] - ManifestUpdateFailed { - target_dir: Option, - #[source] - source: std::io::Error, - }, - #[error("couldn't get `Cargo.toml` for your project (have you run `cargo init` yet?)")] - GetUserManifestFailed { - #[source] - source: cargo_toml::Error, - }, - #[error( - "your project's `Cargo.toml` doesn't have a `[package]` section (package name is required)" - )] - MalformedUserManifest, - #[error("couldn't remove corrupted `.perseus/` directory as required by previous error (please delete `.perseus/` manually)")] - RemoveBadDirFailed { - #[source] - source: std::io::Error, - }, - #[error("fetching the custom engine failed")] - GetEngineFailed { - #[source] - source: ExecutionError, - }, - #[error("fetching the custom engine returned non-zero exit code ({exit_code})")] - GetEngineNonZeroExitCode { exit_code: i32 }, - #[error("couldn't remove git internals at '{target_dir:?}' for custom engine")] - RemoveEngineGitFailed { - target_dir: Option, - #[source] - source: std::io::Error, - }, -} -/// Checks if the given error should cause the CLI to delete the '.perseus/' folder so the user doesn't have something incomplete. -/// When deleting the directory, it should only be deleted if it exists, if not don't worry. If it does and deletion fails, fail like hell. -pub fn err_should_cause_deletion(err: &Error) -> bool { - matches!( - err, - Error::PrepError( - PrepError::ExtractionFailed { .. } - | PrepError::GitignoreUpdateFailed { .. } - | PrepError::ManifestUpdateFailed { .. } - ) - ) + #[error(transparent)] + ExecutionError(#[from] ExecutionError), + #[error(transparent)] + EjectionError(#[from] EjectionError), + #[error(transparent)] + ExportError(#[from] ExportError), + #[error(transparent)] + DeployError(#[from] DeployError), + #[error(transparent)] + WatchError(#[from] WatchError), } /// Errors that can occur while attempting to execute a Perseus app with `build`/`serve` (export errors are separate). diff --git a/packages/perseus-cli/src/export.rs b/packages/perseus-cli/src/export.rs index a5fd060872..f5a10614cd 100644 --- a/packages/perseus-cli/src/export.rs +++ b/packages/perseus-cli/src/export.rs @@ -139,8 +139,6 @@ pub fn export_internal( ), ExportError, > { - let target = dir.join(".perseus"); - // Exporting pages message let ep_msg = format!( "{} {} Exporting your app's pages", @@ -158,20 +156,21 @@ pub fn export_internal( // We make sure to add them at the top (the server spinner may have already been instantiated) let ep_spinner = spinners.insert(0, ProgressBar::new_spinner()); let ep_spinner = cfg_spinner(ep_spinner, &ep_msg); - let ep_target = target.join("builder"); + let ep_target = dir.clone(); let wb_spinner = spinners.insert(1, ProgressBar::new_spinner()); let wb_spinner = cfg_spinner(wb_spinner, &wb_msg); - let wb_target = target; + let wb_target = dir; let ep_thread = spawn_thread(move || { handle_exit_code!(run_stage( vec![&format!( - "{} run --bin perseus-exporter {}", + "{} run {}", env::var("PERSEUS_CARGO_PATH").unwrap_or_else(|_| "cargo".to_string()), if is_release { "--release" } else { "" } )], &ep_target, &ep_spinner, - &ep_msg + &ep_msg, + "export" )?); Ok(0) @@ -179,13 +178,14 @@ pub fn export_internal( let wb_thread = spawn_thread(move || { handle_exit_code!(run_stage( vec![&format!( - "{} build --target web {}", + "{} build --out-dir dist/pkg --target web {}", env::var("PERSEUS_WASM_PACK_PATH").unwrap_or_else(|_| "wasm-pack".to_string()), if is_release { "--release" } else { "--dev" } )], &wb_target, &wb_spinner, - &wb_msg + &wb_msg, + "" // Not a builder command )?); Ok(0) @@ -216,7 +216,7 @@ pub fn export(dir: PathBuf, opts: ExportOpts) -> Result { } // And now we can run the finalization stage - finalize_export(&dir.join(".perseus"))?; + finalize_export(&dir)?; // We've handled errors in the component threads, so the exit code is now zero Ok(0) diff --git a/packages/perseus-cli/src/export_error_page.rs b/packages/perseus-cli/src/export_error_page.rs index 278d9412ef..655527edb9 100644 --- a/packages/perseus-cli/src/export_error_page.rs +++ b/packages/perseus-cli/src/export_error_page.rs @@ -9,12 +9,13 @@ pub fn export_error_page(dir: PathBuf, opts: ExportErrorPageOpts) -> Result>(dir: Dir, path: S) -> std::io::Result<()> { - let path = path.as_ref(); - - // Create all the subdirectories in here (but not their files yet) - for dir in dir.dirs() { - std::fs::create_dir_all(path.join(dir.path()))?; - // Recurse for this directory - extract_dir(*dir, path)?; - } - - // Write all the files at the root of this directory - for file in dir.files() { - let mut fsf = std::fs::OpenOptions::new() - .write(true) - .create_new(true) - .open(path.join(file.path()))?; - fsf.write_all(file.contents())?; - fsf.sync_all()?; - } - - Ok(()) -} diff --git a/packages/perseus-cli/src/lib.rs b/packages/perseus-cli/src/lib.rs index fa021f5a0f..7e82ed0f82 100644 --- a/packages/perseus-cli/src/lib.rs +++ b/packages/perseus-cli/src/lib.rs @@ -18,11 +18,9 @@ the documentation you'd like to see on this front! mod build; mod cmd; mod deploy; -mod eject; pub mod errors; mod export; mod export_error_page; -mod extraction; /// Parsing utilities for arguments. pub mod parse; mod prepare; @@ -41,34 +39,35 @@ use std::path::PathBuf; pub const PERSEUS_VERSION: &str = env!("CARGO_PKG_VERSION"); pub use build::build; pub use deploy::deploy; -pub use eject::{eject, has_ejected}; pub use export::export; pub use export_error_page::export_error_page; -pub use prepare::{check_env, prepare}; +pub use prepare::check_env; pub use reload_server::{order_reload, run_reload_server}; pub use serve::serve; pub use serve_exported::serve_exported; pub use snoop::{snoop_build, snoop_server, snoop_wasm_build}; pub use tinker::tinker; -/// Deletes a corrupted '.perseus/' directory. This will be called on certain error types that would leave the user with a half-finished -/// product, which is better to delete for safety and sanity. -pub fn delete_bad_dir(dir: PathBuf) -> Result<(), PrepError> { - let mut target = dir; - target.extend([".perseus"]); - // We'll only delete the directory if it exists, otherwise we're fine +/// Deletes the entire `dist/` directory. Nicely, because there are no Cargo artifacts in there, +/// running this won't slow down future runs at all. +pub fn delete_dist(dir: PathBuf) -> Result<(), ExecutionError> { + let target = dir.join("dist"); if target.exists() { if let Err(err) = fs::remove_dir_all(&target) { - return Err(PrepError::RemoveBadDirFailed { source: err }); + return Err(ExecutionError::RemoveArtifactsFailed { + target: target.to_str().map(|s| s.to_string()), + source: err, + }); } } + Ok(()) } -/// Deletes build artifacts in `.perseus/dist/static` or `.perseus/dist/pkg` and replaces the directory. +/// Deletes build artifacts in `dist/static` or `dist/pkg` and replaces the directory. pub fn delete_artifacts(dir: PathBuf, dir_to_remove: &str) -> Result<(), ExecutionError> { let mut target = dir; - target.extend([".perseus", "dist", dir_to_remove]); + target.extend(["dist", dir_to_remove]); // We'll only delete the directory if it exists, otherwise we're fine if target.exists() { if let Err(err) = fs::remove_dir_all(&target) { diff --git a/packages/perseus-cli/src/parse.rs b/packages/perseus-cli/src/parse.rs index 6c67f7cf5d..e614da27db 100644 --- a/packages/perseus-cli/src/parse.rs +++ b/packages/perseus-cli/src/parse.rs @@ -10,43 +10,10 @@ use clap::Parser; #[clap(version = PERSEUS_VERSION)] // #[clap(setting = AppSettings::ColoredHelp)] pub struct Opts { - /// The URL of a Git repository to clone to provide a custom engine. If this is set to `default`, the normal Perseus engine (packaged with the CLI) will be used. A branch name can be added at - /// the end of this after `@` to specify a custom branch/version - #[clap(short, long, default_value = "default")] - pub engine: String, #[clap(subcommand)] pub subcmd: Subcommand, } -#[derive(Parser, PartialEq, Eq, Clone)] -pub enum Integration { - ActixWeb, - Warp, - Axum, -} -// We use an `enum` for this so we don't get errors from Cargo about non-existent feature flags, overly verbose but fails quickly -impl std::str::FromStr for Integration { - type Err = String; - - fn from_str(s: &str) -> Result { - match s { - "actix-web" => Ok(Self::ActixWeb), - "warp" => Ok(Self::Warp), - "axum" => Ok(Self::Axum), - _ => Err("invalid integration name".into()), - } - } -} -impl ToString for Integration { - fn to_string(&self) -> String { - match self { - Self::ActixWeb => "actix-web".to_string(), - Self::Warp => "warp".to_string(), - Self::Axum => "axum".to_string(), - } - } -} - #[derive(Parser)] pub enum Subcommand { Build(BuildOpts), @@ -55,12 +22,9 @@ pub enum Subcommand { Serve(ServeOpts), /// Serves your app as `perseus serve` does, but puts it in testing mode Test(ServeOpts), - Clean(CleanOpts), - /// Ejects you from the CLI harness, enabling you to work with the internals of Perseus - Eject, + /// Removes build artifacts in the `dist/` directory + Clean, Deploy(DeployOpts), - /// Prepares the `.perseus/` directory (done automatically by `build` and `serve`) - Prep, Tinker(TinkerOpts), /// Runs one of the underlying commands that builds your app, allowing you to see more detailed logs #[clap(subcommand)] @@ -115,9 +79,6 @@ pub struct ServeOpts { /// Make the final binary standalone (this is used in `perseus deploy` only, don't manually invoke it unless you have a good reason!) #[clap(long)] pub standalone: bool, - /// The server integration to use - #[clap(short, long, default_value = "warp")] - pub integration: Integration, /// Watch the files in your working directory for changes (exluding `target/` and `.perseus/`) #[clap(short, long)] pub watch: bool, @@ -128,16 +89,6 @@ pub struct ServeOpts { #[clap(long, default_value = "8080")] pub port: u16, } -/// Removes `.perseus/` entirely for updates or to fix corruptions -#[derive(Parser)] -pub struct CleanOpts { - /// Only remove the `.perseus/dist/` folder (use if you've ejected) - #[clap(short, long)] - pub dist: bool, - /// Remove the directory, even if you've ejected (this will permanently destroy any changes you've made to `.perseus/`!) - #[clap(short, long)] - pub force: bool, -} /// Packages your app for deployment #[derive(Parser)] pub struct DeployOpts { @@ -147,19 +98,13 @@ pub struct DeployOpts { /// Export you app to purely static files (see `export`) #[clap(short, long)] pub export_static: bool, - /// The server integration to use (only affects non-exported deployments) - #[clap(short, long, default_value = "warp")] - pub integration: Integration, } /// Runs the `tinker` action of plugins, which lets them modify the Perseus engine #[derive(Parser)] pub struct TinkerOpts { - /// Don't remove and recreate the `.perseus/` directory + /// Don't remove and recreate the `dist/` directory #[clap(long)] pub no_clean: bool, - /// Force this command to run, even if you've ejected (this may result in some or all of your changes being removed, it depends on the plugins you're using) - #[clap(long)] - pub force: bool, } #[derive(Parser)] @@ -181,9 +126,6 @@ pub struct SnoopWasmOpts { #[derive(Parser)] pub struct SnoopServeOpts { - /// The server integration to use - #[clap(short, long, default_value = "warp")] - pub integration: Integration, /// Where to host your exported app #[clap(long, default_value = "127.0.0.1")] pub host: String, diff --git a/packages/perseus-cli/src/prepare.rs b/packages/perseus-cli/src/prepare.rs index ac57099636..f5b6cdda03 100644 --- a/packages/perseus-cli/src/prepare.rs +++ b/packages/perseus-cli/src/prepare.rs @@ -1,229 +1,13 @@ -use crate::cmd::run_cmd_directly; use crate::errors::*; -use crate::extraction::extract_dir; #[allow(unused_imports)] -use crate::PERSEUS_VERSION; use cargo_toml::Manifest; -use include_dir::{include_dir, Dir}; use std::env; -use std::fs; -use std::fs::OpenOptions; -use std::io::Write; -use std::path::PathBuf; use std::process::Command; -// This literally includes the entire subcrate in the program, allowing more efficient development. -// This MUST be copied in from `../../examples/cli/.perseus/` every time the CLI is tested (use the Bonnie script). -const SUBCRATES: Dir = include_dir!("./.perseus"); - -/// Prepares the user's project by copying in the `.perseus/` subcrates. We use these subcrates to do all the building/serving, we just -/// have to execute the right commands in the CLI. We can essentially treat the subcrates themselves as a blackbox of just a folder. -pub fn prepare(dir: PathBuf, engine_url: &str) -> Result<(), PrepError> { - // The location in the target directory at which we'll put the subcrates - let target = dir.join(".perseus"); - - if target.exists() { - // We don't care if it's corrupted etc., it just has to exist - // If the user wants to clean it, they can do that - // Besides, we want them to be able to customize stuff - Ok(()) - } else { - // Create the directory first - if let Err(err) = fs::create_dir(&target) { - return Err(PrepError::ExtractionFailed { - target_dir: target.to_str().map(|s| s.to_string()), - source: err, - }); - } - // Check if we're using the bundled engine or a custom one - if engine_url == "default" { - // Write the stored directory to the target location - // Notably, this function will not do anything or tell us if the directory already exists... - if let Err(err) = extract_dir(SUBCRATES, &target) { - return Err(PrepError::ExtractionFailed { - target_dir: target.to_str().map(|s| s.to_string()), - source: err, - }); - } - } else { - // We're using a non-standard engine, which we'll download using Git - // All other steps of integration with the user's package after this are the same - let url_parts = engine_url.split('@').collect::>(); - let engine_url = url_parts[0]; - // A custom branch can be specified after a `@`, or we'll use `stable` - let engine_branch = url_parts.get(1).unwrap_or(&"stable"); - let cmd = format!( - // We'll only clone the production branch, and only the top level, we don't need the whole shebang - "{} clone --single-branch --branch {branch} --depth 1 {repo} {output}", - env::var("PERSEUS_GIT_PATH").unwrap_or_else(|_| "git".to_string()), - branch = engine_branch, - repo = engine_url, - output = target.to_string_lossy() - ); - println!("Fetching custom engine with command: '{}'.", &cmd); - // Tell the user what command we're running so that they can debug it - let exit_code = run_cmd_directly( - cmd, - &dir, // We'll run this in the current directory and output into `.perseus/` - ) - .map_err(|err| PrepError::GetEngineFailed { source: err })?; - if exit_code != 0 { - return Err(PrepError::GetEngineNonZeroExitCode { exit_code }); - } - // Now delete the Git internals - let git_target = target.join(".git"); - if let Err(err) = fs::remove_dir_all(&git_target) { - return Err(PrepError::RemoveEngineGitFailed { - target_dir: git_target.to_str().map(|s| s.to_string()), - source: err, - }); - } - } - - // Prepare for transformations on the manifest files - // We have to store `Cargo.toml` as `Cargo.toml.old` for packaging - let root_manifest_pkg = target.join("Cargo.toml.old"); - let root_manifest = target.join("Cargo.toml"); - let server_manifest_pkg = target.join("server/Cargo.toml.old"); - let server_manifest = target.join("server/Cargo.toml"); - let builder_manifest_pkg = target.join("builder/Cargo.toml.old"); - let builder_manifest = target.join("builder/Cargo.toml"); - let root_manifest_contents = fs::read_to_string(&root_manifest_pkg).map_err(|err| { - PrepError::ManifestUpdateFailed { - target_dir: root_manifest_pkg.to_str().map(|s| s.to_string()), - source: err, - } - })?; - let server_manifest_contents = fs::read_to_string(&server_manifest_pkg).map_err(|err| { - PrepError::ManifestUpdateFailed { - target_dir: server_manifest_pkg.to_str().map(|s| s.to_string()), - source: err, - } - })?; - let builder_manifest_contents = - fs::read_to_string(&builder_manifest_pkg).map_err(|err| { - PrepError::ManifestUpdateFailed { - target_dir: builder_manifest_pkg.to_str().map(|s| s.to_string()), - source: err, - } - })?; - // Get the name of the user's crate (which the subcrates depend on) - // We assume they're running this in a folder with a Cargo.toml... - let user_manifest = Manifest::from_path("./Cargo.toml") - .map_err(|err| PrepError::GetUserManifestFailed { source: err })?; - let user_crate_name = user_manifest.package; - let user_crate_name = match user_crate_name { - Some(package) => package.name, - None => return Err(PrepError::MalformedUserManifest), - }; - // Update the name of the user's crate (Cargo needs more than just a path and an alias) - // We don't need to do that in the server manifest because it uses the root code (which re-exports the `PerseusApp`) - // We used to add a workspace here, but that means size optimizations apply to both the client and the server, so that's not done anymore - // Now, we use an empty workspace to make sure we don't include the engine in any user workspaces - // We use a token here that's set by the build script - let updated_root_manifest = - root_manifest_contents.replace("USER_PKG_NAME", &user_crate_name) + "\n[workspace]"; - let updated_server_manifest = server_manifest_contents + "\n[workspace]"; - let updated_builder_manifest = builder_manifest_contents + "\n[workspace]"; - - // We also need to set the Perseus version - // In production, we'll use the full version, but in development we'll use relative path references from the examples - // The tokens here are set by the build script once again - // Production - #[cfg(not(debug_assertions))] - let updated_root_manifest = updated_root_manifest.replace( - "PERSEUS_VERSION", - &format!("version = \"{}\"", PERSEUS_VERSION), - ); - #[cfg(not(debug_assertions))] - let updated_server_manifest = updated_server_manifest - .replace( - "PERSEUS_VERSION", - &format!("version = \"{}\"", PERSEUS_VERSION), - ) - .replace( - "PERSEUS_ACTIX_WEB_VERSION", - &format!("version = \"{}\"", PERSEUS_VERSION), - ) - .replace( - "PERSEUS_WARP_VERSION", - &format!("version = \"{}\"", PERSEUS_VERSION), - ); - #[cfg(not(debug_assertions))] - let updated_builder_manifest = updated_builder_manifest.replace( - "PERSEUS_VERSION", - &format!("version = \"{}\"", PERSEUS_VERSION), - ); - // Development - #[cfg(debug_assertions)] - let updated_root_manifest = updated_root_manifest - .replace("PERSEUS_VERSION", "path = \"../../../../packages/perseus\""); - #[cfg(debug_assertions)] - let updated_server_manifest = updated_server_manifest - .replace( - "PERSEUS_VERSION", - "path = \"../../../../../packages/perseus\"", - ) - .replace( - "PERSEUS_ACTIX_WEB_VERSION", - "path = \"../../../../../packages/perseus-actix-web\"", - ) - .replace( - "PERSEUS_WARP_VERSION", - "path = \"../../../../../packages/perseus-warp\"", - ); - #[cfg(debug_assertions)] - let updated_builder_manifest = updated_builder_manifest.replace( - "PERSEUS_VERSION", - "path = \"../../../../../packages/perseus\"", - ); - - // Write the updated manifests back - if let Err(err) = fs::write(&root_manifest, updated_root_manifest) { - return Err(PrepError::ManifestUpdateFailed { - target_dir: root_manifest.to_str().map(|s| s.to_string()), - source: err, - }); - } - if let Err(err) = fs::write(&server_manifest, updated_server_manifest) { - return Err(PrepError::ManifestUpdateFailed { - target_dir: server_manifest.to_str().map(|s| s.to_string()), - source: err, - }); - } - if let Err(err) = fs::write(&builder_manifest, updated_builder_manifest) { - return Err(PrepError::ManifestUpdateFailed { - target_dir: builder_manifest.to_str().map(|s| s.to_string()), - source: err, - }); - } - - // If we aren't already gitignoring the subcrates, update .gitignore to do so - if let Ok(contents) = fs::read_to_string(".gitignore") { - if contents.contains(".perseus/") { - return Ok(()); - } - } - let file = OpenOptions::new() - .append(true) - .create(true) // If it doesn't exist, create it - .open(".gitignore"); - let mut file = match file { - Ok(file) => file, - Err(err) => return Err(PrepError::GitignoreUpdateFailed { source: err }), - }; - // Check for errors with appending to the file - if let Err(err) = file.write_all(b"\n.perseus/") { - return Err(PrepError::GitignoreUpdateFailed { source: err }); - } - Ok(()) - } -} - /// Checks if the user has the necessary prerequisites on their system (i.e. `cargo` and `wasm-pack`). These can all be checked /// by just trying to run their binaries and looking for errors. If the user has other paths for these, they can define them under the /// environment variables `PERSEUS_CARGO_PATH` and `PERSEUS_WASM_PACK_PATH`. -pub fn check_env() -> Result<(), PrepError> { +pub fn check_env() -> Result<(), Error> { // We'll loop through each prerequisite executable to check their existence // If the spawn returns an error, it's considered not present, success means presence let prereq_execs = vec![ @@ -243,7 +27,7 @@ pub fn check_env() -> Result<(), PrepError> { let res = Command::new(&exec.0).output(); // Any errors are interpreted as meaning that the user doesn't have the prerequisite installed properly. if let Err(err) = res { - return Err(PrepError::PrereqNotPresent { + return Err(Error::PrereqNotPresent { cmd: exec.1.to_string(), env_var: exec.2.to_string(), source: err, diff --git a/packages/perseus-cli/src/serve.rs b/packages/perseus-cli/src/serve.rs index 6476d71fb3..422d5ff8ad 100644 --- a/packages/perseus-cli/src/serve.rs +++ b/packages/perseus-cli/src/serve.rs @@ -1,6 +1,6 @@ -use crate::build::{build_internal, finalize}; +use crate::build::build_internal; use crate::cmd::{cfg_spinner, run_stage}; -use crate::parse::{Integration, ServeOpts}; +use crate::parse::ServeOpts; use crate::thread::{spawn_thread, ThreadHandle}; use crate::{errors::*, order_reload}; use console::{style, Emoji}; @@ -36,24 +36,14 @@ fn build_server( did_build: bool, exec: Arc>, is_release: bool, - is_standalone: bool, - integration: Integration, ) -> Result< ThreadHandle Result, Result>, ExecutionError, > { - // If we're using the Actix Web integration, warn that it's unstable - // A similar warning is emitted for snooping on it - // TODO Remove this once Actix Web v4.0.0 goes stable - if integration == Integration::ActixWeb { - println!("WARNING: The Actix Web integration uses a beta version of Actix Web, and is considered unstable. It is not recommended for production usage.") - } - let num_steps = match did_build { true => 4, false => 2, }; - let target = dir.join(".perseus/server"); // Server building message let sb_msg = format!( @@ -68,26 +58,19 @@ fn build_server( // We deliberately insert the spinner at the end of the list let sb_spinner = spinners.insert(num_steps - 1, ProgressBar::new_spinner()); let sb_spinner = cfg_spinner(sb_spinner, &sb_msg); - let sb_target = target; + let sb_target = dir.clone(); let sb_thread = spawn_thread(move || { let (stdout, _stderr) = handle_exit_code!(run_stage( vec![&format!( // This sets Cargo to tell us everything, including the executable path to the server - "{} build --message-format json --features integration-{} {} --no-default-features {}", + "{} build --message-format json {}", env::var("PERSEUS_CARGO_PATH").unwrap_or_else(|_| "cargo".to_string()), - // Enable the appropriate integration - integration.to_string(), - // We'll also handle whether or not it's standalone because that goes under the `--features` flag - if is_standalone { - "--features standalone" - } else { - "" - }, if is_release { "--release" } else { "" }, )], &sb_target, &sb_spinner, - &sb_msg + &sb_msg, + "" // The server will be built if we build for the server-side (builder and server are currently one for Cargo) )?); let msgs: Vec<&str> = stdout.trim().split('\n').collect(); @@ -134,7 +117,6 @@ fn run_server( dir: PathBuf, did_build: bool, ) -> Result { - let target = dir.join(".perseus/server"); let num_steps = match did_build { true => 4, false => 2, @@ -152,7 +134,7 @@ fn run_server( // Manually run the generated binary (invoking in the right directory context for good measure if it ever needs it in future) let child = Command::new(&server_exec_path) - .current_dir(target) + .current_dir(&dir) // We should be able to access outputs in case there's an error .stdout(Stdio::piped()) .stderr(Stdio::piped()) @@ -213,8 +195,6 @@ pub fn serve(dir: PathBuf, opts: ServeOpts) -> Result<(i32, Option), Exe did_build, Arc::clone(&exec), opts.release, - opts.standalone, - opts.integration, )?; // Only build if the user hasn't set `--no-build`, handling non-zero exit codes if did_build { @@ -239,11 +219,6 @@ pub fn serve(dir: PathBuf, opts: ServeOpts) -> Result<(i32, Option), Exe return Ok((sb_res, None)); } - // And now we can run the finalization stage (only if `--no-build` wasn't specified) - if did_build { - finalize(&dir.join(".perseus"))?; - } - // Order any connected browsers to reload order_reload(); @@ -254,7 +229,7 @@ pub fn serve(dir: PathBuf, opts: ServeOpts) -> Result<(i32, Option), Exe } else { // The user doesn't want to run the server, so we'll give them the executable path instead let exec_str = (*exec.lock().unwrap()).to_string(); - println!("Not running server because `--no-run` was provided. You can run it manually by running the following executable in `.perseus/server/`.\n{}", &exec_str); + println!("Not running server because `--no-run` was provided. You can run it manually by running the following executable from the root of the project.\n{}", &exec_str); Ok((0, Some(exec_str))) } } diff --git a/packages/perseus-cli/src/snoop.rs b/packages/perseus-cli/src/snoop.rs index e41e483be0..63482274d0 100644 --- a/packages/perseus-cli/src/snoop.rs +++ b/packages/perseus-cli/src/snoop.rs @@ -7,22 +7,21 @@ use std::path::PathBuf; /// Runs static generation processes directly so the user can see detailed logs. This is commonly used for allowing users to see `dbg!` and /// the like in their builder functions. pub fn snoop_build(dir: PathBuf) -> Result { - let target = dir.join(".perseus/builder"); run_cmd_directly( format!( "{} run", env::var("PERSEUS_CARGO_PATH").unwrap_or_else(|_| "cargo".to_string()) ), - &target, + &dir, + "build" ) } /// Runs the commands to build the user's app to Wasm directly so they can see detailed logs. pub fn snoop_wasm_build(dir: PathBuf, opts: SnoopWasmOpts) -> Result { - let target = dir.join(".perseus"); run_cmd_directly( format!( - "{} build --target web {}", + "{} build --out-dir dist/pkg --target web {}", env::var("PERSEUS_WASM_PACK_PATH").unwrap_or_else(|_| "wasm-pack".to_string()), if opts.profiling { "--profiling" @@ -30,7 +29,8 @@ pub fn snoop_wasm_build(dir: PathBuf, opts: SnoopWasmOpts) -> Result Result Result, Result>, Error, > { - let target = dir.join(".perseus/builder"); - // Tinkering message let tk_msg = format!( "{} {} Running plugin tinkers", @@ -43,16 +41,17 @@ pub fn tinker_internal( // We make sure to add them at the top (other spinners may have already been instantiated) let tk_spinner = spinners.insert(0, ProgressBar::new_spinner()); let tk_spinner = cfg_spinner(tk_spinner, &tk_msg); - let tk_target = target; + let tk_target = dir; let tk_thread = spawn_thread(move || { handle_exit_code!(run_stage( vec![&format!( - "{} run --bin perseus-tinker", + "{} run", env::var("PERSEUS_CARGO_PATH").unwrap_or_else(|_| "cargo".to_string()), )], &tk_target, &tk_spinner, - &tk_msg + &tk_msg, + "tinker" )?); Ok(0) diff --git a/packages/perseus-macro/src/autoserde.rs b/packages/perseus-macro/src/autoserde.rs index bca5617669..213a057810 100644 --- a/packages/perseus-macro/src/autoserde.rs +++ b/packages/perseus-macro/src/autoserde.rs @@ -111,6 +111,10 @@ pub fn autoserde_impl(input: AutoserdeFn, fn_type: AutoserdeArgs) -> TokenStream if fn_type.build_state { // This will always be asynchronous quote! { + // We create a normal version of the function and one to appease the handlers in Wasm (which expect functions that take no arguments, etc.) + #[cfg(target_arch = "wasm32")] + #vis fn #name() {} + #[cfg(not(target_arch = "wasm32"))] #vis async fn #name(path: ::std::string::String, locale: ::std::string::String) -> ::perseus::RenderFnResultWithCause<::std::string::String> { // The user's function // We can assume the return type to be `RenderFnResultWithCause` @@ -129,6 +133,10 @@ pub fn autoserde_impl(input: AutoserdeFn, fn_type: AutoserdeArgs) -> TokenStream } else if fn_type.request_state { // This will always be asynchronous quote! { + // We create a normal version of the function and one to appease the handlers in Wasm (which expect functions that take no arguments, etc.) + #[cfg(target_arch = "wasm32")] + #vis fn #name() {} + #[cfg(not(target_arch = "wasm32"))] #vis async fn #name(path: ::std::string::String, locale: ::std::string::String, req: ::perseus::Request) -> ::perseus::RenderFnResultWithCause<::std::string::String> { // The user's function // We can assume the return type to be `RenderFnResultWithCause` @@ -147,6 +155,10 @@ pub fn autoserde_impl(input: AutoserdeFn, fn_type: AutoserdeArgs) -> TokenStream } else if fn_type.set_headers { // This will always be synchronous quote! { + // We create a normal version of the function and one to appease the handlers in Wasm (which expect functions that take no arguments, etc.) + #[cfg(target_arch = "wasm32")] + #vis fn #name() {} + #[cfg(not(target_arch = "wasm32"))] #vis fn #name(props: ::std::option::Option<::std::string::String>) -> ::perseus::http::header::HeaderMap { // The user's function // We can assume the return type to be `HeaderMap` @@ -162,6 +174,10 @@ pub fn autoserde_impl(input: AutoserdeFn, fn_type: AutoserdeArgs) -> TokenStream } else if fn_type.amalgamate_states { // This will always be synchronous quote! { + // We create a normal version of the function and one to appease the handlers in Wasm (which expect functions that take no arguments, etc.) + #[cfg(target_arch = "wasm32")] + #vis fn #name() {} + #[cfg(not(target_arch = "wasm32"))] #vis fn #name(states: ::perseus::States) -> ::perseus::RenderFnResultWithCause<::std::option::Option<::std::string::String>> { // The user's function // We can assume the return type to be `RenderFnResultWithCause>` @@ -179,6 +195,10 @@ pub fn autoserde_impl(input: AutoserdeFn, fn_type: AutoserdeArgs) -> TokenStream } } else if fn_type.global_build_state { quote! { + // We create a normal version of the function and one to appease the handlers in Wasm (which expect functions that take no arguments, etc.) + #[cfg(target_arch = "wasm32")] + #vis fn #name() {} + #[cfg(not(target_arch = "wasm32"))] #vis async fn #name() -> ::perseus::RenderFnResult<::std::string::String> { // The user's function // We can assume the return type to be `RenderFnResultWithCause` diff --git a/packages/perseus-macro/src/head.rs b/packages/perseus-macro/src/head.rs index 7a07c0b63f..90c85d52c8 100644 --- a/packages/perseus-macro/src/head.rs +++ b/packages/perseus-macro/src/head.rs @@ -134,6 +134,10 @@ pub fn head_impl(input: HeadFn) -> TokenStream { if arg.is_some() { // There's an argument that will be provided as a `String`, so the wrapper will deserialize it quote! { + // We create a normal version of the function and one to appease the handlers in Wasm (which expect functions that take no arguments, etc.) + #[cfg(target_arch = "wasm32")] + #vis fn #name() {} + #[cfg(not(target_arch = "wasm32"))] #vis fn #name(cx: ::sycamore::prelude::Scope, props: ::perseus::templates::PageProps) -> ::sycamore::prelude::View<::sycamore::prelude::SsrNode> { // The user's function, with Sycamore component annotations and the like preserved // We know this won't be async because Sycamore doesn't allow that @@ -151,6 +155,10 @@ pub fn head_impl(input: HeadFn) -> TokenStream { } else { // There are no arguments quote! { + // We create a normal version of the function and one to appease the handlers in Wasm (which expect functions that take no arguments, etc.) + #[cfg(target_arch = "wasm32")] + #vis fn #name() {} + #[cfg(not(target_arch = "wasm32"))] #vis fn #name(cx: ::sycamore::prelude::Scope, props: ::perseus::templates::PageProps) -> ::sycamore::prelude::View<::sycamore::prelude::SsrNode> { // The user's function, with Sycamore component annotations and the like preserved // We know this won't be async because Sycamore doesn't allow that diff --git a/packages/perseus-macro/src/template_rx.rs b/packages/perseus-macro/src/template_rx.rs index 2963692406..28d1ecaa7e 100644 --- a/packages/perseus-macro/src/template_rx.rs +++ b/packages/perseus-macro/src/template_rx.rs @@ -125,15 +125,15 @@ fn make_mid(ty: &Type) -> Type { /// Gets the code fragment used to support live reloading and HSR. // This is also used by the normal `#[template(...)]` macro pub fn get_live_reload_frag() -> TokenStream { - #[cfg(all(feature = "hsr", debug_assertions))] + #[cfg(all(feature = "hsr", debug_assertions, target_arch = "wasm32"))] let hsr_frag = quote! { ::perseus::state::hsr_freeze(frozen_state).await; }; - #[cfg(not(all(feature = "hsr", debug_assertions)))] + #[cfg(not(all(feature = "hsr", debug_assertions, target_arch = "wasm32")))] #[allow(unused_variables)] let hsr_frag = quote!(); - #[cfg(all(feature = "live-reload", debug_assertions))] + #[cfg(all(feature = "live-reload", debug_assertions, target_arch = "wasm32"))] let live_reload_frag = quote! {{ use ::perseus::state::Freeze; let render_ctx = ::perseus::get_render_ctx!(cx); @@ -157,7 +157,7 @@ pub fn get_live_reload_frag() -> TokenStream { } }); }}; - #[cfg(not(all(feature = "live-reload", debug_assertions)))] + #[cfg(not(all(feature = "live-reload", debug_assertions, target_arch = "wasm32")))] let live_reload_frag = quote!(); live_reload_frag @@ -165,7 +165,7 @@ pub fn get_live_reload_frag() -> TokenStream { /// Gets the code fragment used to support HSR thawing. This MUST be prefixed by a `#[cfg(target_arch = "wasm32")]`. pub fn get_hsr_thaw_frag() -> TokenStream { - #[cfg(all(feature = "hsr", debug_assertions))] + #[cfg(all(feature = "hsr", debug_assertions, target_arch = "wasm32"))] let hsr_thaw_frag = quote! {{ let render_ctx = ::perseus::get_render_ctx!(cx); ::perseus::spawn_local_scoped(cx, async move { @@ -178,7 +178,7 @@ pub fn get_hsr_thaw_frag() -> TokenStream { }); }}; // If HSR is disabled, there'll still be a Wasm-gate, which means we have to give it something to gate (or it'll gate the code after it, which is very bad!) - #[cfg(not(all(feature = "hsr", debug_assertions)))] + #[cfg(not(all(feature = "hsr", debug_assertions, target_arch = "wasm32")))] let hsr_thaw_frag = quote!({}); hsr_thaw_frag @@ -389,7 +389,7 @@ pub fn template_impl(input: TemplateFn) -> TokenStream { #block } - #component_name(cx, ()) + #component_name(cx) } } } else { diff --git a/packages/perseus/Cargo.toml b/packages/perseus/Cargo.toml index f687891385..769c857303 100644 --- a/packages/perseus/Cargo.toml +++ b/packages/perseus/Cargo.toml @@ -18,30 +18,32 @@ sycamore = { version = "=0.8.0-beta.6", features = [ "ssr" ] } sycamore-router = "=0.8.0-beta.6" sycamore-futures = "=0.8.0-beta.6" perseus-macro = { path = "../perseus-macro", version = "0.4.0-beta.1" } -# TODO review feature flags here -web-sys = { version = "0.3", features = [ "Headers", "Navigator", "NodeList", "Request", "RequestInit", "RequestMode", "Response", "ReadableStream", "Window" ] } -wasm-bindgen = { version = "0.2", features = ["serde-serialize"] } -wasm-bindgen-futures = "0.4" serde = { version = "1", features = ["derive"] } serde_json = "1" thiserror = "1" -fmterr = "0.1" -futures = "0.3" -urlencoding = "2.1" -chrono = "0.4" -http = "0.2" async-trait = "0.1" +futures = "0.3" +fmterr = "0.1" fluent-bundle = { version = "0.15", optional = true } unic-langid = { version = "0.9", optional = true } intl-memoizer = { version = "0.5", optional = true } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +regex = "1" tokio = { version = "1", features = [ "fs", "io-util" ] } +fs_extra = { version = "1", optional = true } +http = "0.2" +urlencoding = "2.1" +chrono = "0.4" + +[target.'cfg(target_arch = "wasm32")'.dependencies] rexie = { version = "0.2", optional = true } js-sys = { version = "0.3", optional = true } -fs_extra = { version = "1", optional = true } console_error_panic_hook = { version = "0.1.6", optional = true } - -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] -regex = "1" +# TODO review feature flags here +web-sys = { version = "0.3", features = [ "Headers", "Navigator", "NodeList", "Request", "RequestInit", "RequestMode", "Response", "ReadableStream", "Window" ] } +wasm-bindgen = "0.2" +wasm-bindgen-futures = "0.4" [features] # Live reloading will only take effect in development, and won't impact production @@ -50,11 +52,8 @@ default = [ "live-reload", "hsr", "builder", "client-helpers" ] translator-fluent = ["fluent-bundle", "unic-langid", "intl-memoizer"] # This feature makes tinker-only plugins be registered (this flag is enabled internally in the engine) tinker-plugins = [] -# This feature enables server-side-only features, which should be used on both the server and in the builder -# This prevents leakage of server-side code -server-side = [] -# This feature enables code required only in the builder systems on the server-side -builder = [ "server-side", "fs_extra" ] +# This feature enables code required only in the builder systems on the server-side. +builder = [ "fs_extra" ] # This feature enable support for functions that make using the default engine configuration much easier. dflt-engine = [ "builder" ] # This features enables client-side helpers designed to be run in the browser. diff --git a/packages/perseus/src/error_pages.rs b/packages/perseus/src/error_pages.rs index f7d71a2023..83e42ae7af 100644 --- a/packages/perseus/src/error_pages.rs +++ b/packages/perseus/src/error_pages.rs @@ -1,11 +1,16 @@ use crate::translator::Translator; -use crate::{DomNode, Html, HydrateNode, SsrNode}; +use crate::Html; +#[cfg(target_arch = "wasm32")] +use crate::{DomNode, HydrateNode}; +#[cfg(not(target_arch = "wasm32"))] +use crate::SsrNode; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::rc::Rc; use sycamore::prelude::Scope; use sycamore::view; use sycamore::view::View; +#[cfg(target_arch = "wasm32")] use web_sys::Element; /// The callback to a template the user must provide for error pages. This is passed the status code, the error message, the URL of the @@ -77,6 +82,7 @@ impl ErrorPages { // template_fn(cx, url.to_string(), status, err.to_string(), translator) // } } +#[cfg(target_arch = "wasm32")] impl ErrorPages { /// Renders the appropriate error page to the given DOM container. pub fn render_page( @@ -96,6 +102,7 @@ impl ErrorPages { ); } } +#[cfg(target_arch = "wasm32")] impl ErrorPages { /// Hydrates the appropriate error page to the given DOM container. This is used for when an error page is rendered by the server /// and then needs interactivity. @@ -116,6 +123,7 @@ impl ErrorPages { ); } } +#[cfg(not(target_arch = "wasm32"))] impl ErrorPages { /// Renders the error page to a string. This should then be hydrated on the client-side. No reactive scope is provided to this function, it uses an internal one. pub fn render_to_string( diff --git a/packages/perseus/src/errors.rs b/packages/perseus/src/errors.rs index 2fdfd0d5d3..0ee42e226a 100644 --- a/packages/perseus/src/errors.rs +++ b/packages/perseus/src/errors.rs @@ -1,5 +1,6 @@ #![allow(missing_docs)] +#[cfg(not(target_arch = "wasm32"))] use crate::i18n::TranslationsManagerError; use thiserror::Error; @@ -8,15 +9,16 @@ use thiserror::Error; pub enum Error { #[error(transparent)] ClientError(#[from] ClientError), + #[cfg(not(target_arch = "wasm32"))] #[error(transparent)] ServerError(#[from] ServerError), - #[cfg(feature = "builder")] + #[cfg(all(feature = "builder", not(target_arch = "wasm32")))] #[error(transparent)] EngineError(#[from] EngineError), } /// Errors that can occur in the server-side engine system (responsible for building the app). -#[cfg(feature = "builder")] +#[cfg(all(feature = "builder", not(target_arch = "wasm32")))] #[derive(Error, Debug)] pub enum EngineError { // Many of the build/export processes return these more generic errors @@ -75,6 +77,7 @@ pub enum ClientError { } /// Errors that can occur in the build process or while the server is running. +#[cfg(not(target_arch = "wasm32"))] #[derive(Error, Debug)] pub enum ServerError { #[error("render function '{fn_name}' in template '{template_name}' failed (cause: {cause:?})")] @@ -101,6 +104,7 @@ pub enum ServerError { ServeError(#[from] ServeError), } /// Converts a server error into an HTTP status code. +#[cfg(not(target_arch = "wasm32"))] pub fn err_to_status_code(err: &ServerError) -> u16 { match err { ServerError::ServeError(ServeError::PageNotFound { .. }) => 404, @@ -125,6 +129,7 @@ pub enum GlobalStateError { } /// Errors that can occur while reading from or writing to a mutable or immutable store. +// We do need this on the client to complete some things #[derive(Error, Debug)] pub enum StoreError { #[error("asset '{name}' not found in store")] @@ -164,6 +169,7 @@ pub enum FetchError { } /// Errors that can occur while building an app. +#[cfg(not(target_arch = "wasm32"))] #[derive(Error, Debug)] pub enum BuildError { #[error("template '{template_name}' is missing feature '{feature_name}' (required due to its properties)")] @@ -189,6 +195,7 @@ pub enum BuildError { } /// Errors that can occur while exporting an app to static files. +#[cfg(not(target_arch = "wasm32"))] #[derive(Error, Debug)] pub enum ExportError { #[error("template '{template_name}' can't be exported because it depends on strategies that can't be run at build-time (only build state and build paths can be use din exportable templates)")] @@ -204,6 +211,7 @@ pub enum ServeError { PageNotFound { path: String }, #[error("both build and request states were defined for a template when only one or fewer were expected (should it be able to amalgamate states?)")] BothStatesDefined, + #[cfg(not(target_arch = "wasm32"))] #[error("couldn't parse revalidation datetime (try cleaning all assets)")] BadRevalidate { #[from] diff --git a/packages/perseus/src/export.rs b/packages/perseus/src/export.rs index a1bedaeeaa..665771a292 100644 --- a/packages/perseus/src/export.rs +++ b/packages/perseus/src/export.rs @@ -1,9 +1,9 @@ use crate::errors::*; use crate::i18n::{Locales, TranslationsManager}; -use crate::server::{get_render_cfg, HtmlShell, PageData}; +use crate::server::{get_render_cfg, HtmlShell}; use crate::stores::ImmutableStore; use crate::template::TemplateMap; -use crate::SsrNode; +use crate::{PageData, SsrNode}; use futures::future::{try_join, try_join_all}; /// Gets the static page data. diff --git a/packages/perseus/src/i18n/mod.rs b/packages/perseus/src/i18n/mod.rs index 435a2eb6cc..74288a29cf 100644 --- a/packages/perseus/src/i18n/mod.rs +++ b/packages/perseus/src/i18n/mod.rs @@ -1,9 +1,13 @@ +#[cfg(target_arch = "wasm32")] mod client_translations_manager; +#[cfg(target_arch = "wasm32")] mod locale_detector; mod locales; mod translations_manager; +#[cfg(target_arch = "wasm32")] pub use client_translations_manager::ClientTranslationsManager; +#[cfg(target_arch = "wasm32")] pub use locale_detector::detect_locale; pub use locales::Locales; pub use translations_manager::{ diff --git a/packages/perseus/src/i18n/translations_manager.rs b/packages/perseus/src/i18n/translations_manager.rs index dabab9c14f..681b62d292 100644 --- a/packages/perseus/src/i18n/translations_manager.rs +++ b/packages/perseus/src/i18n/translations_manager.rs @@ -25,10 +25,14 @@ pub enum TranslationsManagerError { } use crate::translator::Translator; -use futures::future::join_all; +#[cfg(not(target_arch = "wasm32"))] use std::collections::HashMap; +#[cfg(not(target_arch = "wasm32"))] use tokio::fs::File; +#[cfg(not(target_arch = "wasm32"))] use tokio::io::AsyncReadExt; +#[cfg(not(target_arch = "wasm32"))] +use futures::future::join_all; /// A trait for systems that manage where to put translations. At simplest, we'll just write them to static files, but they might also /// be stored in a CMS. It is **strongly** advised that any implementations use some form of caching, guided by `FsTranslationsManager`. @@ -53,6 +57,7 @@ pub trait TranslationsManager: std::fmt::Debug + Clone + Send + Sync { } /// A utility function for allowing parallel futures execution. This returns a tuple of the locale and the translations as a JSON string. +#[cfg(not(target_arch = "wasm32"))] async fn get_translations_str_and_cache( locale: String, manager: &FsTranslationsManager, @@ -77,17 +82,23 @@ async fn get_translations_str_and_cache( /// As this is used as the default translations manager by most apps, this also supports not using i18n at all. #[derive(Clone, Debug)] pub struct FsTranslationsManager { + #[cfg(not(target_arch = "wasm32"))] root_path: String, /// A map of locales to cached translations. This decreases the number of file reads significantly for the locales specified. This /// does NOT cache dynamically, and will only cache the requested locales. Translators can be created when necessary from these. + #[cfg(not(target_arch = "wasm32"))] cached_translations: HashMap, /// The locales being cached for easier access. + #[cfg(not(target_arch = "wasm32"))] cached_locales: Vec, /// The file extension expected (e.g. JSON, FTL, etc). This allows for greater flexibility of translation engines (future). + #[cfg(not(target_arch = "wasm32"))] file_ext: String, /// This will be `true` is this translations manager is being used for an app that's not using i18n. + #[cfg(not(target_arch = "wasm32"))] is_dummy: bool, } +#[cfg(not(target_arch = "wasm32"))] impl FsTranslationsManager { /// Creates a new filesystem translations manager. You should provide a path like `/translations` here. You should also provide /// the locales you want to cache, which will have their translations stored in memory. Any supported locales not specified here @@ -116,8 +127,10 @@ impl FsTranslationsManager { manager } } +// `FsTranslationsManager` needs to exist in the browser, but it shouldn't do anything #[async_trait::async_trait] impl TranslationsManager for FsTranslationsManager { + #[cfg(not(target_arch = "wasm32"))] fn new_dummy() -> Self { Self { root_path: String::new(), @@ -127,6 +140,7 @@ impl TranslationsManager for FsTranslationsManager { is_dummy: true, } } + #[cfg(not(target_arch = "wasm32"))] async fn get_translations_str_for_locale( &self, locale: String, @@ -172,6 +186,7 @@ impl TranslationsManager for FsTranslationsManager { } } } + #[cfg(not(target_arch = "wasm32"))] async fn get_translator_for_locale( &self, locale: String, @@ -204,4 +219,22 @@ impl TranslationsManager for FsTranslationsManager { Ok(translator) } + #[cfg(target_arch = "wasm32")] + fn new_dummy() -> Self { + Self {} + } + #[cfg(target_arch = "wasm32")] + async fn get_translations_str_for_locale( + &self, + _locale: String, + ) -> Result { + Ok(String::new()) + } + #[cfg(target_arch = "wasm32")] + async fn get_translator_for_locale( + &self, + _locale: String + ) -> Result { + Ok(crate::internal::i18n::DummyTranslator::new(String::new(), String::new()).unwrap()) + } } diff --git a/packages/perseus/src/init.rs b/packages/perseus/src/init.rs index 801abf3f5b..ad3abeff25 100644 --- a/packages/perseus/src/init.rs +++ b/packages/perseus/src/init.rs @@ -1,16 +1,20 @@ use crate::plugins::PluginAction; -#[cfg(feature = "server-side")] +#[cfg(not(target_arch = "wasm32"))] use crate::server::{get_render_cfg, HtmlShell}; -#[cfg(feature = "server-side")] +#[cfg(not(target_arch = "wasm32"))] use crate::utils::get_path_prefix_server; +use crate::stores::ImmutableStore; use crate::{ - i18n::{FsTranslationsManager, Locales, TranslationsManager}, + i18n::{Locales, TranslationsManager}, state::GlobalStateCreator, - stores::{FsMutableStore, ImmutableStore, MutableStore}, + stores::MutableStore, templates::TemplateMap, ErrorPages, Html, Plugins, SsrNode, Template, }; use futures::Future; +#[cfg(target_arch = "wasm32")] +use std::marker::PhantomData; +#[cfg(not(target_arch = "wasm32"))] use std::pin::Pin; use std::{collections::HashMap, rc::Rc}; use sycamore::prelude::Scope; @@ -48,10 +52,12 @@ impl std::fmt::Debug for ErrorPagesGetter { /// The different types of translations managers that can be stored. This allows us to store dummy translations managers directly, without holding futures. If this stores a full /// translations manager though, it will store it as a `Future`, which is later evaluated. +#[cfg(not(target_arch = "wasm32"))] enum Tm { Dummy(T), Full(Pin>>), } +#[cfg(not(target_arch = "wasm32"))] impl std::fmt::Debug for Tm { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Tm").finish() @@ -91,26 +97,35 @@ pub struct PerseusAppBase { /// The app's error pages. error_pages: ErrorPagesGetter, /// The global state creator for the app. + #[cfg(not(target_arch = "wasm32"))] global_state_creator: GlobalStateCreator, /// The internationalization information for the app. locales: Locales, /// The static aliases the app serves. + #[cfg(not(target_arch = "wasm32"))] static_aliases: HashMap, /// The plugins the app uses. plugins: Rc>, /// The app's immutable store. + #[cfg(not(target_arch = "wasm32"))] immutable_store: ImmutableStore, /// The HTML template that'll be used to render the app into. This must be static, but can be generated or sourced in any way. Note that this MUST /// contain a `
` with the `id` set to whatever the value of `self.root` is. index_view: String, /// The app's mutable store. + #[cfg(not(target_arch = "wasm32"))] mutable_store: M, /// The app's translations manager, expressed as a function yielding a `Future`. This is only ever needed on the server-side, and can't be set up properly on the client-side because /// we can't use futures in the app initialization in Wasm. + #[cfg(not(target_arch = "wasm32"))] translations_manager: Tm, /// The location of the directory to use for static assets that will placed under the URL `/.perseus/static/`. By default, this is the `static/` directory at the root /// of your project. Note that the directory set here will only be used if it exists. + #[cfg(not(target_arch = "wasm32"))] static_dir: String, + // We need this on the client-side to account for the unused type parameters + #[cfg(target_arch = "wasm32")] + _marker: PhantomData<(M, T)> } // The usual implementation in which the default mutable store is used @@ -123,40 +138,60 @@ impl PerseusAppBase { /// /// This is asynchronous because it creates a translations manager in the background. // It makes no sense to implement `Default` on this, so we silence Clippy deliberately + #[cfg(not(target_arch = "wasm32"))] #[allow(clippy::new_without_default)] pub fn new() -> Self { Self::new_with_mutable_store(FsMutableStore::new("./dist/mutable".to_string())) } + /// Creates a new instance of a Perseus app using the default filesystem-based mutable store. For most apps, this will be sufficient. Note that this initializes the translations manager + /// as a dummy, and adds no templates or error pages. + /// + /// In development, you can get away with defining no error pages, but production apps (e.g. those created with `perseus deploy`) MUST set their own custom error pages. + /// + /// This is asynchronous because it creates a translations manager in the background. + // It makes no sense to implement `Default` on this, so we silence Clippy deliberately + #[cfg(target_arch = "wasm32")] + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + Self::new_wasm() + } } // If one's using the default translations manager, caching should be handled automatically for them impl PerseusAppBase { /// The same as `.locales_and_translations_manager()`, but this accepts a literal `Locales` `struct`, which means this can be used when you're using `FsTranslationsManager` but when you don't /// know if your app is using i18n or not (almost always middleware). pub fn locales_lit_and_translations_manager(mut self, locales: Locales) -> Self { + #[cfg(not(target_arch = "wasm32"))] let using_i18n = locales.using_i18n; + self.locales = locales; - // If we're using i18n, do caching stuff - // If not, use a dummy translations manager - if using_i18n { - // By default, all translations are cached - let all_locales: Vec = self - .locales - .get_all() - .iter() + // We only handle the translations manager on the server-side (it doesn't exist on the client-side) + #[cfg(not(target_arch = "wasm32"))] + { + // If we're using i18n, do caching stuff + // If not, use a dummy translations manager + if using_i18n { + // By default, all translations are cached + let all_locales: Vec = self + .locales + .get_all() + .iter() // We have a `&&String` at this point, hence the double clone - .cloned() - .cloned() - .collect(); - let tm_fut = FsTranslationsManager::new( - crate::internal::i18n::DFLT_TRANSLATIONS_DIR.to_string(), - all_locales, - crate::internal::i18n::TRANSLATOR_FILE_EXT.to_string(), - ); - self.translations_manager = Tm::Full(Box::pin(tm_fut)); - } else { - self.translations_manager = Tm::Dummy(FsTranslationsManager::new_dummy()); + .cloned() + .cloned() + .collect(); + let tm_fut = FsTranslationsManager::new( + crate::internal::i18n::DFLT_TRANSLATIONS_DIR.to_string(), + all_locales, + crate::internal::i18n::TRANSLATOR_FILE_EXT.to_string(), + ); + self.translations_manager = Tm::Full(Box::pin(tm_fut)); + } else { + self.translations_manager = Tm::Dummy(FsTranslationsManager::new_dummy()); + } } + self } /// Sets the internationalization information for an app using the default translations manager (`FsTranslationsManager`). This handles locale caching and the like automatically for you, @@ -178,6 +213,7 @@ impl PerseusAppBase { // The base implementation, generic over the mutable store and translations manager impl PerseusAppBase { /// Creates a new instance of a Perseus app, with the default options and a custom mutable store. + #[allow(unused_variables)] pub fn new_with_mutable_store(mutable_store: M) -> Self { Self { root: "root".to_string(), @@ -185,6 +221,7 @@ impl PerseusAppBase { template_getters: TemplateGetters(Vec::new()), // We do offer default error pages, but they'll panic if they're called for production building error_pages: ErrorPagesGetter(Box::new(ErrorPages::default)), + #[cfg(not(target_arch = "wasm32"))] global_state_creator: GlobalStateCreator::default(), // By default, we'll disable i18n (as much as I may want more websites to support more languages...) locales: Locales { @@ -193,16 +230,44 @@ impl PerseusAppBase { using_i18n: false, }, // By default, we won't serve any static content outside the `static/` directory + #[cfg(not(target_arch = "wasm32"))] static_aliases: HashMap::new(), // By default, we won't use any plugins plugins: Rc::new(Plugins::new()), - // This is relative to `.perseus/` + #[cfg(not(target_arch = "wasm32"))] immutable_store: ImmutableStore::new("./dist".to_string()), + #[cfg(not(target_arch = "wasm32"))] mutable_store, + #[cfg(not(target_arch = "wasm32"))] translations_manager: Tm::Dummy(T::new_dummy()), // Many users won't need anything fancy in the index view, so we provide a default index_view: DFLT_INDEX_VIEW.to_string(), + #[cfg(not(target_arch = "wasm32"))] static_dir: "./static".to_string(), + #[cfg(target_arch = "wasm32")] + _marker: PhantomData + } + } + /// Internal function for Wasm initialization. This should never be called by the user! + #[cfg(target_arch = "wasm32")] + fn new_wasm() -> Self { + Self { + root: "root".to_string(), + // We do initialize with no templates, because an app without templates is in theory possible (and it's more convenient to call `.template()` for each one) + template_getters: TemplateGetters(Vec::new()), + // We do offer default error pages, but they'll panic if they're called for production building + error_pages: ErrorPagesGetter(Box::new(ErrorPages::default)), + // By default, we'll disable i18n (as much as I may want more websites to support more languages...) + locales: Locales { + default: "xx-XX".to_string(), + other: Vec::new(), + using_i18n: false, + }, + // By default, we won't use any plugins + plugins: Rc::new(Plugins::new()), + // Many users won't need anything fancy in the index view, so we provide a default + index_view: DFLT_INDEX_VIEW.to_string(), + _marker: PhantomData } } @@ -213,8 +278,11 @@ impl PerseusAppBase { self } /// Sets the location of the directory storing static assets to be hosted under the URL `/.perseus/static/`. + #[allow(unused_variables)] + #[allow(unused_mut)] pub fn static_dir(mut self, val: &str) -> Self { - self.static_dir = val.to_string(); + #[cfg(not(target_arch = "wasm32"))] + { self.static_dir = val.to_string(); } self } /// Sets all the app's templates. This takes a vector of boxed functions that return templates. @@ -233,8 +301,11 @@ impl PerseusAppBase { self } /// Sets the app's global state creator. + #[allow(unused_variables)] + #[allow(unused_mut)] pub fn global_state_creator(mut self, val: GlobalStateCreator) -> Self { - self.global_state_creator = val; + #[cfg(not(target_arch = "wasm32"))] + { self.global_state_creator = val; } self } /// Sets the locales information for the app. The first argument is the default locale (used as a fallback for users with no locale preferences set in their browsers), and @@ -265,8 +336,11 @@ impl PerseusAppBase { /// When your code is run on the server, the `Future` will be `.await`ed on, but on Wasm, it will be discarded and ignored, since the translations manager isn't needed in Wasm. /// /// This is generally intended for use with custom translations manager or specific use-cases with the default (mostly to do with custom caching behavior). + #[allow(unused_variables)] + #[allow(unused_mut)] pub fn translations_manager(mut self, val: impl Future + 'static) -> Self { - self.translations_manager = Tm::Full(Box::pin(val)); + #[cfg(not(target_arch = "wasm32"))] + { self.translations_manager = Tm::Full(Box::pin(val)); } self } /// Explicitly disables internationalization. You shouldn't ever need to call this, as it's the default, but you may want to if you're writing middleware that doesn't support i18n. @@ -277,16 +351,23 @@ impl PerseusAppBase { using_i18n: false, }; // All translations manager must implement this function, which is designed for this exact purpose - self.translations_manager = Tm::Dummy(T::new_dummy()); + #[cfg(not(target_arch = "wasm32"))] + { self.translations_manager = Tm::Dummy(T::new_dummy()); } self } /// Sets all the app's static aliases. This takes a map of URLs (e.g. `/file`) to resource paths, relative to the project directory (e.g. `style.css`). + #[allow(unused_variables)] + #[allow(unused_mut)] pub fn static_aliases(mut self, val: HashMap) -> Self { - self.static_aliases = val; + #[cfg(not(target_arch = "wasm32"))] + { self.static_aliases = val; } self } /// Adds a single static alias (convenience function). This takes a URL path (e.g. `/file`) followed by a path to a resource (which must be within the project directory, e.g. `style.css`). + #[allow(unused_variables)] + #[allow(unused_mut)] pub fn static_alias(mut self, url: &str, resource: &str) -> Self { + #[cfg(not(target_arch = "wasm32"))] // We don't elaborate the alias to an actual filesystem path until the getter self.static_aliases .insert(url.to_string(), resource.to_string()); @@ -299,13 +380,19 @@ impl PerseusAppBase { } /// Sets the mutable store for the app to use, which you would change for some production server environments if you wanted to store build artifacts that can change at runtime in a /// place other than on the filesystem (created for serverless functions specifically). + #[allow(unused_variables)] + #[allow(unused_mut)] pub fn mutable_store(mut self, val: M) -> Self { - self.mutable_store = val; + #[cfg(not(target_arch = "wasm32"))] + { self.mutable_store = val; } self } /// Sets the immutable store for the app to use. You should almost never need to change this unless you're not working with the CLI. + #[allow(unused_variables)] + #[allow(unused_mut)] pub fn immutable_store(mut self, val: ImmutableStore) -> Self { - self.immutable_store = val; + #[cfg(not(target_arch = "wasm32"))] + { self.immutable_store = val; } self } /// Sets the index view as a string. This should be used if you're using an `index.html` file or the like. @@ -345,6 +432,7 @@ impl PerseusAppBase { } /// Gets the directory containing static assets to be hosted under the URL `/.perseus/static/`. // TODO Plugin action for this? + #[cfg(not(target_arch = "wasm32"))] pub fn get_static_dir(&self) -> String { self.static_dir.to_string() } @@ -352,14 +440,14 @@ impl PerseusAppBase { /// /// Note that this automatically adds `` to the start of the HTMl shell produced, which can only be overriden with a control plugin (though you should really never do this /// in Perseus, which targets HTML on the web). - #[cfg(feature = "server-side")] + #[cfg(not(target_arch = "wasm32"))] pub fn get_index_view_str(&self) -> String { // We have to add an HTML document type declaration, otherwise the browser could think it's literally anything! (This shouldn't be a problem, but it could be in 100 years...) format!("\n{}", self.index_view) } /// Gets an HTML shell from an index view string. This is broken out so that it can be executed after the app has been built (which requries getting the translations manager, consuming /// `self`). As inconvenient as this is, it's necessitated, otherwise exporting would try to access the built app before it had actually been built. - #[cfg(feature = "server-side")] + #[cfg(not(target_arch = "wasm32"))] pub async fn get_html_shell( index_view_str: String, root: &str, @@ -477,7 +565,7 @@ impl PerseusAppBase { map } /// Gets the templates in an `Arc`-based `HashMap` for concurrent access. This should only be relevant on the server-side. - #[cfg(feature = "server-side")] + #[cfg(not(target_arch = "wasm32"))] pub fn get_atomic_templates_map(&self) -> crate::templates::ArcTemplateMap { let mut map = HashMap::new(); @@ -521,6 +609,7 @@ impl PerseusAppBase { error_pages } /// Gets the global state creator. This can't be directly modified by plugins because of reactive type complexities. + #[cfg(not(target_arch = "wasm32"))] pub fn get_global_state_creator(&self) -> GlobalStateCreator { self.global_state_creator.clone() } @@ -537,7 +626,7 @@ impl PerseusAppBase { /// Gets the server-side translations manager. Like the mutable store, this can't be modified by plugins due to trait complexities. /// /// This involves evaluating the future stored for the translations manager, and so this consumes `self`. - #[cfg(feature = "server-side")] + #[cfg(not(target_arch = "wasm32"))] pub async fn get_translations_manager(self) -> T { match self.translations_manager { Tm::Dummy(tm) => tm, @@ -545,7 +634,7 @@ impl PerseusAppBase { } } /// Gets the immutable store. - #[cfg(feature = "server-side")] + #[cfg(not(target_arch = "wasm32"))] pub fn get_immutable_store(&self) -> ImmutableStore { let immutable_store = self.immutable_store.clone(); self.plugins @@ -556,7 +645,7 @@ impl PerseusAppBase { .unwrap_or(immutable_store) } /// Gets the mutable store. This can't be modified by plugins due to trait complexities, so plugins should instead expose a function that the user can use to manually set it. - #[cfg(feature = "server-side")] + #[cfg(not(target_arch = "wasm32"))] pub fn get_mutable_store(&self) -> M { self.mutable_store.clone() } @@ -566,7 +655,7 @@ impl PerseusAppBase { } /// Gets the static aliases. This will check all provided resource paths to ensure they don't reference files outside the project directory, due to potential security risks in production /// (we don't want to accidentally serve an arbitrary in a production environment where a path may point to somewhere evil, like an alias to `/etc/passwd`). - #[cfg(feature = "server-side")] + #[cfg(not(target_arch = "wasm32"))] pub fn get_static_aliases(&self) -> HashMap { let mut static_aliases = self.static_aliases.clone(); // This will return a map of plugin name to another map of static aliases that that plugin produced @@ -634,6 +723,9 @@ pub fn PerseusRoot(cx: Scope) -> View { } } +use crate::stores::FsMutableStore; +use crate::i18n::FsTranslationsManager; + /// An alias for the usual kind of Perseus app, which uses the filesystem-based mutable store and translations manager. pub type PerseusApp = PerseusAppBase; /// An alias for a Perseus app that uses a custom mutable store type. diff --git a/packages/perseus/src/lib.rs b/packages/perseus/src/lib.rs index bddfb0c782..9dcb1355da 100644 --- a/packages/perseus/src/lib.rs +++ b/packages/perseus/src/lib.rs @@ -34,31 +34,39 @@ pub mod state; /// Utilities for working with immutable and mutable stores. You can learn more about these in the book. pub mod stores; +#[cfg(not(target_arch = "wasm32"))] mod build; -#[cfg(feature = "client-helpers")] +#[cfg(all(feature = "client-helpers", target_arch = "wasm32"))] mod client; -#[cfg(feature = "builder")] +#[cfg(all(feature = "builder", not(target_arch = "wasm32")))] mod engine; mod error_pages; +#[cfg(not(target_arch = "wasm32"))] mod export; mod i18n; mod init; mod macros; mod router; +#[cfg(not(target_arch = "wasm32"))] mod server; +#[cfg(target_arch = "wasm32")] mod shell; mod template; mod translator; mod utils; +mod page_data; // The rest of this file is devoted to module structuring // Re-exports +#[cfg(not(target_arch = "wasm32"))] pub use http; +#[cfg(not(target_arch = "wasm32"))] pub use http::Request as HttpRequest; pub use sycamore_futures::spawn_local_scoped; /// All HTTP requests use empty bodies for simplicity of passing them around. They'll never need payloads (value in path requested). +#[cfg(not(target_arch = "wasm32"))] pub type Request = HttpRequest<()>; -#[cfg(feature = "client-helpers")] +#[cfg(all(feature = "client-helpers", target_arch = "wasm32"))] pub use client::{run_client, ClientReturn}; pub use perseus_macro::{autoserde, head, main, make_rx, template, template_rx, test}; pub use sycamore::prelude::{DomNode, Html, HydrateNode, SsrNode}; @@ -68,10 +76,15 @@ pub use sycamore_router::{navigate, navigate_replace, Route}; // TODO Should we // Items that should be available at the root (this should be nearly everything used in a typical Perseus app) pub use crate::error_pages::ErrorPages; +pub use crate::page_data::PageData; pub use crate::errors::{ErrorCause, GenericErrorWithCause}; pub use crate::plugins::{Plugin, PluginAction, Plugins}; +#[cfg(target_arch = "wasm32")] pub use crate::shell::checkpoint; -pub use crate::template::{HeadFn, RenderFnResult, RenderFnResultWithCause, States, Template}; +#[cfg(not(target_arch = "wasm32"))] +pub use crate::template::{States, HeadFn}; +pub use crate::template::{RenderFnResult, RenderFnResultWithCause, Template}; +#[cfg(not(target_arch = "wasm32"))] pub use crate::utils::{cache_fallible_res, cache_res}; // Everything in the `init.rs` file should be available at the top-level for convenience pub use crate::init::*; @@ -82,7 +95,7 @@ pub mod templates { pub use crate::template::*; } /// Utilities for building an app. -#[cfg(feature = "builder")] +#[cfg(all(feature = "builder", not(target_arch = "wasm32")))] pub mod builder { pub use crate::engine::*; } @@ -98,6 +111,7 @@ pub mod internal { } /// Internal utilities for working with the serving process. These will be useful for building integrations for hosting Perseus /// on different platforms. + #[cfg(not(target_arch = "wasm32"))] pub mod serve { pub use crate::server::*; } @@ -110,19 +124,26 @@ pub mod internal { pub use crate::error_pages::*; } /// Internal utilities for working with the app shell. + #[cfg(target_arch = "wasm32")] pub mod shell { pub use crate::shell::*; } /// Internal utilities for building apps at a very low level. + #[cfg(not(target_arch = "wasm32"))] pub mod build { pub use crate::build::*; } /// Internal utilities for exporting apps at a very low level. + #[cfg(not(target_arch = "wasm32"))] pub mod export { pub use crate::export::*; } - pub use crate::utils::{get_path_prefix_client, get_path_prefix_server}; + #[cfg(not(target_arch = "wasm32"))] + pub use crate::utils::get_path_prefix_server; + #[cfg(target_arch = "wasm32")] + pub use crate::utils::get_path_prefix_client; /// Internal utilities for logging. These are just re-exports so that users don't have to have `web_sys` and `wasm_bindgen` to use `web_log!`. + #[cfg(target_arch = "wasm32")] pub mod log { pub use wasm_bindgen::JsValue; pub use web_sys::console::log_1 as log_js_value; diff --git a/packages/perseus/src/server/page_data.rs b/packages/perseus/src/page_data.rs similarity index 100% rename from packages/perseus/src/server/page_data.rs rename to packages/perseus/src/page_data.rs diff --git a/packages/perseus/src/plugins/functional.rs b/packages/perseus/src/plugins/functional.rs index 3879da94c1..4768ce008e 100644 --- a/packages/perseus/src/plugins/functional.rs +++ b/packages/perseus/src/plugins/functional.rs @@ -1,8 +1,10 @@ +#[cfg(not(target_arch = "wasm32"))] use crate::errors::EngineError; use crate::plugins::*; use crate::Html; use std::any::Any; use std::collections::HashMap; +#[cfg(not(target_arch = "wasm32"))] use std::rc::Rc; /// An action which can be taken by many plugins. When run, a functional action will return a map of plugin names to their return types. @@ -71,13 +73,13 @@ pub struct FunctionalPluginActions { /// Actions pertaining to the modification of settings created with `PerseusApp`. pub settings_actions: FunctionalPluginSettingsActions, /// Actions pertaining to the build process. - #[cfg(feature = "builder")] + #[cfg(all(feature = "builder", not(target_arch = "wasm32")))] pub build_actions: FunctionalPluginBuildActions, /// Actions pertaining to the export process. - #[cfg(feature = "builder")] + #[cfg(all(feature = "builder", not(target_arch = "wasm32")))] pub export_actions: FunctionalPluginExportActions, /// Actions pertaining to the process of exporting an error page. - #[cfg(feature = "builder")] + #[cfg(all(feature = "builder", not(target_arch = "wasm32")))] pub export_error_page_actions: FunctionalPluginExportErrorPageActions, /// Actions pertaining to the server. pub server_actions: FunctionalPluginServerActions, @@ -89,11 +91,11 @@ impl Default for FunctionalPluginActions { Self { tinker: FunctionalPluginAction::default(), settings_actions: FunctionalPluginSettingsActions::::default(), - #[cfg(feature = "builder")] + #[cfg(all(feature = "builder", not(target_arch = "wasm32")))] build_actions: FunctionalPluginBuildActions::default(), - #[cfg(feature = "builder")] + #[cfg(all(feature = "builder", not(target_arch = "wasm32")))] export_actions: FunctionalPluginExportActions::default(), - #[cfg(feature = "builder")] + #[cfg(all(feature = "builder", not(target_arch = "wasm32")))] export_error_page_actions: FunctionalPluginExportErrorPageActions::default(), server_actions: FunctionalPluginServerActions::default(), client_actions: FunctionalPluginClientActions::default(), @@ -151,7 +153,7 @@ pub struct FunctionalPluginHtmlShellActions { /// Functional actions that pertain to the build process. Note that these actions are not available for the build /// stage of the export process, and those should be registered separately. -#[cfg(feature = "builder")] +#[cfg(all(feature = "builder", not(target_arch = "wasm32")))] #[derive(Default, Debug)] pub struct FunctionalPluginBuildActions { /// Runs before the build process. @@ -164,7 +166,7 @@ pub struct FunctionalPluginBuildActions { pub after_failed_global_state_creation: FunctionalPluginAction, ()>, } /// Functional actions that pertain to the export process. -#[cfg(feature = "builder")] +#[cfg(all(feature = "builder", not(target_arch = "wasm32")))] #[derive(Default, Debug)] pub struct FunctionalPluginExportActions { /// Runs before the export process. @@ -188,7 +190,7 @@ pub struct FunctionalPluginExportActions { pub after_failed_global_state_creation: FunctionalPluginAction, ()>, } /// Functional actions that pertain to the process of exporting an error page. -#[cfg(feature = "builder")] +#[cfg(all(feature = "builder", not(target_arch = "wasm32")))] #[derive(Default, Debug)] pub struct FunctionalPluginExportErrorPageActions { /// Runs before the process of exporting an error page, providing the HTTP status code to be exported and the output filename (relative to the root of the project, not to `.perseus/`). diff --git a/packages/perseus/src/router/mod.rs b/packages/perseus/src/router/mod.rs index ba5d63f907..7c39ebfb1c 100644 --- a/packages/perseus/src/router/mod.rs +++ b/packages/perseus/src/router/mod.rs @@ -1,6 +1,7 @@ mod app_route; // This just exposes a macro mod match_route; mod route_verdict; +#[cfg(target_arch = "wasm32")] mod router_component; mod router_state; @@ -9,5 +10,6 @@ pub use match_route::{ get_template_for_path, get_template_for_path_atomic, match_route, match_route_atomic, }; pub use route_verdict::{RouteInfo, RouteInfoAtomic, RouteVerdict, RouteVerdictAtomic}; +#[cfg(target_arch = "wasm32")] pub use router_component::*; // TODO pub use router_state::{RouterLoadState, RouterState}; diff --git a/packages/perseus/src/server/html_shell.rs b/packages/perseus/src/server/html_shell.rs index 0524fb608d..14d58142d6 100644 --- a/packages/perseus/src/server/html_shell.rs +++ b/packages/perseus/src/server/html_shell.rs @@ -1,5 +1,5 @@ use crate::error_pages::ErrorPageData; -use crate::server::PageData; +use crate::PageData; use std::collections::HashMap; use std::{env, fmt}; diff --git a/packages/perseus/src/server/mod.rs b/packages/perseus/src/server/mod.rs index 4112501899..3746ac2849 100644 --- a/packages/perseus/src/server/mod.rs +++ b/packages/perseus/src/server/mod.rs @@ -5,14 +5,12 @@ mod build_error_page; mod get_render_cfg; mod html_shell; mod options; -mod page_data; mod render; pub use build_error_page::build_error_page; pub use get_render_cfg::get_render_cfg; pub use html_shell::HtmlShell; pub use options::{ServerOptions, ServerProps}; -pub use page_data::PageData; pub use render::{get_page, get_page_for_template, GetPageProps}; /// Removes empty elements from a path, which is important due to double slashes. This returns a vector of the path's components; diff --git a/packages/perseus/src/server/render.rs b/packages/perseus/src/server/render.rs index 05a62aaa04..62a5abb7c3 100644 --- a/packages/perseus/src/server/render.rs +++ b/packages/perseus/src/server/render.rs @@ -1,4 +1,4 @@ -use super::PageData; +use crate::PageData; use crate::errors::*; use crate::i18n::TranslationsManager; use crate::stores::{ImmutableStore, MutableStore}; diff --git a/packages/perseus/src/shell.rs b/packages/perseus/src/shell.rs index 43fab27663..180d1b7c07 100644 --- a/packages/perseus/src/shell.rs +++ b/packages/perseus/src/shell.rs @@ -2,7 +2,7 @@ use crate::error_pages::ErrorPageData; use crate::errors::*; use crate::i18n::ClientTranslationsManager; use crate::router::{RouteVerdict, RouterLoadState, RouterState}; -use crate::server::PageData; +use crate::PageData; use crate::template::{PageProps, Template, TemplateNodeType}; use crate::utils::get_path_prefix_client; use crate::ErrorPages; diff --git a/packages/perseus/src/state/mod.rs b/packages/perseus/src/state/mod.rs index 4d95118cbe..64b0b8b895 100644 --- a/packages/perseus/src/state/mod.rs +++ b/packages/perseus/src/state/mod.rs @@ -8,20 +8,20 @@ pub use global_state::{GlobalState, GlobalStateCreator}; pub use page_state_store::PageStateStore; pub use rx_state::{AnyFreeze, Freeze, MakeRx, MakeUnrx}; -#[cfg(feature = "idb-freezing")] +#[cfg(all(feature = "idb-freezing", target_arch = "wasm32"))] mod freeze_idb; -#[cfg(feature = "idb-freezing")] +#[cfg(all(feature = "idb-freezing", target_arch = "wasm32"))] pub use freeze_idb::*; // TODO Be specific here // We'll allow live reloading (of which HSR is a subset) if it's feature-enabled and we're in development mode -#[cfg(all(feature = "live-reload", debug_assertions))] +#[cfg(all(feature = "live-reload", debug_assertions, target_arch = "wasm32"))] mod live_reload; -#[cfg(all(feature = "live-reload", debug_assertions))] +#[cfg(all(feature = "live-reload", debug_assertions, target_arch = "wasm32"))] pub(crate) use live_reload::connect_to_reload_server; -#[cfg(all(feature = "live-reload", debug_assertions))] +#[cfg(all(feature = "live-reload", debug_assertions, target_arch = "wasm32"))] pub use live_reload::force_reload; -#[cfg(all(feature = "hsr", debug_assertions))] +#[cfg(all(feature = "hsr", debug_assertions, target_arch = "wasm32"))] mod hsr; -#[cfg(all(feature = "hsr", debug_assertions))] +#[cfg(all(feature = "hsr", debug_assertions, target_arch = "wasm32"))] pub use hsr::*; // TODO diff --git a/packages/perseus/src/stores/immutable.rs b/packages/perseus/src/stores/immutable.rs index 83cc969c66..18aa282f6a 100644 --- a/packages/perseus/src/stores/immutable.rs +++ b/packages/perseus/src/stores/immutable.rs @@ -1,4 +1,7 @@ + +#[cfg(not(target_arch = "wasm32"))] use crate::errors::*; +#[cfg(not(target_arch = "wasm32"))] use tokio::{ fs::{create_dir_all, File}, io::{AsyncReadExt, AsyncWriteExt}, @@ -11,10 +14,12 @@ use tokio::{ /// Note: the `.write()` methods on this implementation will create any missing parent directories automatically. #[derive(Clone, Debug)] pub struct ImmutableStore { + #[cfg(not(target_arch = "wasm32"))] root_path: String, } impl ImmutableStore { /// Creates a new immutable store. You should provide a path like `dist` here. Note that any trailing slashes will be automatically stripped. + #[cfg(not(target_arch = "wasm32"))] pub fn new(root_path: String) -> Self { let root_path = root_path .strip_prefix('/') @@ -25,10 +30,12 @@ impl ImmutableStore { /// Gets the filesystem path used for this immutable store. /// /// This is designed to be used in particular by the engine to work out where to put static assets and the like when exporting. + #[cfg(not(target_arch = "wasm32"))] pub fn get_path(&self) -> &str { &self.root_path } /// Reads the given asset from the filesystem asynchronously. + #[cfg(not(target_arch = "wasm32"))] pub async fn read(&self, name: &str) -> Result { let asset_path = format!("{}/{}", self.root_path, name); let mut file = File::open(&asset_path) @@ -61,6 +68,7 @@ impl ImmutableStore { } /// Writes the given asset to the filesystem asynchronously. This must only be used at build-time, and must not be changed /// afterward. + #[cfg(not(target_arch = "wasm32"))] pub async fn write(&self, name: &str, content: &str) -> Result<(), StoreError> { let asset_path = format!("{}/{}", self.root_path, name); let mut dir_tree: Vec<&str> = asset_path.split('/').collect(); diff --git a/packages/perseus/src/stores/mutable.rs b/packages/perseus/src/stores/mutable.rs index cfa3fb4c43..562a610f9f 100644 --- a/packages/perseus/src/stores/mutable.rs +++ b/packages/perseus/src/stores/mutable.rs @@ -1,4 +1,5 @@ use crate::errors::*; +#[cfg(not(target_arch = "wasm32"))] use tokio::{ fs::{create_dir_all, File}, io::{AsyncReadExt, AsyncWriteExt}, @@ -21,17 +22,21 @@ pub trait MutableStore: std::fmt::Debug + Clone + Send + Sync { /// Note: the `.write()` methods on this implementation will create any missing parent directories automatically. #[derive(Clone, Debug)] pub struct FsMutableStore { + #[cfg(not(target_arch = "wasm32"))] root_path: String, } +#[cfg(not(target_arch = "wasm32"))] impl FsMutableStore { /// Creates a new filesystem configuration manager. You should provide a path like `dist/mutable` here. Make sure that this is /// not the same path as the immutable store, as this will cause potentially problematic overlap between the two systems. + #[cfg(not(target_arch = "wasm32"))] pub fn new(root_path: String) -> Self { Self { root_path } } } #[async_trait::async_trait] impl MutableStore for FsMutableStore { + #[cfg(not(target_arch = "wasm32"))] async fn read(&self, name: &str) -> Result { let asset_path = format!("{}/{}", self.root_path, name); let mut file = File::open(&asset_path) @@ -63,6 +68,7 @@ impl MutableStore for FsMutableStore { } } // This creates a directory structure as necessary + #[cfg(not(target_arch = "wasm32"))] async fn write(&self, name: &str, content: &str) -> Result<(), StoreError> { let asset_path = format!("{}/{}", self.root_path, name); let mut dir_tree: Vec<&str> = asset_path.split('/').collect(); @@ -98,4 +104,12 @@ impl MutableStore for FsMutableStore { Ok(()) } + #[cfg(target_arch = "wasm32")] + async fn read(&self, _name: &str) -> Result { + Ok(String::new()) + } + #[cfg(target_arch = "wasm32")] + async fn write(&self, _name: &str, _content: &str) -> Result<(), StoreError> { + Ok(()) + } } diff --git a/packages/perseus/src/template/core.rs b/packages/perseus/src/template/core.rs index 41be27d721..8609157a12 100644 --- a/packages/perseus/src/template/core.rs +++ b/packages/perseus/src/template/core.rs @@ -1,17 +1,28 @@ // This file contains logic to define how templates are rendered -use super::{default_headers, PageProps, RenderCtx, States}; +use super::PageProps; +#[cfg(not(target_arch = "wasm32"))] +use super::{RenderCtx, States}; +#[cfg(not(target_arch = "wasm32"))] +use super::default_headers; use crate::errors::*; +#[cfg(not(target_arch = "wasm32"))] use crate::make_async_trait; use crate::translator::Translator; use crate::utils::provide_context_signal_replace; +#[cfg(not(target_arch = "wasm32"))] use crate::utils::AsyncFnReturn; use crate::Html; +#[cfg(not(target_arch = "wasm32"))] use crate::Request; +#[cfg(not(target_arch = "wasm32"))] use crate::SsrNode; +#[cfg(not(target_arch = "wasm32"))] use futures::Future; +#[cfg(not(target_arch = "wasm32"))] use http::header::HeaderMap; use sycamore::prelude::{Scope, View}; +#[cfg(not(target_arch = "wasm32"))] use sycamore::utils::hydrate::with_no_hydration_context; /// A generic error type that can be adapted for any errors the user may want to return from a render function. `.into()` can be used @@ -27,14 +38,17 @@ pub type RenderFnResult = std::result::Result = std::result::Result; // A series of asynchronous closure traits that prevent the user from having to pin their functions +#[cfg(not(target_arch = "wasm32"))] make_async_trait!(GetBuildPathsFnType, RenderFnResult>); // The build state strategy needs an error cause if it's invoked from incremental +#[cfg(not(target_arch = "wasm32"))] make_async_trait!( GetBuildStateFnType, RenderFnResultWithCause, path: String, locale: String ); +#[cfg(not(target_arch = "wasm32"))] make_async_trait!( GetRequestStateFnType, RenderFnResultWithCause, @@ -42,6 +56,7 @@ make_async_trait!( locale: String, req: Request ); +#[cfg(not(target_arch = "wasm32"))] make_async_trait!(ShouldRevalidateFnType, RenderFnResultWithCause); // A series of closure types that should not be typed out more than once @@ -52,18 +67,25 @@ pub type TemplateFn = Box View + Send + Sync>; /// A type alias for the function that modifies the document head. This is just a template function that will always be server-side /// rendered in function (it may be rendered on the client, but it will always be used to create an HTML string, rather than a reactive /// template). +#[cfg(not(target_arch = "wasm32"))] pub type HeadFn = TemplateFn; +#[cfg(not(target_arch = "wasm32"))] /// The type of functions that modify HTTP response headers. pub type SetHeadersFn = Box) -> HeaderMap + Send + Sync>; /// The type of functions that get build paths. +#[cfg(not(target_arch = "wasm32"))] pub type GetBuildPathsFn = Box; /// The type of functions that get build state. +#[cfg(not(target_arch = "wasm32"))] pub type GetBuildStateFn = Box; /// The type of functions that get request state. +#[cfg(not(target_arch = "wasm32"))] pub type GetRequestStateFn = Box; /// The type of functions that check if a template sghould revalidate. +#[cfg(not(target_arch = "wasm32"))] pub type ShouldRevalidateFn = Box; /// The type of functions that amalgamate build and request states. +#[cfg(not(target_arch = "wasm32"))] pub type AmalgamateStatesFn = Box RenderFnResultWithCause> + Send + Sync>; @@ -82,37 +104,46 @@ pub struct Template { /// A function that will be used to populate the document's `` with metadata such as the title. This will be passed state in /// the same way as `template`, but will always be rendered to a string, whcih will then be interpolated directly into the ``, /// so reactivity here will not work! + #[cfg(not(target_arch = "wasm32"))] head: TemplateFn, /// A function to be run when the server returns an HTTP response. This should return headers for said response, given the template's /// state. The most common use-case of this is to add cache control that respects revalidation. This will only be run on successful /// responses, and does have the power to override existing headers. By default, this will create sensible cache control headers. + #[cfg(not(target_arch = "wasm32"))] set_headers: SetHeadersFn, /// A function that gets the paths to render for at built-time. This is equivalent to `get_static_paths` in NextJS. If /// `incremental_generation` is `true`, more paths can be rendered at request time on top of these. + #[cfg(not(target_arch = "wasm32"))] get_build_paths: Option, /// Defines whether or not any new paths that match this template will be prerendered and cached in production. This allows you to /// have potentially billions of templates and retain a super-fast build process. The first user will have an ever-so-slightly slower /// experience, and everyone else gets the beneftis afterwards. This requires `get_build_paths`. Note that the template root will NOT /// be rendered on demand, and must be explicitly defined if it's wanted. It can uuse a different template. + #[cfg(not(target_arch = "wasm32"))] incremental_generation: bool, /// A function that gets the initial state to use to prerender the template at build time. This will be passed the path of the template, and /// will be run for any sub-paths. This is equivalent to `get_static_props` in NextJS. + #[cfg(not(target_arch = "wasm32"))] get_build_state: Option, /// A function that will run on every request to generate a state for that request. This allows server-side-rendering. This is equivalent /// to `get_server_side_props` in NextJS. This can be used with `get_build_state`, though custom amalgamation logic must be provided. + #[cfg(not(target_arch = "wasm32"))] get_request_state: Option, /// A function to be run on every request to check if a template prerendered at build-time should be prerendered again. This is equivalent /// to revalidation after a time in NextJS, with the improvement of custom logic. If used with `revalidate_after`, this function will /// only be run after that time period. This function will not be parsed anything specific to the request that invoked it. + #[cfg(not(target_arch = "wasm32"))] should_revalidate: Option, /// A length of time after which to prerender the template again. This is equivalent to revalidating in NextJS. This should specify a /// string interval to revalidate after. That will be converted into a datetime to wait for, which will be updated after every revalidation. /// Note that, if this is used with incremental generation, the counter will only start after the first render (meaning if you expect /// a weekly re-rendering cycle for all pages, they'd likely all be out of sync, you'd need to manually implement that with /// `should_revalidate`). + #[cfg(not(target_arch = "wasm32"))] revalidate_after: Option, /// Custom logic to amalgamate potentially different states generated at build and request time. This is only necessary if your template /// uses both `build_state` and `request_state`. If not specified and both are generated, request state will be prioritized. + #[cfg(not(target_arch = "wasm32"))] amalgamate_states: Option, } impl std::fmt::Debug for Template { @@ -122,34 +153,7 @@ impl std::fmt::Debug for Template { .field("template", &"TemplateFn") .field("head", &"HeadFn") .field("set_headers", &"SetHeadersFn") - .field( - "get_build_paths", - &self.get_build_paths.as_ref().map(|_| "GetBuildPathsFn"), - ) - .field( - "get_build_state", - &self.get_build_state.as_ref().map(|_| "GetBuildStateFn"), - ) - .field( - "get_request_state", - &self.get_request_state.as_ref().map(|_| "GetRequestStateFn"), - ) - .field( - "should_revalidate", - &self - .should_revalidate - .as_ref() - .map(|_| "ShouldRevalidateFn"), - ) - .field("revalidate_after", &self.revalidate_after) - .field( - "amalgamate_states", - &self - .amalgamate_states - .as_ref() - .map(|_| "AmalgamateStatesFn"), - ) - .field("incremental_generation", &self.incremental_generation) + // TODO Server-specific stuff .finish() } } @@ -160,21 +164,31 @@ impl Template { path: path.to_string(), template: Box::new(|cx, _| sycamore::view! { cx, }), // Unlike `template`, this may not be set at all (especially in very simple apps) + #[cfg(not(target_arch = "wasm32"))] head: Box::new(|cx, _| sycamore::view! { cx, }), // We create sensible header defaults here + #[cfg(not(target_arch = "wasm32"))] set_headers: Box::new(|_| default_headers()), + #[cfg(not(target_arch = "wasm32"))] get_build_paths: None, + #[cfg(not(target_arch = "wasm32"))] incremental_generation: false, + #[cfg(not(target_arch = "wasm32"))] get_build_state: None, + #[cfg(not(target_arch = "wasm32"))] get_request_state: None, + #[cfg(not(target_arch = "wasm32"))] should_revalidate: None, + #[cfg(not(target_arch = "wasm32"))] revalidate_after: None, + #[cfg(not(target_arch = "wasm32"))] amalgamate_states: None, } } // Render executors /// Executes the user-given function that renders the template on the client-side ONLY. This takes in an extsing global state. + #[cfg(target_arch = "wasm32")] #[allow(clippy::too_many_arguments)] pub fn render_for_template_client<'a>( &self, @@ -189,6 +203,7 @@ impl Template { (self.template)(cx, props) } /// Executes the user-given function that renders the template on the server-side ONLY. This automatically initializes an isolated global state. + #[cfg(not(target_arch = "wasm32"))] pub fn render_for_template_server<'a>( &self, props: PageProps, @@ -205,6 +220,7 @@ impl Template { } /// Executes the user-given function that renders the document ``, returning a string to be interpolated manually. /// Reactivity in this function will not take effect due to this string rendering. Note that this function will provide a translator context. + #[cfg(not(target_arch = "wasm32"))] pub fn render_head_str(&self, props: PageProps, translator: &Translator) -> String { sycamore::render_to_string(|cx| { // The context we have here has no context elements set on it, so we set all the defaults (job of the router component on the client-side) @@ -217,6 +233,7 @@ impl Template { }) } /// Gets the list of templates that should be prerendered for at build-time. + #[cfg(not(target_arch = "wasm32"))] pub async fn get_build_paths(&self) -> Result, ServerError> { if let Some(get_build_paths) = &self.get_build_paths { let res = get_build_paths.call().await; @@ -240,6 +257,7 @@ impl Template { /// Gets the initial state for a template. This needs to be passed the full path of the template, which may be one of those generated by /// `.get_build_paths()`. This also needs the locale being rendered to so that more compelx applications like custom documentation /// systems can be enabled. + #[cfg(not(target_arch = "wasm32"))] pub async fn get_build_state( &self, path: String, @@ -267,6 +285,7 @@ impl Template { /// Gets the request-time state for a template. This is equivalent to SSR, and will not be performed at build-time. Unlike /// `.get_build_paths()` though, this will be passed information about the request that triggered the render. Errors here can be caused /// by either the server or the client, so the user must specify an [`ErrorCause`]. This is also passed the locale being rendered to. + #[cfg(not(target_arch = "wasm32"))] pub async fn get_request_state( &self, path: String, @@ -294,6 +313,7 @@ impl Template { } /// Amalagmates given request and build states. Errors here can be caused by either the server or the client, so the user must specify /// an [`ErrorCause`]. + #[cfg(not(target_arch = "wasm32"))] pub fn amalgamate_states(&self, states: States) -> Result, ServerError> { if let Some(amalgamate_states) = &self.amalgamate_states { let res = amalgamate_states(states); @@ -317,6 +337,7 @@ impl Template { /// Checks, by the user's custom logic, if this template should revalidate. This function isn't presently parsed anything, but has /// network access etc., and can really do whatever it likes. Errors here can be caused by either the server or the client, so the /// user must specify an [`ErrorCause`]. + #[cfg(not(target_arch = "wasm32"))] pub async fn should_revalidate(&self) -> Result { if let Some(should_revalidate) = &self.should_revalidate { let res = should_revalidate.call().await; @@ -339,6 +360,7 @@ impl Template { } /// Gets the template's headers for the given state. These will be inserted into any successful HTTP responses for this template, /// and they have the power to override. + #[cfg(not(target_arch = "wasm32"))] pub fn get_headers(&self, state: Option) -> HeaderMap { (self.set_headers)(state) } @@ -350,45 +372,55 @@ impl Template { self.path.clone() } /// Gets the interval after which the template will next revalidate. + #[cfg(not(target_arch = "wasm32"))] pub fn get_revalidate_interval(&self) -> Option { self.revalidate_after.clone() } // Render characteristic checkers /// Checks if this template can revalidate existing prerendered templates. + #[cfg(not(target_arch = "wasm32"))] pub fn revalidates(&self) -> bool { self.should_revalidate.is_some() || self.revalidate_after.is_some() } /// Checks if this template can revalidate existing prerendered templates after a given time. + #[cfg(not(target_arch = "wasm32"))] pub fn revalidates_with_time(&self) -> bool { self.revalidate_after.is_some() } /// Checks if this template can revalidate existing prerendered templates based on some given logic. + #[cfg(not(target_arch = "wasm32"))] pub fn revalidates_with_logic(&self) -> bool { self.should_revalidate.is_some() } /// Checks if this template can render more templates beyond those paths it explicitly defines. + #[cfg(not(target_arch = "wasm32"))] pub fn uses_incremental(&self) -> bool { self.incremental_generation } /// Checks if this template is a template to generate paths beneath it. + #[cfg(not(target_arch = "wasm32"))] pub fn uses_build_paths(&self) -> bool { self.get_build_paths.is_some() } /// Checks if this template needs to do anything on requests for it. + #[cfg(not(target_arch = "wasm32"))] pub fn uses_request_state(&self) -> bool { self.get_request_state.is_some() } /// Checks if this template needs to do anything at build time. + #[cfg(not(target_arch = "wasm32"))] pub fn uses_build_state(&self) -> bool { self.get_build_state.is_some() } /// Checks if this template has custom logic to amalgamate build and reqquest states if both are generated. + #[cfg(not(target_arch = "wasm32"))] pub fn can_amalgamate_states(&self) -> bool { self.amalgamate_states.is_some() } /// Checks if this template defines no rendering logic whatsoever. Such templates will be rendered using SSG. Basic templates can /// still modify headers. + #[cfg(not(target_arch = "wasm32"))] pub fn is_basic(&self) -> bool { !self.uses_build_paths() && !self.uses_build_state() @@ -398,7 +430,9 @@ impl Template { } // Builder setters - // These will only be enabled under the `server-side` feature to prevent server-side code leaking into the Wasm binary (only the template setter is needed) + // The server-only ones have a different version for Wasm that takes in an empty function (this means we don't have to bring in function types, and therefore we + // can avoid bringing in the whole `http` module --- a very significant saving!) + // The macros handle the creation of empty functions to make user's lives easier /// Sets the template rendering function to use. pub fn template( mut self, @@ -407,117 +441,136 @@ impl Template { self.template = Box::new(val); self } + /// Sets the document head rendering function to use. - #[allow(unused_mut)] - #[allow(unused_variables)] + #[cfg(not(target_arch = "wasm32"))] pub fn head( mut self, val: impl Fn(Scope, PageProps) -> View + Send + Sync + 'static, ) -> Template { // Headers are always prerendered on the server-side - #[cfg(feature = "server-side")] - { - self.head = Box::new(val); - } + self.head = Box::new(val); + self + } + /// Sets the document head rendering function to use. + #[cfg(target_arch = "wasm32")] + pub fn head(self, _val: impl Fn() + 'static) -> Template { self } + /// Sets the function to set headers. This will override Perseus' inbuilt header defaults. - #[allow(unused_mut)] - #[allow(unused_variables)] + #[cfg(not(target_arch = "wasm32"))] pub fn set_headers_fn( mut self, val: impl Fn(Option) -> HeaderMap + Send + Sync + 'static, ) -> Template { - #[cfg(feature = "server-side")] - { - self.set_headers = Box::new(val); - } + self.set_headers = Box::new(val); + self + } + /// Sets the function to set headers. This will override Perseus' inbuilt header defaults. + #[cfg(target_arch = "wasm32")] + pub fn set_headers_fn(self, _val: impl Fn() + 'static) -> Template { self } + /// Enables the *build paths* strategy with the given function. - #[allow(unused_mut)] - #[allow(unused_variables)] + #[cfg(not(target_arch = "wasm32"))] pub fn build_paths_fn( mut self, val: impl GetBuildPathsFnType + Send + Sync + 'static, ) -> Template { - #[cfg(feature = "server-side")] - { - self.get_build_paths = Some(Box::new(val)); - } + self.get_build_paths = Some(Box::new(val)); self } + /// Enables the *build paths* strategy with the given function. + #[cfg(target_arch = "wasm32")] + pub fn build_paths_fn(self, _val: impl Fn() + 'static) -> Template { + self + } + /// Enables the *incremental generation* strategy. - #[allow(unused_mut)] - #[allow(unused_variables)] + #[cfg(not(target_arch = "wasm32"))] pub fn incremental_generation(mut self) -> Template { - #[cfg(feature = "server-side")] - { - self.incremental_generation = true; - } + self.incremental_generation = true; self } + /// Enables the *incremental generation* strategy. + #[cfg(target_arch = "wasm32")] + pub fn incremental_generation(self) -> Template { + self + } + /// Enables the *build state* strategy with the given function. - #[allow(unused_mut)] - #[allow(unused_variables)] + #[cfg(not(target_arch = "wasm32"))] pub fn build_state_fn( mut self, val: impl GetBuildStateFnType + Send + Sync + 'static, ) -> Template { - #[cfg(feature = "server-side")] - { - self.get_build_state = Some(Box::new(val)); - } + self.get_build_state = Some(Box::new(val)); + self + } + /// Enables the *build state* strategy with the given function. + #[cfg(target_arch = "wasm32")] + pub fn build_state_fn(self, _val: impl Fn() + 'static) -> Template { self } + /// Enables the *request state* strategy with the given function. - #[allow(unused_mut)] - #[allow(unused_variables)] + #[cfg(not(target_arch = "wasm32"))] pub fn request_state_fn( mut self, val: impl GetRequestStateFnType + Send + Sync + 'static, ) -> Template { - #[cfg(feature = "server-side")] - { - self.get_request_state = Some(Box::new(val)); - } + self.get_request_state = Some(Box::new(val)); + self + } + /// Enables the *request state* strategy with the given function. + #[cfg(target_arch = "wasm32")] + pub fn request_state_fn(self, _val: impl Fn() + 'static) -> Template { self } + /// Enables the *revalidation* strategy (logic variant) with the given function. - #[allow(unused_mut)] - #[allow(unused_variables)] + #[cfg(not(target_arch = "wasm32"))] pub fn should_revalidate_fn( mut self, val: impl ShouldRevalidateFnType + Send + Sync + 'static, ) -> Template { - #[cfg(feature = "server-side")] - { - self.should_revalidate = Some(Box::new(val)); - } + self.should_revalidate = Some(Box::new(val)); + self + } + /// Enables the *revalidation* strategy (logic variant) with the given function. + #[cfg(target_arch = "wasm32")] + pub fn should_revalidate_fn(self, _val: impl Fn() + 'static) -> Template { self } + /// Enables the *revalidation* strategy (time variant). This takes a time string of a form like `1w` for one week. More details are available /// [in the book](https://arctic-hen7.github.io/perseus/strategies/revalidation.html#time-syntax). - #[allow(unused_mut)] - #[allow(unused_variables)] + #[cfg(not(target_arch = "wasm32"))] pub fn revalidate_after(mut self, val: String) -> Template { - #[cfg(feature = "server-side")] - { - self.revalidate_after = Some(val); - } + self.revalidate_after = Some(val); + self + } + /// Enables the *revalidation* strategy (time variant). This takes a time string of a form like `1w` for one week. More details are available + /// [in the book](https://arctic-hen7.github.io/perseus/strategies/revalidation.html#time-syntax). + #[cfg(target_arch = "wasm32")] + pub fn revalidate_after(self, _val: String) -> Template { self } + /// Enables state amalgamation with the given function. - #[allow(unused_mut)] - #[allow(unused_variables)] + #[cfg(not(target_arch = "wasm32"))] pub fn amalgamate_states_fn( mut self, val: impl Fn(States) -> RenderFnResultWithCause> + Send + Sync + 'static, ) -> Template { - #[cfg(feature = "server-side")] - { - self.amalgamate_states = Some(Box::new(val)); - } + self.amalgamate_states = Some(Box::new(val)); + self + } + /// Enables state amalgamation with the given function. + #[cfg(target_arch = "wasm32")] + pub fn amalgamate_states_fn(self, _val: impl Fn() + 'static) -> Template { self } } diff --git a/packages/perseus/src/template/mod.rs b/packages/perseus/src/template/mod.rs index 1886d134f2..d87479be82 100644 --- a/packages/perseus/src/template/mod.rs +++ b/packages/perseus/src/template/mod.rs @@ -1,13 +1,17 @@ mod core; // So called because this contains what is essentially the core exposed logic of Perseus +#[cfg(not(target_arch = "wasm32"))] mod default_headers; mod page_props; mod render_ctx; +#[cfg(not(target_arch = "wasm32"))] mod states; mod templates_map; pub use self::core::*; // There are a lot of render function traits in here, there's no point in spelling them all out +#[cfg(not(target_arch = "wasm32"))] pub use default_headers::default_headers; pub use page_props::PageProps; pub use render_ctx::RenderCtx; +#[cfg(not(target_arch = "wasm32"))] pub use states::States; pub use templates_map::{ArcTemplateMap, TemplateMap}; diff --git a/packages/perseus/src/utils/mod.rs b/packages/perseus/src/utils/mod.rs index 1430d7b008..08f9dba318 100644 --- a/packages/perseus/src/utils/mod.rs +++ b/packages/perseus/src/utils/mod.rs @@ -1,13 +1,17 @@ mod async_fn_trait; +#[cfg(not(target_arch = "wasm32"))] mod cache_res; mod context; +#[cfg(not(target_arch = "wasm32"))] mod decode_time_str; mod log; mod path_prefix; mod test; pub use async_fn_trait::AsyncFnReturn; +#[cfg(not(target_arch = "wasm32"))] pub use cache_res::{cache_fallible_res, cache_res}; pub(crate) use context::provide_context_signal_replace; +#[cfg(not(target_arch = "wasm32"))] pub use decode_time_str::decode_time_str; -pub use path_prefix::{get_path_prefix_client, get_path_prefix_server}; +pub use path_prefix::*; diff --git a/packages/perseus/src/utils/path_prefix.rs b/packages/perseus/src/utils/path_prefix.rs index 03c84a1482..9d0318daa1 100644 --- a/packages/perseus/src/utils/path_prefix.rs +++ b/packages/perseus/src/utils/path_prefix.rs @@ -1,12 +1,11 @@ -use std::env; -use wasm_bindgen::JsCast; -use web_sys::{HtmlBaseElement, Url}; - /// Gets the path prefix to apply on the server. This uses the `PERSEUS_BASE_PATH` environment variable, which avoids hardcoding /// something as changeable as this into the final binary. Hence however, that variable must be the same as what's set in `` (done /// automatically). /// Trailing forward slashes will be trimmed automatically. +#[cfg(not(target_arch = "wasm32"))] pub fn get_path_prefix_server() -> String { + use std::env; + let base_path = env::var("PERSEUS_BASE_PATH").unwrap_or_else(|_| "".to_string()); base_path .strip_suffix('/') @@ -16,7 +15,11 @@ pub fn get_path_prefix_server() -> String { /// Gets the path prefix to apply in the browser. This uses the HTML `` element, which would be required anyway to make Sycamore's /// router co-operate with a relative path hosting. +#[cfg(target_arch = "wasm32")] pub fn get_path_prefix_client() -> String { + use wasm_bindgen::JsCast; + use web_sys::{HtmlBaseElement, Url}; + let base_path = match web_sys::window() .unwrap() .document() From e383b16c11b25a5d0e257754f12d1d5c300bdad7 Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Sun, 12 Jun 2022 15:57:23 +1000 Subject: [PATCH 05/37] fix: fixed all issues by changing the cargo resolver This is literally dark magic. --- Cargo.toml | 1 + examples/core/basic/Cargo.toml | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cab401af78..88df8baa9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = [ # "examples/core/basic/.perseus/server", # "examples/core/basic/.perseus/builder" ] +resolver = "2" [patch.crates-io] sycamore = { git = "https://github.com/arctic-hen7/sycamore" } diff --git a/examples/core/basic/Cargo.toml b/examples/core/basic/Cargo.toml index 305d4bb42b..582fea5d6f 100644 --- a/examples/core/basic/Cargo.toml +++ b/examples/core/basic/Cargo.toml @@ -2,17 +2,18 @@ name = "perseus-example-basic" version = "0.4.0-beta.1" edition = "2021" +# forced-target = "wasm32-unknown-unknown" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -# perseus = { path = "../../../packages/perseus", features = [ "hydrate", "builder", "dflt-engine" ] } +perseus = { path = "../../../packages/perseus", features = [ "hydrate", "builder", "dflt-engine" ] } sycamore = "=0.8.0-beta.6" serde = { version = "1", features = ["derive"] } serde_json = "1" [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] -# fantoccini = "0.17" +fantoccini = "0.17" # New Perseus metadata begins here [target.'cfg(not(target_arch = "wasm32"))'.dependencies] From f5fd65b749ae85bb48aac0472da9e816df3fb93e Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Mon, 13 Jun 2022 08:32:12 +1000 Subject: [PATCH 06/37] fix: fixed `HydrateNode`/`DomNode` issues and reformed original structure --- examples/core/basic/Cargo.toml | 3 +- examples/core/basic/src/lib.rs | 11 + examples/core/basic/src/main.rs | 12 - examples/core/basic/tree.txt | 189 --------- examples/core/basic/tree1.txt | 364 ------------------ packages/perseus-cli/src/build.rs | 2 +- packages/perseus-cli/src/cmd.rs | 4 +- packages/perseus-cli/src/errors.rs | 28 -- packages/perseus-cli/src/export.rs | 14 +- packages/perseus-cli/src/serve.rs | 2 + packages/perseus-cli/src/serve_exported.rs | 2 +- packages/perseus-cli/src/snoop.rs | 2 +- packages/perseus/src/client.rs | 7 +- packages/perseus/src/engine/get_op.rs | 13 +- packages/perseus/src/error_pages.rs | 22 ++ packages/perseus/src/router/app_route.rs | 12 +- .../perseus/src/router/router_component.rs | 27 +- packages/perseus/src/server/html_shell.rs | 4 +- packages/perseus/src/shell.rs | 2 +- 19 files changed, 92 insertions(+), 628 deletions(-) delete mode 100644 examples/core/basic/src/main.rs delete mode 100644 examples/core/basic/tree.txt delete mode 100644 examples/core/basic/tree1.txt diff --git a/examples/core/basic/Cargo.toml b/examples/core/basic/Cargo.toml index 582fea5d6f..24cfa276c9 100644 --- a/examples/core/basic/Cargo.toml +++ b/examples/core/basic/Cargo.toml @@ -15,7 +15,6 @@ serde_json = "1" [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] fantoccini = "0.17" -# New Perseus metadata begins here [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } @@ -30,4 +29,4 @@ crate-type = [ "cdylib", "rlib" ] [[bin]] name = "perseus-example-basic" -path = "src/main.rs" +path = "src/lib.rs" diff --git a/examples/core/basic/src/lib.rs b/examples/core/basic/src/lib.rs index 2f7240d521..03fc15dc4e 100644 --- a/examples/core/basic/src/lib.rs +++ b/examples/core/basic/src/lib.rs @@ -10,6 +10,17 @@ pub fn get_app() -> PerseusApp { .error_pages(crate::error_pages::get_error_pages) } +#[cfg(not(target_arch = "wasm32"))] +#[allow(dead_code)] +#[tokio::main] +async fn main() { + use perseus::builder::{get_op, run_dflt_engine}; + + let op = get_op().unwrap(); + let exit_code = run_dflt_engine(op, get_app(), perseus_warp::dflt_server).await; + std::process::exit(exit_code); +} + #[cfg(target_arch = "wasm32")] #[wasm_bindgen::prelude::wasm_bindgen] pub fn main() -> perseus::ClientReturn { diff --git a/examples/core/basic/src/main.rs b/examples/core/basic/src/main.rs deleted file mode 100644 index ec4bbda0db..0000000000 --- a/examples/core/basic/src/main.rs +++ /dev/null @@ -1,12 +0,0 @@ -#[cfg(not(target_arch = "wasm32"))] -#[tokio::main] -async fn main() { - use perseus::builder::{get_op, run_dflt_engine}; - - let op = get_op().unwrap(); - let exit_code = run_dflt_engine(op, lib::get_app(), perseus_warp::dflt_server).await; - std::process::exit(exit_code); -} - -#[cfg(target_arch = "wasm32")] -fn main() {} diff --git a/examples/core/basic/tree.txt b/examples/core/basic/tree.txt deleted file mode 100644 index a3b329d250..0000000000 --- a/examples/core/basic/tree.txt +++ /dev/null @@ -1,189 +0,0 @@ -perseus-example-basic v0.4.0-beta.1 (/home/arctic_hen7/Coding/Projects/perseus/examples/core/basic) -├── perseus v0.4.0-beta.1 (/home/arctic_hen7/Coding/Projects/perseus/packages/perseus) -│ ├── async-trait v0.1.52 (proc-macro) -│ │ ├── proc-macro2 v1.0.36 -│ │ │ └── unicode-xid v0.2.2 -│ │ ├── quote v1.0.15 -│ │ │ └── proc-macro2 v1.0.36 (*) -│ │ └── syn v1.0.86 -│ │ ├── proc-macro2 v1.0.36 (*) -│ │ ├── quote v1.0.15 (*) -│ │ └── unicode-xid v0.2.2 -│ ├── chrono v0.4.19 -│ │ ├── libc v0.2.117 -│ │ ├── num-integer v0.1.44 -│ │ │ └── num-traits v0.2.14 -│ │ │ [build-dependencies] -│ │ │ └── autocfg v1.1.0 -│ │ │ [build-dependencies] -│ │ │ └── autocfg v1.1.0 -│ │ ├── num-traits v0.2.14 (*) -│ │ └── time v0.1.43 -│ │ └── libc v0.2.117 -│ ├── fmterr v0.1.1 -│ ├── fs_extra v1.2.0 -│ ├── futures v0.3.21 -│ │ ├── futures-channel v0.3.21 -│ │ │ ├── futures-core v0.3.21 -│ │ │ └── futures-sink v0.3.21 -│ │ ├── futures-core v0.3.21 -│ │ ├── futures-executor v0.3.21 -│ │ │ ├── futures-core v0.3.21 -│ │ │ ├── futures-task v0.3.21 -│ │ │ └── futures-util v0.3.21 -│ │ │ ├── futures-channel v0.3.21 (*) -│ │ │ ├── futures-core v0.3.21 -│ │ │ ├── futures-io v0.3.21 -│ │ │ ├── futures-macro v0.3.21 (proc-macro) -│ │ │ │ ├── proc-macro2 v1.0.36 (*) -│ │ │ │ ├── quote v1.0.15 (*) -│ │ │ │ └── syn v1.0.86 (*) -│ │ │ ├── futures-sink v0.3.21 -│ │ │ ├── futures-task v0.3.21 -│ │ │ ├── memchr v2.4.1 -│ │ │ ├── pin-project-lite v0.2.8 -│ │ │ ├── pin-utils v0.1.0 -│ │ │ └── slab v0.4.5 -│ │ ├── futures-io v0.3.21 -│ │ ├── futures-sink v0.3.21 -│ │ ├── futures-task v0.3.21 -│ │ └── futures-util v0.3.21 (*) -│ ├── http v0.2.6 -│ │ ├── bytes v1.1.0 -│ │ ├── fnv v1.0.7 -│ │ └── itoa v1.0.1 -│ ├── perseus-macro v0.4.0-beta.1 (proc-macro) (/home/arctic_hen7/Coding/Projects/perseus/packages/perseus-macro) -│ │ ├── darling v0.13.1 -│ │ │ ├── darling_core v0.13.1 -│ │ │ │ ├── fnv v1.0.7 -│ │ │ │ ├── ident_case v1.0.1 -│ │ │ │ ├── proc-macro2 v1.0.36 (*) -│ │ │ │ ├── quote v1.0.15 (*) -│ │ │ │ ├── strsim v0.10.0 -│ │ │ │ └── syn v1.0.86 (*) -│ │ │ └── darling_macro v0.13.1 (proc-macro) -│ │ │ ├── darling_core v0.13.1 (*) -│ │ │ ├── quote v1.0.15 (*) -│ │ │ └── syn v1.0.86 (*) -│ │ ├── proc-macro2 v1.0.36 (*) -│ │ ├── quote v1.0.15 (*) -│ │ ├── regex v1.5.4 -│ │ │ ├── aho-corasick v0.7.18 -│ │ │ │ └── memchr v2.4.1 -│ │ │ ├── memchr v2.4.1 -│ │ │ └── regex-syntax v0.6.25 -│ │ ├── serde_json v1.0.79 -│ │ │ ├── itoa v1.0.1 -│ │ │ ├── ryu v1.0.9 -│ │ │ └── serde v1.0.136 -│ │ │ └── serde_derive v1.0.136 (proc-macro) -│ │ │ ├── proc-macro2 v1.0.36 (*) -│ │ │ ├── quote v1.0.15 (*) -│ │ │ └── syn v1.0.86 (*) -│ │ ├── sycamore-reactive v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) -│ │ │ ├── ahash v0.7.6 -│ │ │ │ ├── getrandom v0.2.4 -│ │ │ │ │ ├── cfg-if v1.0.0 -│ │ │ │ │ └── libc v0.2.117 -│ │ │ │ └── once_cell v1.10.0 -│ │ │ │ [build-dependencies] -│ │ │ │ └── version_check v0.9.4 -│ │ │ ├── bumpalo v3.9.1 -│ │ │ ├── indexmap v1.8.1 -│ │ │ │ └── hashbrown v0.11.2 -│ │ │ │ [build-dependencies] -│ │ │ │ └── autocfg v1.1.0 -│ │ │ ├── slotmap v1.0.6 -│ │ │ │ [build-dependencies] -│ │ │ │ └── version_check v0.9.4 -│ │ │ └── smallvec v1.8.0 -│ │ └── syn v1.0.86 (*) -│ ├── regex v1.5.4 (*) -│ ├── serde v1.0.136 (*) -│ ├── serde_json v1.0.79 (*) -│ ├── sycamore v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) -│ │ ├── ahash v0.7.6 (*) -│ │ ├── html-escape v0.2.11 -│ │ │ └── utf8-width v0.1.5 -│ │ ├── indexmap v1.8.1 (*) -│ │ ├── js-sys v0.3.57 -│ │ │ └── wasm-bindgen v0.2.80 -│ │ │ ├── cfg-if v1.0.0 -│ │ │ └── wasm-bindgen-macro v0.2.80 (proc-macro) -│ │ │ ├── quote v1.0.15 (*) -│ │ │ └── wasm-bindgen-macro-support v0.2.80 -│ │ │ ├── proc-macro2 v1.0.36 (*) -│ │ │ ├── quote v1.0.15 (*) -│ │ │ ├── syn v1.0.86 (*) -│ │ │ ├── wasm-bindgen-backend v0.2.80 -│ │ │ │ ├── bumpalo v3.9.1 -│ │ │ │ ├── lazy_static v1.4.0 -│ │ │ │ ├── log v0.4.14 -│ │ │ │ │ └── cfg-if v1.0.0 -│ │ │ │ ├── proc-macro2 v1.0.36 (*) -│ │ │ │ ├── quote v1.0.15 (*) -│ │ │ │ ├── syn v1.0.86 (*) -│ │ │ │ └── wasm-bindgen-shared v0.2.80 -│ │ │ └── wasm-bindgen-shared v0.2.80 -│ │ ├── once_cell v1.10.0 -│ │ ├── paste v1.0.6 (proc-macro) -│ │ ├── sycamore-core v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) -│ │ │ ├── ahash v0.7.6 (*) -│ │ │ └── sycamore-reactive v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) (*) -│ │ ├── sycamore-macro v0.8.0-beta.6 (proc-macro) (https://github.com/arctic-hen7/sycamore#2e9d62ae) -│ │ │ ├── once_cell v1.10.0 -│ │ │ ├── proc-macro2 v1.0.36 (*) -│ │ │ ├── quote v1.0.15 (*) -│ │ │ └── syn v1.0.86 (*) -│ │ ├── sycamore-reactive v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) (*) -│ │ ├── sycamore-web v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) -│ │ │ ├── html-escape v0.2.11 (*) -│ │ │ ├── indexmap v1.8.1 (*) -│ │ │ ├── js-sys v0.3.57 (*) -│ │ │ ├── once_cell v1.10.0 -│ │ │ ├── sycamore-core v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) (*) -│ │ │ ├── sycamore-reactive v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) (*) -│ │ │ ├── wasm-bindgen v0.2.80 (*) -│ │ │ └── web-sys v0.3.57 -│ │ │ ├── js-sys v0.3.57 (*) -│ │ │ └── wasm-bindgen v0.2.80 (*) -│ │ ├── wasm-bindgen v0.2.80 (*) -│ │ └── web-sys v0.3.57 (*) -│ ├── sycamore-futures v0.8.0-beta.6 -│ │ ├── futures v0.3.21 (*) -│ │ ├── sycamore-reactive v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) (*) -│ │ └── tokio v1.17.0 -│ │ ├── bytes v1.1.0 -│ │ ├── memchr v2.4.1 -│ │ ├── num_cpus v1.13.1 -│ │ │ └── libc v0.2.117 -│ │ ├── pin-project-lite v0.2.8 -│ │ └── tokio-macros v1.7.0 (proc-macro) -│ │ ├── proc-macro2 v1.0.36 (*) -│ │ ├── quote v1.0.15 (*) -│ │ └── syn v1.0.86 (*) -│ ├── sycamore-router v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) -│ │ ├── sycamore v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) (*) -│ │ ├── sycamore-router-macro v0.8.0-beta.6 (proc-macro) (https://github.com/arctic-hen7/sycamore#2e9d62ae) -│ │ │ ├── nom v7.1.0 -│ │ │ │ ├── memchr v2.4.1 -│ │ │ │ └── minimal-lexical v0.2.1 -│ │ │ │ [build-dependencies] -│ │ │ │ └── version_check v0.9.4 -│ │ │ ├── proc-macro2 v1.0.36 (*) -│ │ │ ├── quote v1.0.15 (*) -│ │ │ ├── syn v1.0.86 (*) -│ │ │ └── unicode-xid v0.2.2 -│ │ ├── wasm-bindgen v0.2.80 (*) -│ │ └── web-sys v0.3.57 (*) -│ ├── thiserror v1.0.30 -│ │ └── thiserror-impl v1.0.30 (proc-macro) -│ │ ├── proc-macro2 v1.0.36 (*) -│ │ ├── quote v1.0.15 (*) -│ │ └── syn v1.0.86 (*) -│ ├── tokio v1.17.0 (*) -│ └── urlencoding v2.1.0 -├── serde v1.0.136 (*) -├── serde_json v1.0.79 (*) -├── sycamore v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) (*) -└── tokio v1.17.0 (*) diff --git a/examples/core/basic/tree1.txt b/examples/core/basic/tree1.txt deleted file mode 100644 index d6591b1414..0000000000 --- a/examples/core/basic/tree1.txt +++ /dev/null @@ -1,364 +0,0 @@ -perseus-example-basic v0.4.0-beta.1 (/home/arctic_hen7/Coding/Projects/perseus/examples/core/basic) -├── perseus v0.4.0-beta.1 (/home/arctic_hen7/Coding/Projects/perseus/packages/perseus) -│ ├── async-trait v0.1.52 (proc-macro) -│ │ ├── proc-macro2 v1.0.36 -│ │ │ └── unicode-xid v0.2.2 -│ │ ├── quote v1.0.15 -│ │ │ └── proc-macro2 v1.0.36 (*) -│ │ └── syn v1.0.86 -│ │ ├── proc-macro2 v1.0.36 (*) -│ │ ├── quote v1.0.15 (*) -│ │ └── unicode-xid v0.2.2 -│ ├── chrono v0.4.19 -│ │ ├── libc v0.2.117 -│ │ ├── num-integer v0.1.44 -│ │ │ └── num-traits v0.2.14 -│ │ │ [build-dependencies] -│ │ │ └── autocfg v1.1.0 -│ │ │ [build-dependencies] -│ │ │ └── autocfg v1.1.0 -│ │ ├── num-traits v0.2.14 (*) -│ │ └── time v0.1.43 -│ │ └── libc v0.2.117 -│ ├── fmterr v0.1.1 -│ ├── fs_extra v1.2.0 -│ ├── futures v0.3.21 -│ │ ├── futures-channel v0.3.21 -│ │ │ ├── futures-core v0.3.21 -│ │ │ └── futures-sink v0.3.21 -│ │ ├── futures-core v0.3.21 -│ │ ├── futures-executor v0.3.21 -│ │ │ ├── futures-core v0.3.21 -│ │ │ ├── futures-task v0.3.21 -│ │ │ └── futures-util v0.3.21 -│ │ │ ├── futures-channel v0.3.21 (*) -│ │ │ ├── futures-core v0.3.21 -│ │ │ ├── futures-io v0.3.21 -│ │ │ ├── futures-macro v0.3.21 (proc-macro) -│ │ │ │ ├── proc-macro2 v1.0.36 (*) -│ │ │ │ ├── quote v1.0.15 (*) -│ │ │ │ └── syn v1.0.86 (*) -│ │ │ ├── futures-sink v0.3.21 -│ │ │ ├── futures-task v0.3.21 -│ │ │ ├── memchr v2.4.1 -│ │ │ ├── pin-project-lite v0.2.8 -│ │ │ ├── pin-utils v0.1.0 -│ │ │ └── slab v0.4.5 -│ │ ├── futures-io v0.3.21 -│ │ ├── futures-sink v0.3.21 -│ │ ├── futures-task v0.3.21 -│ │ └── futures-util v0.3.21 (*) -│ ├── http v0.2.6 -│ │ ├── bytes v1.1.0 -│ │ ├── fnv v1.0.7 -│ │ └── itoa v1.0.1 -│ ├── perseus-macro v0.4.0-beta.1 (proc-macro) (/home/arctic_hen7/Coding/Projects/perseus/packages/perseus-macro) -│ │ ├── darling v0.13.1 -│ │ │ ├── darling_core v0.13.1 -│ │ │ │ ├── fnv v1.0.7 -│ │ │ │ ├── ident_case v1.0.1 -│ │ │ │ ├── proc-macro2 v1.0.36 (*) -│ │ │ │ ├── quote v1.0.15 (*) -│ │ │ │ ├── strsim v0.10.0 -│ │ │ │ └── syn v1.0.86 (*) -│ │ │ └── darling_macro v0.13.1 (proc-macro) -│ │ │ ├── darling_core v0.13.1 (*) -│ │ │ ├── quote v1.0.15 (*) -│ │ │ └── syn v1.0.86 (*) -│ │ ├── proc-macro2 v1.0.36 (*) -│ │ ├── quote v1.0.15 (*) -│ │ ├── regex v1.5.4 -│ │ │ ├── aho-corasick v0.7.18 -│ │ │ │ └── memchr v2.4.1 -│ │ │ ├── memchr v2.4.1 -│ │ │ └── regex-syntax v0.6.25 -│ │ ├── serde_json v1.0.79 -│ │ │ ├── itoa v1.0.1 -│ │ │ ├── ryu v1.0.9 -│ │ │ └── serde v1.0.136 -│ │ │ └── serde_derive v1.0.136 (proc-macro) -│ │ │ ├── proc-macro2 v1.0.36 (*) -│ │ │ ├── quote v1.0.15 (*) -│ │ │ └── syn v1.0.86 (*) -│ │ ├── sycamore-reactive v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) -│ │ │ ├── ahash v0.7.6 -│ │ │ │ ├── getrandom v0.2.4 -│ │ │ │ │ ├── cfg-if v1.0.0 -│ │ │ │ │ └── libc v0.2.117 -│ │ │ │ └── once_cell v1.10.0 -│ │ │ │ [build-dependencies] -│ │ │ │ └── version_check v0.9.4 -│ │ │ ├── bumpalo v3.9.1 -│ │ │ ├── indexmap v1.8.1 -│ │ │ │ └── hashbrown v0.11.2 -│ │ │ │ [build-dependencies] -│ │ │ │ └── autocfg v1.1.0 -│ │ │ ├── slotmap v1.0.6 -│ │ │ │ [build-dependencies] -│ │ │ │ └── version_check v0.9.4 -│ │ │ └── smallvec v1.8.0 -│ │ └── syn v1.0.86 (*) -│ ├── regex v1.5.4 (*) -│ ├── serde v1.0.136 (*) -│ ├── serde_json v1.0.79 (*) -│ ├── sycamore v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) -│ │ ├── ahash v0.7.6 (*) -│ │ ├── html-escape v0.2.11 -│ │ │ └── utf8-width v0.1.5 -│ │ ├── indexmap v1.8.1 (*) -│ │ ├── js-sys v0.3.57 -│ │ │ └── wasm-bindgen v0.2.80 -│ │ │ ├── cfg-if v1.0.0 -│ │ │ └── wasm-bindgen-macro v0.2.80 (proc-macro) -│ │ │ ├── quote v1.0.15 (*) -│ │ │ └── wasm-bindgen-macro-support v0.2.80 -│ │ │ ├── proc-macro2 v1.0.36 (*) -│ │ │ ├── quote v1.0.15 (*) -│ │ │ ├── syn v1.0.86 (*) -│ │ │ ├── wasm-bindgen-backend v0.2.80 -│ │ │ │ ├── bumpalo v3.9.1 -│ │ │ │ ├── lazy_static v1.4.0 -│ │ │ │ ├── log v0.4.14 -│ │ │ │ │ └── cfg-if v1.0.0 -│ │ │ │ ├── proc-macro2 v1.0.36 (*) -│ │ │ │ ├── quote v1.0.15 (*) -│ │ │ │ ├── syn v1.0.86 (*) -│ │ │ │ └── wasm-bindgen-shared v0.2.80 -│ │ │ └── wasm-bindgen-shared v0.2.80 -│ │ ├── once_cell v1.10.0 -│ │ ├── paste v1.0.6 (proc-macro) -│ │ ├── sycamore-core v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) -│ │ │ ├── ahash v0.7.6 (*) -│ │ │ └── sycamore-reactive v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) (*) -│ │ ├── sycamore-macro v0.8.0-beta.6 (proc-macro) (https://github.com/arctic-hen7/sycamore#2e9d62ae) -│ │ │ ├── once_cell v1.10.0 -│ │ │ ├── proc-macro2 v1.0.36 (*) -│ │ │ ├── quote v1.0.15 (*) -│ │ │ └── syn v1.0.86 (*) -│ │ ├── sycamore-reactive v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) (*) -│ │ ├── sycamore-web v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) -│ │ │ ├── html-escape v0.2.11 (*) -│ │ │ ├── indexmap v1.8.1 (*) -│ │ │ ├── js-sys v0.3.57 (*) -│ │ │ ├── once_cell v1.10.0 -│ │ │ ├── sycamore-core v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) (*) -│ │ │ ├── sycamore-reactive v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) (*) -│ │ │ ├── wasm-bindgen v0.2.80 (*) -│ │ │ └── web-sys v0.3.57 -│ │ │ ├── js-sys v0.3.57 (*) -│ │ │ └── wasm-bindgen v0.2.80 (*) -│ │ ├── wasm-bindgen v0.2.80 (*) -│ │ └── web-sys v0.3.57 (*) -│ ├── sycamore-futures v0.8.0-beta.6 -│ │ ├── futures v0.3.21 (*) -│ │ ├── sycamore-reactive v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) (*) -│ │ └── tokio v1.17.0 -│ │ ├── bytes v1.1.0 -│ │ ├── libc v0.2.117 -│ │ ├── memchr v2.4.1 -│ │ ├── mio v0.8.0 -│ │ │ ├── libc v0.2.117 -│ │ │ └── log v0.4.14 (*) -│ │ ├── num_cpus v1.13.1 -│ │ │ └── libc v0.2.117 -│ │ ├── pin-project-lite v0.2.8 -│ │ ├── socket2 v0.4.4 -│ │ │ └── libc v0.2.117 -│ │ └── tokio-macros v1.7.0 (proc-macro) -│ │ ├── proc-macro2 v1.0.36 (*) -│ │ ├── quote v1.0.15 (*) -│ │ └── syn v1.0.86 (*) -│ ├── sycamore-router v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) -│ │ ├── sycamore v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) (*) -│ │ ├── sycamore-router-macro v0.8.0-beta.6 (proc-macro) (https://github.com/arctic-hen7/sycamore#2e9d62ae) -│ │ │ ├── nom v7.1.0 -│ │ │ │ ├── memchr v2.4.1 -│ │ │ │ └── minimal-lexical v0.2.1 -│ │ │ │ [build-dependencies] -│ │ │ │ └── version_check v0.9.4 -│ │ │ ├── proc-macro2 v1.0.36 (*) -│ │ │ ├── quote v1.0.15 (*) -│ │ │ ├── syn v1.0.86 (*) -│ │ │ └── unicode-xid v0.2.2 -│ │ ├── wasm-bindgen v0.2.80 (*) -│ │ └── web-sys v0.3.57 (*) -│ ├── thiserror v1.0.30 -│ │ └── thiserror-impl v1.0.30 (proc-macro) -│ │ ├── proc-macro2 v1.0.36 (*) -│ │ ├── quote v1.0.15 (*) -│ │ └── syn v1.0.86 (*) -│ ├── tokio v1.17.0 (*) -│ └── urlencoding v2.1.0 -├── perseus-warp v0.4.0-beta.1 (/home/arctic_hen7/Coding/Projects/perseus/packages/perseus-warp) -│ ├── fmterr v0.1.1 -│ ├── futures v0.3.21 (*) -│ ├── perseus v0.4.0-beta.1 (/home/arctic_hen7/Coding/Projects/perseus/packages/perseus) (*) -│ ├── serde v1.0.136 (*) -│ ├── serde_json v1.0.79 (*) -│ ├── sycamore v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) (*) -│ ├── thiserror v1.0.30 (*) -│ ├── tokio v1.17.0 (*) -│ ├── urlencoding v2.1.0 -│ └── warp-fix-171 v0.3.2 -│ ├── bytes v1.1.0 -│ ├── futures-channel v0.3.21 (*) -│ ├── futures-util v0.3.21 (*) -│ ├── headers v0.3.7 -│ │ ├── base64 v0.13.0 -│ │ ├── bitflags v1.3.2 -│ │ ├── bytes v1.1.0 -│ │ ├── headers-core v0.2.0 -│ │ │ └── http v0.2.6 (*) -│ │ ├── http v0.2.6 (*) -│ │ ├── httpdate v1.0.2 -│ │ ├── mime v0.3.16 -│ │ └── sha-1 v0.10.0 -│ │ ├── cfg-if v1.0.0 -│ │ ├── cpufeatures v0.2.1 -│ │ └── digest v0.10.2 -│ │ ├── block-buffer v0.10.2 -│ │ │ └── generic-array v0.14.5 -│ │ │ └── typenum v1.15.0 -│ │ │ [build-dependencies] -│ │ │ └── version_check v0.9.4 -│ │ └── crypto-common v0.1.2 -│ │ └── generic-array v0.14.5 (*) -│ ├── http v0.2.6 (*) -│ ├── hyper v0.14.17 -│ │ ├── bytes v1.1.0 -│ │ ├── futures-channel v0.3.21 (*) -│ │ ├── futures-core v0.3.21 -│ │ ├── futures-util v0.3.21 (*) -│ │ ├── h2 v0.3.11 -│ │ │ ├── bytes v1.1.0 -│ │ │ ├── fnv v1.0.7 -│ │ │ ├── futures-core v0.3.21 -│ │ │ ├── futures-sink v0.3.21 -│ │ │ ├── futures-util v0.3.21 (*) -│ │ │ ├── http v0.2.6 (*) -│ │ │ ├── indexmap v1.8.1 (*) -│ │ │ ├── slab v0.4.5 -│ │ │ ├── tokio v1.17.0 (*) -│ │ │ ├── tokio-util v0.6.9 -│ │ │ │ ├── bytes v1.1.0 -│ │ │ │ ├── futures-core v0.3.21 -│ │ │ │ ├── futures-sink v0.3.21 -│ │ │ │ ├── log v0.4.14 (*) -│ │ │ │ ├── pin-project-lite v0.2.8 -│ │ │ │ └── tokio v1.17.0 (*) -│ │ │ └── tracing v0.1.30 -│ │ │ ├── cfg-if v1.0.0 -│ │ │ ├── log v0.4.14 (*) -│ │ │ ├── pin-project-lite v0.2.8 -│ │ │ └── tracing-core v0.1.22 -│ │ │ └── lazy_static v1.4.0 -│ │ ├── http v0.2.6 (*) -│ │ ├── http-body v0.4.4 -│ │ │ ├── bytes v1.1.0 -│ │ │ ├── http v0.2.6 (*) -│ │ │ └── pin-project-lite v0.2.8 -│ │ ├── httparse v1.6.0 -│ │ ├── httpdate v1.0.2 -│ │ ├── itoa v1.0.1 -│ │ ├── pin-project-lite v0.2.8 -│ │ ├── socket2 v0.4.4 (*) -│ │ ├── tokio v1.17.0 (*) -│ │ ├── tower-service v0.3.1 -│ │ ├── tracing v0.1.30 (*) -│ │ └── want v0.3.0 -│ │ ├── log v0.4.14 (*) -│ │ └── try-lock v0.2.3 -│ ├── log v0.4.14 (*) -│ ├── mime v0.3.16 -│ ├── mime_guess v2.0.3 -│ │ ├── mime v0.3.16 -│ │ └── unicase v2.6.0 -│ │ [build-dependencies] -│ │ └── version_check v0.9.4 -│ │ [build-dependencies] -│ │ └── unicase v2.6.0 (*) -│ ├── multipart v0.18.0 -│ │ ├── buf_redux v0.8.4 -│ │ │ ├── memchr v2.4.1 -│ │ │ └── safemem v0.3.3 -│ │ ├── httparse v1.6.0 -│ │ ├── log v0.4.14 (*) -│ │ ├── mime v0.3.16 -│ │ ├── mime_guess v2.0.3 (*) -│ │ ├── quick-error v1.2.3 -│ │ ├── rand v0.8.5 -│ │ │ ├── libc v0.2.117 -│ │ │ ├── rand_chacha v0.3.1 -│ │ │ │ ├── ppv-lite86 v0.2.16 -│ │ │ │ └── rand_core v0.6.3 -│ │ │ │ └── getrandom v0.2.4 (*) -│ │ │ └── rand_core v0.6.3 (*) -│ │ ├── safemem v0.3.3 -│ │ ├── tempfile v3.3.0 -│ │ │ ├── cfg-if v1.0.0 -│ │ │ ├── fastrand v1.7.0 -│ │ │ ├── libc v0.2.117 -│ │ │ └── remove_dir_all v0.5.3 -│ │ └── twoway v0.1.8 -│ │ └── memchr v2.4.1 -│ ├── percent-encoding v2.1.0 -│ ├── pin-project v1.0.10 -│ │ └── pin-project-internal v1.0.10 (proc-macro) -│ │ ├── proc-macro2 v1.0.36 (*) -│ │ ├── quote v1.0.15 (*) -│ │ └── syn v1.0.86 (*) -│ ├── scoped-tls v1.0.0 -│ ├── serde v1.0.136 (*) -│ ├── serde_json v1.0.79 (*) -│ ├── serde_urlencoded v0.7.1 -│ │ ├── form_urlencoded v1.0.1 -│ │ │ ├── matches v0.1.9 -│ │ │ └── percent-encoding v2.1.0 -│ │ ├── itoa v1.0.1 -│ │ ├── ryu v1.0.9 -│ │ └── serde v1.0.136 (*) -│ ├── tokio v1.17.0 (*) -│ ├── tokio-stream v0.1.8 -│ │ ├── futures-core v0.3.21 -│ │ ├── pin-project-lite v0.2.8 -│ │ └── tokio v1.17.0 (*) -│ ├── tokio-tungstenite v0.15.0 -│ │ ├── futures-util v0.3.21 (*) -│ │ ├── log v0.4.14 (*) -│ │ ├── pin-project v1.0.10 (*) -│ │ ├── tokio v1.17.0 (*) -│ │ └── tungstenite v0.14.0 -│ │ ├── base64 v0.13.0 -│ │ ├── byteorder v1.4.3 -│ │ ├── bytes v1.1.0 -│ │ ├── http v0.2.6 (*) -│ │ ├── httparse v1.6.0 -│ │ ├── log v0.4.14 (*) -│ │ ├── rand v0.8.5 (*) -│ │ ├── sha-1 v0.9.8 -│ │ │ ├── block-buffer v0.9.0 -│ │ │ │ └── generic-array v0.14.5 (*) -│ │ │ ├── cfg-if v1.0.0 -│ │ │ ├── cpufeatures v0.2.1 -│ │ │ ├── digest v0.9.0 -│ │ │ │ └── generic-array v0.14.5 (*) -│ │ │ └── opaque-debug v0.3.0 -│ │ ├── thiserror v1.0.30 (*) -│ │ ├── url v2.2.2 -│ │ │ ├── form_urlencoded v1.0.1 (*) -│ │ │ ├── idna v0.2.3 -│ │ │ │ ├── matches v0.1.9 -│ │ │ │ ├── unicode-bidi v0.3.7 -│ │ │ │ └── unicode-normalization v0.1.19 -│ │ │ │ └── tinyvec v1.5.1 -│ │ │ │ └── tinyvec_macros v0.1.0 -│ │ │ ├── matches v0.1.9 -│ │ │ └── percent-encoding v2.1.0 -│ │ └── utf-8 v0.7.6 -│ ├── tokio-util v0.6.9 (*) -│ ├── tower-service v0.3.1 -│ └── tracing v0.1.30 (*) -├── serde v1.0.136 (*) -├── serde_json v1.0.79 (*) -├── sycamore v0.8.0-beta.6 (https://github.com/arctic-hen7/sycamore#2e9d62ae) (*) -└── tokio v1.17.0 (*) diff --git a/packages/perseus-cli/src/build.rs b/packages/perseus-cli/src/build.rs index d632c3ca70..2b17e7e43c 100644 --- a/packages/perseus-cli/src/build.rs +++ b/packages/perseus-cli/src/build.rs @@ -93,7 +93,7 @@ pub fn build_internal( let wb_thread = spawn_thread(move || { handle_exit_code!(run_stage( vec![&format!( - "{} build --out-dir dist/pkg --target web {}", + "{} build --out-dir dist/pkg --out-name perseus_engine --target web {}", env::var("PERSEUS_WASM_PACK_PATH").unwrap_or_else(|_| "wasm-pack".to_string()), if is_release { "--release" } else { "--dev" } // If we don't supply `--dev`, another profile will be used )], diff --git a/packages/perseus-cli/src/cmd.rs b/packages/perseus-cli/src/cmd.rs index 8f654a5a2f..ad0691077f 100644 --- a/packages/perseus-cli/src/cmd.rs +++ b/packages/perseus-cli/src/cmd.rs @@ -31,7 +31,7 @@ pub fn run_cmd( // This will NOT pipe output/errors to the console let output = Command::new(shell_exec) .args([shell_param, &cmd]) - .env("PERSEUS_BUILDER_OPERATION", op) + .env("PERSEUS_ENGINE_OPERATION", op) .current_dir(dir) .output() .map_err(|err| ExecutionError::CmdExecFailed { cmd, source: err })?; @@ -121,7 +121,7 @@ pub fn run_cmd_directly(cmd: String, dir: &Path, op: &str) -> Result Result<(), ExportError> { - // Move the `pkg/` directory into `dist/pkg/` as usual - let pkg_dir = target.join("dist/pkg"); - if pkg_dir.exists() { - if let Err(err) = fs::remove_dir_all(&pkg_dir) { - return Err(ExecutionError::MovePkgDirFailed { source: err }.into()); - } - } - // The `fs::rename()` function will fail on Windows if the destination already exists, so this should work (we've just deleted it as per https://github.com/rust-lang/rust/issues/31301#issuecomment-177117325) - if let Err(err) = fs::rename(target.join("pkg"), target.join("dist/pkg")) { - return Err(ExecutionError::MovePkgDirFailed { source: err }.into()); - } - // Copy files over (the directory structure should already exist from exporting the pages) copy_file!( "dist/pkg/perseus_engine.js", @@ -178,7 +166,7 @@ pub fn export_internal( let wb_thread = spawn_thread(move || { handle_exit_code!(run_stage( vec![&format!( - "{} build --out-dir dist/pkg --target web {}", + "{} build --out-dir dist/pkg --out-name perseus_engine --target web {}", env::var("PERSEUS_WASM_PACK_PATH").unwrap_or_else(|_| "wasm-pack".to_string()), if is_release { "--release" } else { "--dev" } )], diff --git a/packages/perseus-cli/src/serve.rs b/packages/perseus-cli/src/serve.rs index 422d5ff8ad..d49b2167f3 100644 --- a/packages/perseus-cli/src/serve.rs +++ b/packages/perseus-cli/src/serve.rs @@ -135,6 +135,8 @@ fn run_server( // Manually run the generated binary (invoking in the right directory context for good measure if it ever needs it in future) let child = Command::new(&server_exec_path) .current_dir(&dir) + // This needs to be provided in development, but not in production + .env("PERSEUS_ENGINE_OPERATION", "serve") // We should be able to access outputs in case there's an error .stdout(Stdio::piped()) .stderr(Stdio::piped()) diff --git a/packages/perseus-cli/src/serve_exported.rs b/packages/perseus-cli/src/serve_exported.rs index 7fe6a78e40..0aaeedf2c9 100644 --- a/packages/perseus-cli/src/serve_exported.rs +++ b/packages/perseus-cli/src/serve_exported.rs @@ -7,7 +7,7 @@ static SERVING: Emoji<'_, '_> = Emoji("🛰️ ", ""); /// Serves an exported app, assuming it's already been exported. pub async fn serve_exported(dir: PathBuf, host: String, port: u16) { - let dir = dir.join(".perseus/dist/exported"); + let dir = dir.join("dist/exported"); // We actually don't have to worry about HTML file extensions at all let files = warp::any().and(warp::fs::dir(dir)); // Parse `localhost` into `127.0.0.1` (picky Rust `std`) diff --git a/packages/perseus-cli/src/snoop.rs b/packages/perseus-cli/src/snoop.rs index 63482274d0..1ca1e9b692 100644 --- a/packages/perseus-cli/src/snoop.rs +++ b/packages/perseus-cli/src/snoop.rs @@ -21,7 +21,7 @@ pub fn snoop_build(dir: PathBuf) -> Result { pub fn snoop_wasm_build(dir: PathBuf, opts: SnoopWasmOpts) -> Result { run_cmd_directly( format!( - "{} build --out-dir dist/pkg --target web {}", + "{} build --out-dir dist/pkg --out-name perseus_engine --target web {}", env::var("PERSEUS_WASM_PACK_PATH").unwrap_or_else(|_| "wasm-pack".to_string()), if opts.profiling { "--profiling" diff --git a/packages/perseus/src/client.rs b/packages/perseus/src/client.rs index 7e89539895..1f3da1f6db 100644 --- a/packages/perseus/src/client.rs +++ b/packages/perseus/src/client.rs @@ -2,8 +2,9 @@ use crate::{ checkpoint, internal::router::{perseus_router, PerseusRouterProps}, plugins::PluginAction, + shell::get_render_cfg, + templates::TemplateNodeType }; -use sycamore::web::DomNode; use wasm_bindgen::JsValue; use crate::{i18n::TranslationsManager, stores::MutableStore, PerseusAppBase}; @@ -13,7 +14,7 @@ use crate::{i18n::TranslationsManager, stores::MutableStore, PerseusAppBase}; /// /// This is entirely engine-agnostic, using only the properties from the given `PerseusApp`. pub fn run_client( - app: PerseusAppBase, + app: PerseusAppBase, ) -> Result<(), JsValue> { let plugins = app.get_plugins(); @@ -41,6 +42,8 @@ pub fn run_client( let router_props = PerseusRouterProps { locales: app.get_locales(), error_pages: app.get_error_pages(), + templates: app.get_templates_map(), + render_cfg: get_render_cfg().expect("render configuration invalid or not injected") }; // This top-level context is what we use for everything, allowing page state to be registered and stored for the lifetime of the app diff --git a/packages/perseus/src/engine/get_op.rs b/packages/perseus/src/engine/get_op.rs index d987e29778..2ed17ba9df 100644 --- a/packages/perseus/src/engine/get_op.rs +++ b/packages/perseus/src/engine/get_op.rs @@ -3,13 +3,24 @@ use std::env; /// Determines the engine operation to be performed by examining environment variables (set automatically by the CLI as appropriate). pub fn get_op() -> Option { let var = env::var("PERSEUS_ENGINE_OPERATION").ok()?; + match var.as_str() { "serve" => Some(EngineOperation::Serve), "build" => Some(EngineOperation::Build), "export" => Some(EngineOperation::Export), "export_error_page" => Some(EngineOperation::ExportErrorPage), "tinker" => Some(EngineOperation::Tinker), - _ => None, + _ => { + // The only typical use of a release-built binary is as a server, in which case we shouldn't need to specify this environment variable + // So, in production, we take the server as the default + // If a user wants a builder though, they can just set the environment variable + // TODO Document this! + if cfg!(debug_assertions) { + None + } else { + Some(EngineOperation::Serve) + } + }, } } diff --git a/packages/perseus/src/error_pages.rs b/packages/perseus/src/error_pages.rs index 83e42ae7af..885d0babc1 100644 --- a/packages/perseus/src/error_pages.rs +++ b/packages/perseus/src/error_pages.rs @@ -114,6 +114,28 @@ impl ErrorPages { err: &str, translator: Option>, container: &Element, + ) { + let template_fn = self.get_template_fn(status); + let hydrate_view = template_fn(cx, url.to_string(), status, err.to_string(), translator); + // TODO Now convert that `HydrateNode` to a `DomNode` + let dom_view = hydrate_view; + // Render that to the given container + sycamore::hydrate_to( + |_| dom_view, + container, + ); + } + /// Renders the appropriate error page to the given DOM container. This is implemented on `HydrateNode` to avoid having to have two `Html` type parameters everywhere + /// (one for templates and one for error pages). + // TODO Convert from a `HydrateNode` to a `DomNode` + pub fn render_page( + &self, + cx: Scope, + url: &str, + status: u16, + err: &str, + translator: Option>, + container: &Element, ) { let template_fn = self.get_template_fn(status); // Render that to the given container diff --git a/packages/perseus/src/router/app_route.rs b/packages/perseus/src/router/app_route.rs index 546948bfbd..4182ac1b9b 100644 --- a/packages/perseus/src/router/app_route.rs +++ b/packages/perseus/src/router/app_route.rs @@ -33,10 +33,14 @@ use sycamore_router::Route; /// The Perseus route system, which implements Sycamore `Route`, but adds additional data for Perseus' processing system. pub struct PerseusRoute { - verdict: RouteVerdict, - render_cfg: HashMap, - templates: TemplateMap, - locales: Locales, + /// The current route verdict. The initialization value of this is completely irrelevant (it will be overriden immediately by the internal routing logic). + pub verdict: RouteVerdict, + /// The app's render configuration. + pub render_cfg: HashMap, + /// The templates the app is using. + pub templates: TemplateMap, + /// The app's i18n configuration. + pub locales: Locales, } // Sycamore would only use this if we were processing dynamic routes, which we're not // In other words, it's fine that these values would break everything if they were used, they're just compiler appeasement diff --git a/packages/perseus/src/router/router_component.rs b/packages/perseus/src/router/router_component.rs index bd055525d9..29b8716d6b 100644 --- a/packages/perseus/src/router/router_component.rs +++ b/packages/perseus/src/router/router_component.rs @@ -7,16 +7,17 @@ use crate::{ router::{PerseusRoute, RouteInfo, RouteVerdict}, shell::{app_shell, get_initial_state, InitialState, ShellProps}, }, - templates::{RenderCtx, RouterLoadState, RouterState, TemplateNodeType}, + templates::{RenderCtx, RouterLoadState, RouterState, TemplateNodeType, TemplateMap}, DomNode, ErrorPages, Html, }; use std::cell::RefCell; use std::rc::Rc; +use std::collections::HashMap; use sycamore::{ prelude::{component, create_effect, create_signal, view, NodeRef, ReadSignal, Scope, View}, Prop, }; -use sycamore_router::{HistoryIntegration, Router}; +use sycamore_router::{HistoryIntegration, RouterBase}; use web_sys::Element; // We don't want to bring in a styling library, so we do this the old-fashioned way! @@ -43,7 +44,7 @@ struct OnRouteChangeProps<'a, G: Html> { container_rx: NodeRef, router_state: RouterState, translations_manager: Rc>, - error_pages: Rc>, + error_pages: Rc>, initial_container: Option, } @@ -130,9 +131,13 @@ fn on_route_change( #[derive(Debug, Prop)] pub struct PerseusRouterProps { /// The error pages the app is using. - pub error_pages: ErrorPages, + pub error_pages: ErrorPages, /// The locales settings the app is using. pub locales: Locales, + /// The templates the app is using. + pub templates: TemplateMap, + /// The render configuration of the app (which lays out routing information, among other things). + pub render_cfg: HashMap, } /// The Perseus router. This is used internally in the Perseus engine, and you shouldn't need to access this directly unless @@ -146,8 +151,18 @@ pub fn perseus_router( PerseusRouterProps { error_pages, locales, + templates, + render_cfg, }: PerseusRouterProps, ) -> View { + // Create a `Route` to pass through Sycamore with the information we need + let route = PerseusRoute { + verdict: RouteVerdict::NotFound, + templates, + render_cfg, + locales: locales.clone() + }; + // Get the root that the server will have injected initial load content into // This will be moved into a reactive `
` by the app shell // This is an `Option` until we know we aren't doing locale detection (in which case it wouldn't exist) @@ -263,8 +278,10 @@ pub fn perseus_router( crate::state::connect_to_reload_server(live_reload_indicator.clone()); view! { cx, - Router { + // This is a lower-level version of `Router` that lets us provide a `Route` with the data we want + RouterBase { integration: HistoryIntegration::new(), + route, view: move |cx, route: &ReadSignal>| { // Sycamore's reactivity is broken by a future, so we need to explicitly add the route to the reactive dependencies here // We do need the future though (otherwise `container_rx` doesn't link to anything until it's too late) diff --git a/packages/perseus/src/server/html_shell.rs b/packages/perseus/src/server/html_shell.rs index 14d58142d6..9c39a273cb 100644 --- a/packages/perseus/src/server/html_shell.rs +++ b/packages/perseus/src/server/html_shell.rs @@ -68,7 +68,7 @@ impl HtmlShell { #[cfg(not(feature = "wasm2js"))] let load_wasm_bundle = format!( r#" - import init, {{ run }} from "{path_prefix}/.perseus/bundle.js"; + import init, {{ main as run }} from "{path_prefix}/.perseus/bundle.js"; async function main() {{ await init("{path_prefix}/.perseus/bundle.wasm"); run(); @@ -80,7 +80,7 @@ impl HtmlShell { #[cfg(feature = "wasm2js")] let load_wasm_bundle = format!( r#" - import init, {{ run }} from "{path_prefix}/.perseus/bundle.js"; + import init, {{ main as run }} from "{path_prefix}/.perseus/bundle.js"; async function main() {{ await init("{path_prefix}/.perseus/bundle.wasm.js"); run(); diff --git a/packages/perseus/src/shell.rs b/packages/perseus/src/shell.rs index 180d1b7c07..262495340d 100644 --- a/packages/perseus/src/shell.rs +++ b/packages/perseus/src/shell.rs @@ -246,7 +246,7 @@ pub struct ShellProps<'a> { /// A *client-side* translations manager to use (this manages caching translations). pub translations_manager: Rc>, /// The error pages, for use if something fails. - pub error_pages: Rc>, + pub error_pages: Rc>, /// The container responsible for the initial render from the server (non-interactive, this may need to be wiped). pub initial_container: Element, /// The container for reactive content. From 0ef0424604414e87b82f2fec36e4ff56d9c014d5 Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Mon, 13 Jun 2022 09:24:22 +1000 Subject: [PATCH 07/37] fix: fixed small errors with cli --- packages/perseus-cli/src/bin/main.rs | 10 ++--- packages/perseus-cli/src/cmd.rs | 2 +- packages/perseus-cli/src/export_error_page.rs | 5 +-- packages/perseus-cli/src/serve.rs | 22 +++++----- packages/perseus-cli/src/snoop.rs | 6 +-- packages/perseus/src/client.rs | 4 +- packages/perseus/src/engine/get_op.rs | 23 ++++++++--- packages/perseus/src/error_pages.rs | 9 ++-- .../perseus/src/i18n/translations_manager.rs | 6 +-- packages/perseus/src/init.rs | 41 ++++++++++++------- packages/perseus/src/lib.rs | 10 ++--- .../perseus/src/router/router_component.rs | 6 +-- packages/perseus/src/server/render.rs | 2 +- packages/perseus/src/shell.rs | 2 +- packages/perseus/src/stores/immutable.rs | 1 - packages/perseus/src/template/core.rs | 6 +-- packages/perseus/src/template/render_ctx.rs | 1 - 17 files changed, 87 insertions(+), 69 deletions(-) diff --git a/packages/perseus-cli/src/bin/main.rs b/packages/perseus-cli/src/bin/main.rs index 754652bb5a..8868f3867e 100644 --- a/packages/perseus-cli/src/bin/main.rs +++ b/packages/perseus-cli/src/bin/main.rs @@ -8,7 +8,10 @@ use perseus_cli::{ parse::{Opts, Subcommand}, serve, serve_exported, tinker, }; -use perseus_cli::{delete_dist, errors::*, export_error_page, order_reload, run_reload_server, snoop_build, snoop_server, snoop_wasm_build}; +use perseus_cli::{ + delete_dist, errors::*, export_error_page, order_reload, run_reload_server, snoop_build, + snoop_server, snoop_wasm_build, +}; use std::env; use std::io::Write; use std::path::PathBuf; @@ -34,10 +37,7 @@ async fn real_main() -> i32 { let dir = match dir { Ok(dir) => dir, Err(err) => { - eprintln!( - "{}", - fmt_err(&Error::CurrentDirUnavailable { source: err }) - ); + eprintln!("{}", fmt_err(&Error::CurrentDirUnavailable { source: err })); return 1; } }; diff --git a/packages/perseus-cli/src/cmd.rs b/packages/perseus-cli/src/cmd.rs index ad0691077f..121d2ff116 100644 --- a/packages/perseus-cli/src/cmd.rs +++ b/packages/perseus-cli/src/cmd.rs @@ -82,7 +82,7 @@ pub fn run_stage( target: &Path, spinner: &ProgressBar, message: &str, - op: &str + op: &str, ) -> Result<(String, String, i32), ExecutionError> { let mut last_output = (String::new(), String::new()); // Run the commands diff --git a/packages/perseus-cli/src/export_error_page.rs b/packages/perseus-cli/src/export_error_page.rs index 655527edb9..d9c264f833 100644 --- a/packages/perseus-cli/src/export_error_page.rs +++ b/packages/perseus-cli/src/export_error_page.rs @@ -6,7 +6,6 @@ use std::path::PathBuf; /// Exports a single error page for the given HTTP status code to the given location. pub fn export_error_page(dir: PathBuf, opts: ExportErrorPageOpts) -> Result { - let target = dir.join(".perseus/builder"); run_cmd_directly( format!( "{} run {} {}", @@ -15,7 +14,7 @@ pub fn export_error_page(dir: PathBuf, opts: ExportErrorPageOpts) -> Result = stdout.trim().split('\n').collect(); // If we got to here, the exit code was 0 and everything should've worked diff --git a/packages/perseus-cli/src/snoop.rs b/packages/perseus-cli/src/snoop.rs index 1ca1e9b692..2703610d7a 100644 --- a/packages/perseus-cli/src/snoop.rs +++ b/packages/perseus-cli/src/snoop.rs @@ -13,7 +13,7 @@ pub fn snoop_build(dir: PathBuf) -> Result { env::var("PERSEUS_CARGO_PATH").unwrap_or_else(|_| "cargo".to_string()) ), &dir, - "build" + "build", ) } @@ -30,7 +30,7 @@ pub fn snoop_wasm_build(dir: PathBuf, opts: SnoopWasmOpts) -> Result Result( locales: app.get_locales(), error_pages: app.get_error_pages(), templates: app.get_templates_map(), - render_cfg: get_render_cfg().expect("render configuration invalid or not injected") + render_cfg: get_render_cfg().expect("render configuration invalid or not injected"), }; // This top-level context is what we use for everything, allowing page state to be registered and stored for the lifetime of the app diff --git a/packages/perseus/src/engine/get_op.rs b/packages/perseus/src/engine/get_op.rs index 2ed17ba9df..4c2b17979a 100644 --- a/packages/perseus/src/engine/get_op.rs +++ b/packages/perseus/src/engine/get_op.rs @@ -2,7 +2,22 @@ use std::env; /// Determines the engine operation to be performed by examining environment variables (set automatically by the CLI as appropriate). pub fn get_op() -> Option { - let var = env::var("PERSEUS_ENGINE_OPERATION").ok()?; + let var = match env::var("PERSEUS_ENGINE_OPERATION").ok() { + Some(var) => var, + None => { + return { + // The only typical use of a release-built binary is as a server, in which case we shouldn't need to specify this environment variable + // So, in production, we take the server as the default + // If a user wants a builder though, they can just set the environment variable + // TODO Document this! + if cfg!(debug_assertions) { + None + } else { + Some(EngineOperation::Serve) + } + } + } + }; match var.as_str() { "serve" => Some(EngineOperation::Serve), @@ -11,16 +26,12 @@ pub fn get_op() -> Option { "export_error_page" => Some(EngineOperation::ExportErrorPage), "tinker" => Some(EngineOperation::Tinker), _ => { - // The only typical use of a release-built binary is as a server, in which case we shouldn't need to specify this environment variable - // So, in production, we take the server as the default - // If a user wants a builder though, they can just set the environment variable - // TODO Document this! if cfg!(debug_assertions) { None } else { Some(EngineOperation::Serve) } - }, + } } } diff --git a/packages/perseus/src/error_pages.rs b/packages/perseus/src/error_pages.rs index 885d0babc1..92d844a48e 100644 --- a/packages/perseus/src/error_pages.rs +++ b/packages/perseus/src/error_pages.rs @@ -1,9 +1,9 @@ use crate::translator::Translator; use crate::Html; -#[cfg(target_arch = "wasm32")] -use crate::{DomNode, HydrateNode}; #[cfg(not(target_arch = "wasm32"))] use crate::SsrNode; +#[cfg(target_arch = "wasm32")] +use crate::{DomNode, HydrateNode}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::rc::Rc; @@ -120,10 +120,7 @@ impl ErrorPages { // TODO Now convert that `HydrateNode` to a `DomNode` let dom_view = hydrate_view; // Render that to the given container - sycamore::hydrate_to( - |_| dom_view, - container, - ); + sycamore::hydrate_to(|_| dom_view, container); } /// Renders the appropriate error page to the given DOM container. This is implemented on `HydrateNode` to avoid having to have two `Html` type parameters everywhere /// (one for templates and one for error pages). diff --git a/packages/perseus/src/i18n/translations_manager.rs b/packages/perseus/src/i18n/translations_manager.rs index 681b62d292..d23e8ceda8 100644 --- a/packages/perseus/src/i18n/translations_manager.rs +++ b/packages/perseus/src/i18n/translations_manager.rs @@ -26,13 +26,13 @@ pub enum TranslationsManagerError { use crate::translator::Translator; #[cfg(not(target_arch = "wasm32"))] +use futures::future::join_all; +#[cfg(not(target_arch = "wasm32"))] use std::collections::HashMap; #[cfg(not(target_arch = "wasm32"))] use tokio::fs::File; #[cfg(not(target_arch = "wasm32"))] use tokio::io::AsyncReadExt; -#[cfg(not(target_arch = "wasm32"))] -use futures::future::join_all; /// A trait for systems that manage where to put translations. At simplest, we'll just write them to static files, but they might also /// be stored in a CMS. It is **strongly** advised that any implementations use some form of caching, guided by `FsTranslationsManager`. @@ -233,7 +233,7 @@ impl TranslationsManager for FsTranslationsManager { #[cfg(target_arch = "wasm32")] async fn get_translator_for_locale( &self, - _locale: String + _locale: String, ) -> Result { Ok(crate::internal::i18n::DummyTranslator::new(String::new(), String::new()).unwrap()) } diff --git a/packages/perseus/src/init.rs b/packages/perseus/src/init.rs index ad3abeff25..8bd69757a1 100644 --- a/packages/perseus/src/init.rs +++ b/packages/perseus/src/init.rs @@ -1,9 +1,9 @@ use crate::plugins::PluginAction; #[cfg(not(target_arch = "wasm32"))] use crate::server::{get_render_cfg, HtmlShell}; +use crate::stores::ImmutableStore; #[cfg(not(target_arch = "wasm32"))] use crate::utils::get_path_prefix_server; -use crate::stores::ImmutableStore; use crate::{ i18n::{Locales, TranslationsManager}, state::GlobalStateCreator, @@ -125,7 +125,7 @@ pub struct PerseusAppBase { static_dir: String, // We need this on the client-side to account for the unused type parameters #[cfg(target_arch = "wasm32")] - _marker: PhantomData<(M, T)> + _marker: PhantomData<(M, T)>, } // The usual implementation in which the default mutable store is used @@ -176,7 +176,7 @@ impl PerseusAppBase { .locales .get_all() .iter() - // We have a `&&String` at this point, hence the double clone + // We have a `&&String` at this point, hence the double clone .cloned() .cloned() .collect(); @@ -191,7 +191,6 @@ impl PerseusAppBase { } } - self } /// Sets the internationalization information for an app using the default translations manager (`FsTranslationsManager`). This handles locale caching and the like automatically for you, @@ -245,7 +244,7 @@ impl PerseusAppBase { #[cfg(not(target_arch = "wasm32"))] static_dir: "./static".to_string(), #[cfg(target_arch = "wasm32")] - _marker: PhantomData + _marker: PhantomData, } } /// Internal function for Wasm initialization. This should never be called by the user! @@ -267,7 +266,7 @@ impl PerseusAppBase { plugins: Rc::new(Plugins::new()), // Many users won't need anything fancy in the index view, so we provide a default index_view: DFLT_INDEX_VIEW.to_string(), - _marker: PhantomData + _marker: PhantomData, } } @@ -282,7 +281,9 @@ impl PerseusAppBase { #[allow(unused_mut)] pub fn static_dir(mut self, val: &str) -> Self { #[cfg(not(target_arch = "wasm32"))] - { self.static_dir = val.to_string(); } + { + self.static_dir = val.to_string(); + } self } /// Sets all the app's templates. This takes a vector of boxed functions that return templates. @@ -305,7 +306,9 @@ impl PerseusAppBase { #[allow(unused_mut)] pub fn global_state_creator(mut self, val: GlobalStateCreator) -> Self { #[cfg(not(target_arch = "wasm32"))] - { self.global_state_creator = val; } + { + self.global_state_creator = val; + } self } /// Sets the locales information for the app. The first argument is the default locale (used as a fallback for users with no locale preferences set in their browsers), and @@ -340,7 +343,9 @@ impl PerseusAppBase { #[allow(unused_mut)] pub fn translations_manager(mut self, val: impl Future + 'static) -> Self { #[cfg(not(target_arch = "wasm32"))] - { self.translations_manager = Tm::Full(Box::pin(val)); } + { + self.translations_manager = Tm::Full(Box::pin(val)); + } self } /// Explicitly disables internationalization. You shouldn't ever need to call this, as it's the default, but you may want to if you're writing middleware that doesn't support i18n. @@ -352,7 +357,9 @@ impl PerseusAppBase { }; // All translations manager must implement this function, which is designed for this exact purpose #[cfg(not(target_arch = "wasm32"))] - { self.translations_manager = Tm::Dummy(T::new_dummy()); } + { + self.translations_manager = Tm::Dummy(T::new_dummy()); + } self } /// Sets all the app's static aliases. This takes a map of URLs (e.g. `/file`) to resource paths, relative to the project directory (e.g. `style.css`). @@ -360,7 +367,9 @@ impl PerseusAppBase { #[allow(unused_mut)] pub fn static_aliases(mut self, val: HashMap) -> Self { #[cfg(not(target_arch = "wasm32"))] - { self.static_aliases = val; } + { + self.static_aliases = val; + } self } /// Adds a single static alias (convenience function). This takes a URL path (e.g. `/file`) followed by a path to a resource (which must be within the project directory, e.g. `style.css`). @@ -384,7 +393,9 @@ impl PerseusAppBase { #[allow(unused_mut)] pub fn mutable_store(mut self, val: M) -> Self { #[cfg(not(target_arch = "wasm32"))] - { self.mutable_store = val; } + { + self.mutable_store = val; + } self } /// Sets the immutable store for the app to use. You should almost never need to change this unless you're not working with the CLI. @@ -392,7 +403,9 @@ impl PerseusAppBase { #[allow(unused_mut)] pub fn immutable_store(mut self, val: ImmutableStore) -> Self { #[cfg(not(target_arch = "wasm32"))] - { self.immutable_store = val; } + { + self.immutable_store = val; + } self } /// Sets the index view as a string. This should be used if you're using an `index.html` file or the like. @@ -723,8 +736,8 @@ pub fn PerseusRoot(cx: Scope) -> View { } } -use crate::stores::FsMutableStore; use crate::i18n::FsTranslationsManager; +use crate::stores::FsMutableStore; /// An alias for the usual kind of Perseus app, which uses the filesystem-based mutable store and translations manager. pub type PerseusApp = PerseusAppBase; diff --git a/packages/perseus/src/lib.rs b/packages/perseus/src/lib.rs index 9dcb1355da..d9fb464179 100644 --- a/packages/perseus/src/lib.rs +++ b/packages/perseus/src/lib.rs @@ -46,6 +46,7 @@ mod export; mod i18n; mod init; mod macros; +mod page_data; mod router; #[cfg(not(target_arch = "wasm32"))] mod server; @@ -54,7 +55,6 @@ mod shell; mod template; mod translator; mod utils; -mod page_data; // The rest of this file is devoted to module structuring // Re-exports @@ -76,13 +76,13 @@ pub use sycamore_router::{navigate, navigate_replace, Route}; // TODO Should we // Items that should be available at the root (this should be nearly everything used in a typical Perseus app) pub use crate::error_pages::ErrorPages; -pub use crate::page_data::PageData; pub use crate::errors::{ErrorCause, GenericErrorWithCause}; +pub use crate::page_data::PageData; pub use crate::plugins::{Plugin, PluginAction, Plugins}; #[cfg(target_arch = "wasm32")] pub use crate::shell::checkpoint; #[cfg(not(target_arch = "wasm32"))] -pub use crate::template::{States, HeadFn}; +pub use crate::template::{HeadFn, States}; pub use crate::template::{RenderFnResult, RenderFnResultWithCause, Template}; #[cfg(not(target_arch = "wasm32"))] pub use crate::utils::{cache_fallible_res, cache_res}; @@ -138,10 +138,10 @@ pub mod internal { pub mod export { pub use crate::export::*; } - #[cfg(not(target_arch = "wasm32"))] - pub use crate::utils::get_path_prefix_server; #[cfg(target_arch = "wasm32")] pub use crate::utils::get_path_prefix_client; + #[cfg(not(target_arch = "wasm32"))] + pub use crate::utils::get_path_prefix_server; /// Internal utilities for logging. These are just re-exports so that users don't have to have `web_sys` and `wasm_bindgen` to use `web_log!`. #[cfg(target_arch = "wasm32")] pub mod log { diff --git a/packages/perseus/src/router/router_component.rs b/packages/perseus/src/router/router_component.rs index 29b8716d6b..a0b5cd18dd 100644 --- a/packages/perseus/src/router/router_component.rs +++ b/packages/perseus/src/router/router_component.rs @@ -7,12 +7,12 @@ use crate::{ router::{PerseusRoute, RouteInfo, RouteVerdict}, shell::{app_shell, get_initial_state, InitialState, ShellProps}, }, - templates::{RenderCtx, RouterLoadState, RouterState, TemplateNodeType, TemplateMap}, + templates::{RenderCtx, RouterLoadState, RouterState, TemplateMap, TemplateNodeType}, DomNode, ErrorPages, Html, }; use std::cell::RefCell; -use std::rc::Rc; use std::collections::HashMap; +use std::rc::Rc; use sycamore::{ prelude::{component, create_effect, create_signal, view, NodeRef, ReadSignal, Scope, View}, Prop, @@ -160,7 +160,7 @@ pub fn perseus_router( verdict: RouteVerdict::NotFound, templates, render_cfg, - locales: locales.clone() + locales: locales.clone(), }; // Get the root that the server will have injected initial load content into diff --git a/packages/perseus/src/server/render.rs b/packages/perseus/src/server/render.rs index 62a5abb7c3..aebae700bf 100644 --- a/packages/perseus/src/server/render.rs +++ b/packages/perseus/src/server/render.rs @@ -1,10 +1,10 @@ -use crate::PageData; use crate::errors::*; use crate::i18n::TranslationsManager; use crate::stores::{ImmutableStore, MutableStore}; use crate::template::{PageProps, States, Template, TemplateMap}; use crate::translator::Translator; use crate::utils::decode_time_str; +use crate::PageData; use crate::Request; use crate::SsrNode; use chrono::{DateTime, Utc}; diff --git a/packages/perseus/src/shell.rs b/packages/perseus/src/shell.rs index 262495340d..67c232c1d8 100644 --- a/packages/perseus/src/shell.rs +++ b/packages/perseus/src/shell.rs @@ -2,10 +2,10 @@ use crate::error_pages::ErrorPageData; use crate::errors::*; use crate::i18n::ClientTranslationsManager; use crate::router::{RouteVerdict, RouterLoadState, RouterState}; -use crate::PageData; use crate::template::{PageProps, Template, TemplateNodeType}; use crate::utils::get_path_prefix_client; use crate::ErrorPages; +use crate::PageData; use fmterr::fmt_err; use std::cell::RefCell; use std::collections::HashMap; diff --git a/packages/perseus/src/stores/immutable.rs b/packages/perseus/src/stores/immutable.rs index 18aa282f6a..30424a6f05 100644 --- a/packages/perseus/src/stores/immutable.rs +++ b/packages/perseus/src/stores/immutable.rs @@ -1,4 +1,3 @@ - #[cfg(not(target_arch = "wasm32"))] use crate::errors::*; #[cfg(not(target_arch = "wasm32"))] diff --git a/packages/perseus/src/template/core.rs b/packages/perseus/src/template/core.rs index 8609157a12..5bd464f4cd 100644 --- a/packages/perseus/src/template/core.rs +++ b/packages/perseus/src/template/core.rs @@ -1,10 +1,10 @@ // This file contains logic to define how templates are rendered +#[cfg(not(target_arch = "wasm32"))] +use super::default_headers; use super::PageProps; #[cfg(not(target_arch = "wasm32"))] use super::{RenderCtx, States}; -#[cfg(not(target_arch = "wasm32"))] -use super::default_headers; use crate::errors::*; #[cfg(not(target_arch = "wasm32"))] use crate::make_async_trait; @@ -153,7 +153,7 @@ impl std::fmt::Debug for Template { .field("template", &"TemplateFn") .field("head", &"HeadFn") .field("set_headers", &"SetHeadersFn") - // TODO Server-specific stuff + // TODO Server-specific stuff .finish() } } diff --git a/packages/perseus/src/template/render_ctx.rs b/packages/perseus/src/template/render_ctx.rs index d4fd3d78ed..dadbff26af 100644 --- a/packages/perseus/src/template/render_ctx.rs +++ b/packages/perseus/src/template/render_ctx.rs @@ -1,6 +1,5 @@ use crate::errors::*; use crate::router::{RouterLoadState, RouterState}; -#[cfg(all(feature = "live-reload", debug_assertions))] use crate::state::{ AnyFreeze, Freeze, FrozenApp, GlobalState, MakeRx, MakeUnrx, PageStateStore, ThawPrefs, }; From 9ef2b5085be98074fbfbd852e46b5be06e706d72 Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Mon, 13 Jun 2022 09:36:47 +1000 Subject: [PATCH 08/37] fix: fixed some macro and example errors --- .../core/idb_freezing/src/templates/about.rs | 10 +++++++--- .../core/idb_freezing/src/templates/index.rs | 16 +++++++++++----- packages/perseus-macro/src/template.rs | 2 +- packages/perseus-macro/src/template_rx.rs | 2 +- packages/perseus/src/engine/get_op.rs | 2 +- 5 files changed, 21 insertions(+), 11 deletions(-) diff --git a/examples/core/idb_freezing/src/templates/about.rs b/examples/core/idb_freezing/src/templates/about.rs index 7b1bf064f6..37a11260b1 100644 --- a/examples/core/idb_freezing/src/templates/about.rs +++ b/examples/core/idb_freezing/src/templates/about.rs @@ -1,4 +1,3 @@ -use perseus::state::{Freeze, IdbFrozenStateStore}; use perseus::{Html, Template}; use sycamore::prelude::*; @@ -8,6 +7,9 @@ use crate::global_state::*; pub fn about_page<'a, G: Html>(cx: Scope<'a>, _: (), global_state: AppStateRx<'a>) -> View { // This is not part of our data model let freeze_status = create_signal(cx, String::new()); + // It's faster to get this only once and rely on reactivity + // But it's unused when this runs on the server-side because of the target-gate below + #[allow(unused_variables)] let render_ctx = perseus::get_render_ctx!(cx); view! { cx, @@ -18,9 +20,11 @@ pub fn about_page<'a, G: Html>(cx: Scope<'a>, _: (), global_state: AppStateRx<'a br() // We'll let the user freeze from here to demonstrate that the frozen state also navigates back to the last route - button(id = "freeze_button", on:click = move |_| + button(id = "freeze_button", on:click = move |_| { // The IndexedDB API is asynchronous, so we'll spawn a future + #[cfg(target_arch = "wasm32")] perseus::spawn_local_scoped(cx, async move { + use perseus::state::{IdbFrozenStateStore, Freeze}; // We do this here (rather than when we get the render context) so that it's updated whenever we press the button let frozen_state = render_ctx.freeze(); let idb_store = match IdbFrozenStateStore::new().await { @@ -35,7 +39,7 @@ pub fn about_page<'a, G: Html>(cx: Scope<'a>, _: (), global_state: AppStateRx<'a Err(_) => freeze_status.set("Error.".to_string()) }; }) - ) { "Freeze to IndexedDB" } + }) { "Freeze to IndexedDB" } p { (freeze_status.get()) } } } diff --git a/examples/core/idb_freezing/src/templates/index.rs b/examples/core/idb_freezing/src/templates/index.rs index 683bf7fb6f..31af6aba65 100644 --- a/examples/core/idb_freezing/src/templates/index.rs +++ b/examples/core/idb_freezing/src/templates/index.rs @@ -1,4 +1,3 @@ -use perseus::state::{Freeze, IdbFrozenStateStore, PageThawPrefs, ThawPrefs}; use perseus::{Html, RenderFnResultWithCause, Template}; use sycamore::prelude::*; @@ -18,6 +17,9 @@ pub fn index_page<'a, G: Html>( // This is not part of our data model let freeze_status = create_signal(cx, String::new()); let thaw_status = create_signal(cx, String::new()); + // It's faster to get this only once and rely on reactivity + // But it's unused when this runs on the server-side because of the target-gate below + #[allow(unused_variables)] let render_ctx = perseus::get_render_ctx!(cx); view! { cx, @@ -31,9 +33,11 @@ pub fn index_page<'a, G: Html>( a(href = "about", id = "about-link") { "About" } br() - button(id = "freeze_button", on:click = move |_| + button(id = "freeze_button", on:click = move |_| { // The IndexedDB API is asynchronous, so we'll spawn a future + #[cfg(target_arch = "wasm32")] // The freezing types are only available in the browser perseus::spawn_local_scoped(cx, async { + use perseus::state::{IdbFrozenStateStore, Freeze, PageThawPrefs, ThawPrefs}; // We do this here (rather than when we get the render context) so that it's updated whenever we press the button let frozen_state = render_ctx.freeze(); let idb_store = match IdbFrozenStateStore::new().await { @@ -48,12 +52,14 @@ pub fn index_page<'a, G: Html>( Err(_) => freeze_status.set("Error.".to_string()) }; }) - ) { "Freeze to IndexedDB" } + }) { "Freeze to IndexedDB" } p { (freeze_status.get()) } - button(id = "thaw_button", on:click = move |_| + button(id = "thaw_button", on:click = move |_| { // The IndexedDB API is asynchronous, so we'll spawn a future + #[cfg(target_arch = "wasm32")] // The freezing types are only available in the browser perseus::spawn_local_scoped(cx, async move { + use perseus::state::{IdbFrozenStateStore, Freeze, PageThawPrefs, ThawPrefs}; let idb_store = match IdbFrozenStateStore::new().await { Ok(idb_store) => idb_store, Err(_) => { @@ -79,7 +85,7 @@ pub fn index_page<'a, G: Html>( Err(_) => thaw_status.set("Error.".to_string()) } }) - ) { "Thaw from IndexedDB" } + }) { "Thaw from IndexedDB" } p { (thaw_status.get()) } } } diff --git a/packages/perseus-macro/src/template.rs b/packages/perseus-macro/src/template.rs index bd38da3b02..1297c6a9f0 100644 --- a/packages/perseus-macro/src/template.rs +++ b/packages/perseus-macro/src/template.rs @@ -162,7 +162,7 @@ pub fn template_impl(input: TemplateFn) -> TokenStream { #block } - #component_name(cx, ()) + #component_name(cx) } } } diff --git a/packages/perseus-macro/src/template_rx.rs b/packages/perseus-macro/src/template_rx.rs index 28d1ecaa7e..2ee9537f68 100644 --- a/packages/perseus-macro/src/template_rx.rs +++ b/packages/perseus-macro/src/template_rx.rs @@ -261,7 +261,7 @@ pub fn template_impl(input: TemplateFn) -> TokenStream { #block } - #component_name(cx, ()) + #component_name(cx) } }, // This template takes its own state and global state diff --git a/packages/perseus/src/engine/get_op.rs b/packages/perseus/src/engine/get_op.rs index 4c2b17979a..57c4dbe618 100644 --- a/packages/perseus/src/engine/get_op.rs +++ b/packages/perseus/src/engine/get_op.rs @@ -15,7 +15,7 @@ pub fn get_op() -> Option { } else { Some(EngineOperation::Serve) } - } + }; } }; From 39389b18228077e57bbeccf695b6d0140859a59d Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Mon, 13 Jun 2022 09:40:03 +1000 Subject: [PATCH 09/37] style: appeased `clippy` --- packages/perseus-cli/src/build.rs | 2 +- packages/perseus-cli/src/serve.rs | 2 +- packages/perseus/src/engine/export.rs | 4 ++-- packages/perseus/src/engine/serve.rs | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/perseus-cli/src/build.rs b/packages/perseus-cli/src/build.rs index 2b17e7e43c..98714aa535 100644 --- a/packages/perseus-cli/src/build.rs +++ b/packages/perseus-cli/src/build.rs @@ -113,7 +113,7 @@ pub fn build_internal( pub fn build(dir: PathBuf, opts: BuildOpts) -> Result { let spinners = MultiProgress::new(); - let (sg_thread, wb_thread) = build_internal(dir.clone(), &spinners, 2, opts.release)?; + let (sg_thread, wb_thread) = build_internal(dir, &spinners, 2, opts.release)?; let sg_res = sg_thread .join() .map_err(|_| ExecutionError::ThreadWaitFailed)??; diff --git a/packages/perseus-cli/src/serve.rs b/packages/perseus-cli/src/serve.rs index f260aa5f7f..06d1a48177 100644 --- a/packages/perseus-cli/src/serve.rs +++ b/packages/perseus-cli/src/serve.rs @@ -58,7 +58,7 @@ fn build_server( // We deliberately insert the spinner at the end of the list let sb_spinner = spinners.insert(num_steps - 1, ProgressBar::new_spinner()); let sb_spinner = cfg_spinner(sb_spinner, &sb_msg); - let sb_target = dir.clone(); + let sb_target = dir; let sb_thread = spawn_thread(move || { let (stdout, _stderr) = handle_exit_code!(run_stage( vec![&format!( diff --git a/packages/perseus/src/engine/export.rs b/packages/perseus/src/engine/export.rs index a1cc89501c..133169d6fb 100644 --- a/packages/perseus/src/engine/export.rs +++ b/packages/perseus/src/engine/export.rs @@ -150,7 +150,7 @@ fn copy_static_aliases( if let Err(err) = copy_dir(&from, &to, &CopyOptions::new()) { let err = EngineError::CopyStaticAliasDirErr { source: err, - to: to.to_string(), + to, from: path.to_string(), }; let err = Rc::new(err); @@ -164,7 +164,7 @@ fn copy_static_aliases( } else if let Err(err) = fs::copy(&from, &to) { let err = EngineError::CopyStaticAliasFileError { source: err, - to: to.to_string(), + to, from: path.to_string(), }; let err = Rc::new(err); diff --git a/packages/perseus/src/engine/serve.rs b/packages/perseus/src/engine/serve.rs index 4008c5b5c0..f34ec7f8c3 100644 --- a/packages/perseus/src/engine/serve.rs +++ b/packages/perseus/src/engine/serve.rs @@ -58,9 +58,9 @@ pub fn get_props( .before_serve .run((), plugins.get_plugin_data()); - let static_dir_path = app.get_static_dir().to_string(); + let static_dir_path = app.get_static_dir(); - let app_root = app.get_root().to_string(); + let app_root = app.get_root(); let immutable_store = app.get_immutable_store(); let index_view_str = app.get_index_view_str(); // By the time this binary is being run, the app has already been built be the CLI (hopefully!), so we can depend on access to the render config From c808915a5553fa45b4596bc3e4edd43d3ac6bd23 Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Mon, 13 Jun 2022 09:42:09 +1000 Subject: [PATCH 10/37] chore: updated bonnie checking script --- bonnie.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bonnie.toml b/bonnie.toml index 2952fee551..074c9bdb53 100644 --- a/bonnie.toml +++ b/bonnie.toml @@ -143,7 +143,10 @@ site.subcommands.run.desc = "runs the website without watching for changes" check.cmd = [ "cargo check --all", "cargo fmt --all -- --check", - "cargo clippy --all" + "cargo clippy --all", + # We also have to check the `perseus` package in particular on Wasm (the examples are handled by the E2E tests) + "cd packages/perseus", + "cargo check --target wasm32-unknown-unknown" ] check.desc = "checks code for formatting errors and the like" From 3354c2b522b3dffcc17b155828cac5e45c4bdd18 Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Mon, 13 Jun 2022 09:43:48 +1000 Subject: [PATCH 11/37] chore: deleted `.perseus/`! --- examples/core/basic/.perseus.old/.gitignore | 2 - examples/core/basic/.perseus.old/Cargo.toml | 29 --- .../basic/.perseus.old/builder/Cargo.toml | 36 --- .../.perseus.old/builder/src/bin/build.rs | 80 ------- .../.perseus.old/builder/src/bin/export.rs | 207 ------------------ .../builder/src/bin/export_error_page.rs | 93 -------- .../.perseus.old/builder/src/bin/tinker.rs | 24 -- .../core/basic/.perseus.old/server/Cargo.toml | 34 --- .../basic/.perseus.old/server/src/main.rs | 171 --------------- examples/core/basic/.perseus.old/src/lib.rs | 67 ------ 10 files changed, 743 deletions(-) delete mode 100644 examples/core/basic/.perseus.old/.gitignore delete mode 100644 examples/core/basic/.perseus.old/Cargo.toml delete mode 100644 examples/core/basic/.perseus.old/builder/Cargo.toml delete mode 100644 examples/core/basic/.perseus.old/builder/src/bin/build.rs delete mode 100644 examples/core/basic/.perseus.old/builder/src/bin/export.rs delete mode 100644 examples/core/basic/.perseus.old/builder/src/bin/export_error_page.rs delete mode 100644 examples/core/basic/.perseus.old/builder/src/bin/tinker.rs delete mode 100644 examples/core/basic/.perseus.old/server/Cargo.toml delete mode 100644 examples/core/basic/.perseus.old/server/src/main.rs delete mode 100644 examples/core/basic/.perseus.old/src/lib.rs diff --git a/examples/core/basic/.perseus.old/.gitignore b/examples/core/basic/.perseus.old/.gitignore deleted file mode 100644 index 5076b767e0..0000000000 --- a/examples/core/basic/.perseus.old/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -dist/ -target/ diff --git a/examples/core/basic/.perseus.old/Cargo.toml b/examples/core/basic/.perseus.old/Cargo.toml deleted file mode 100644 index c6bcedfaf9..0000000000 --- a/examples/core/basic/.perseus.old/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -# This crate defines the user's app in terms that Wasm can understand, making development significantly simpler. -# IMPORTANT: spacing matters in this file for runtime replacements, do NOT change it! - -[package] -name = "perseus-engine" -version = "0.4.0-beta.1" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -# We alias here because the package name will change based on whatever's in the user's manifest -app = { package = "perseus-example-basic", path = "../" } - -perseus = { path = "../../../../packages/perseus" } -sycamore = { version = "=0.8.0-beta.6", features = ["ssr"] } -sycamore-router = "=0.8.0-beta.6" -web-sys = { version = "0.3", features = ["Event", "Headers", "Request", "RequestInit", "RequestMode", "Response", "ReadableStream", "Window"] } -wasm-bindgen = "0.2" -wasm-bindgen-futures = "0.4" -console_error_panic_hook = "0.1.6" - -# This section is needed for Wasm Pack (which we use instead of Trunk for flexibility) -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -# This changes a few things to support running as a standalone server binary (this is set by `perseus deploy`, do NOT invoke this manually!) -standalone = [] diff --git a/examples/core/basic/.perseus.old/builder/Cargo.toml b/examples/core/basic/.perseus.old/builder/Cargo.toml deleted file mode 100644 index d490582e1f..0000000000 --- a/examples/core/basic/.perseus.old/builder/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -# This crate defines the build process for Perseus -# This used to be part of the root crate in the engine, but it was moved out of there so feature gating could be different across the server, builder, and client -# IMPORTANT: spacing matters in this file for runtime replacements, do NOT change it! - -[package] -name = "perseus-engine-builder" -version = "0.4.0-beta.1" -edition = "2021" -default-run = "perseus-builder" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -perseus-engine = { path = "../" } -perseus = { path = "../../../../../packages/perseus", features = [ "tinker-plugins", "server-side" ] } -futures = "0.3" -fs_extra = "1" -tokio = { version = "1", features = [ "macros", "rt-multi-thread" ] } -fmterr = "0.1" - -# We define a binary for building, serving, and doing both -[[bin]] -name = "perseus-builder" -path = "src/bin/build.rs" - -[[bin]] -name = "perseus-exporter" -path = "src/bin/export.rs" - -[[bin]] -name = "perseus-tinker" # Yes, the noun is 'tinker', not 'tinkerer' -path = "src/bin/tinker.rs" - -[[bin]] -name = "perseus-error-page-exporter" -path = "src/bin/export_error_page.rs" diff --git a/examples/core/basic/.perseus.old/builder/src/bin/build.rs b/examples/core/basic/.perseus.old/builder/src/bin/build.rs deleted file mode 100644 index feb781c063..0000000000 --- a/examples/core/basic/.perseus.old/builder/src/bin/build.rs +++ /dev/null @@ -1,80 +0,0 @@ -use fmterr::fmt_err; -use perseus::{ - internal::build::{build_app, BuildProps}, - PluginAction, SsrNode, -}; -use perseus_engine as app; - -#[tokio::main] -async fn main() { - let exit_code = real_main().await; - std::process::exit(exit_code) -} - -async fn real_main() -> i32 { - // We want to be working in the root of `.perseus/` - std::env::set_current_dir("../").unwrap(); - let app = app::main::(); - let plugins = app.get_plugins(); - - plugins - .functional_actions - .build_actions - .before_build - .run((), plugins.get_plugin_data()); - - let immutable_store = app.get_immutable_store(); - let mutable_store = app.get_mutable_store(); - let locales = app.get_locales(); - // Generate the global state - let gsc = app.get_global_state_creator(); - let global_state = match gsc.get_build_state().await { - Ok(global_state) => global_state, - Err(err) => { - let err_msg = fmt_err(&err); - plugins - .functional_actions - .build_actions - .after_failed_global_state_creation - .run(err, plugins.get_plugin_data()); - eprintln!("{}", err_msg); - return 1; - } - }; - - // Build the site for all the common locales (done in parallel) - // All these parameters can be modified by `PerseusApp` and plugins, so there's no point in having a plugin opportunity here - let templates_map = app.get_templates_map(); - - // We have to get the translations manager last, because it consumes everything - let translations_manager = app.get_translations_manager().await; - - let res = build_app(BuildProps { - templates: &templates_map, - locales: &locales, - immutable_store: &immutable_store, - mutable_store: &mutable_store, - translations_manager: &translations_manager, - global_state: &global_state, - exporting: false, - }) - .await; - if let Err(err) = res { - let err_msg = fmt_err(&err); - plugins - .functional_actions - .build_actions - .after_failed_build - .run(err, plugins.get_plugin_data()); - eprintln!("{}", err_msg); - 1 - } else { - plugins - .functional_actions - .build_actions - .after_successful_build - .run((), plugins.get_plugin_data()); - println!("Static generation successfully completed!"); - 0 - } -} diff --git a/examples/core/basic/.perseus.old/builder/src/bin/export.rs b/examples/core/basic/.perseus.old/builder/src/bin/export.rs deleted file mode 100644 index 8020744f74..0000000000 --- a/examples/core/basic/.perseus.old/builder/src/bin/export.rs +++ /dev/null @@ -1,207 +0,0 @@ -use fmterr::fmt_err; -use fs_extra::dir::{copy as copy_dir, CopyOptions}; -use perseus::{ - internal::{ - build::{build_app, BuildProps}, - export::{export_app, ExportProps}, - get_path_prefix_server, - }, - PerseusApp, PluginAction, SsrNode, -}; -use perseus_engine as app; -use std::fs; -use std::path::PathBuf; - -#[tokio::main] -async fn main() { - let exit_code = real_main().await; - std::process::exit(exit_code) -} - -async fn real_main() -> i32 { - // We want to be working in the root of `.perseus/` - std::env::set_current_dir("../").unwrap(); - let app = app::main::(); - - let plugins = app.get_plugins(); - - // Building and exporting must be sequential, but that can be done in parallel with static directory/alias copying - let exit_code = build_and_export().await; - if exit_code != 0 { - return exit_code; - } - // After that's done, we can do two copy operations in parallel at least - let exit_code_1 = tokio::task::spawn_blocking(copy_static_dir); - let exit_code_2 = tokio::task::spawn_blocking(copy_static_aliases); - // These errors come from any panics in the threads, which should be propagated up to a panic in the main thread in this case - exit_code_1.await.unwrap(); - exit_code_2.await.unwrap(); - - plugins - .functional_actions - .export_actions - .after_successful_export - .run((), plugins.get_plugin_data()); - println!("Static exporting successfully completed!"); - 0 -} - -async fn build_and_export() -> i32 { - let app = app::main::(); - let plugins = app.get_plugins(); - - plugins - .functional_actions - .build_actions - .before_build - .run((), plugins.get_plugin_data()); - - let immutable_store = app.get_immutable_store(); - // We don't need this in exporting, but the build process does - let mutable_store = app.get_mutable_store(); - let locales = app.get_locales(); - // Generate the global state - let gsc = app.get_global_state_creator(); - let global_state = match gsc.get_build_state().await { - Ok(global_state) => global_state, - Err(err) => { - let err_msg = fmt_err(&err); - plugins - .functional_actions - .export_actions - .after_failed_global_state_creation - .run(err, plugins.get_plugin_data()); - eprintln!("{}", err_msg); - return 1; - } - }; - let templates_map = app.get_templates_map(); - let index_view_str = app.get_index_view_str(); - let root_id = app.get_root(); - // This consumes `self`, so we get it finally - let translations_manager = app.get_translations_manager().await; - - // Build the site for all the common locales (done in parallel), denying any non-exportable features - // We need to build and generate those artifacts before we can proceed on to exporting - let build_res = build_app(BuildProps { - templates: &templates_map, - locales: &locales, - immutable_store: &immutable_store, - mutable_store: &mutable_store, - translations_manager: &translations_manager, - global_state: &global_state, - exporting: true, - }) - .await; - if let Err(err) = build_res { - let err_msg = fmt_err(&err); - plugins - .functional_actions - .export_actions - .after_failed_build - .run(err, plugins.get_plugin_data()); - eprintln!("{}", err_msg); - return 1; - } - plugins - .functional_actions - .export_actions - .after_successful_build - .run((), plugins.get_plugin_data()); - // The app has now been built, so we can safely instantiate the HTML shell (which needs access to the render config, generated in the above build step) - // It doesn't matter if the type parameters here are wrong, this function doesn't use them - let index_view = - PerseusApp::get_html_shell(index_view_str, &root_id, &immutable_store, &plugins).await; - // Turn the build artifacts into self-contained static files - let export_res = export_app(ExportProps { - templates: &templates_map, - html_shell: index_view, - locales: &locales, - immutable_store: &immutable_store, - translations_manager: &translations_manager, - path_prefix: get_path_prefix_server(), - global_state: &global_state, - }) - .await; - if let Err(err) = export_res { - let err_msg = fmt_err(&err); - plugins - .functional_actions - .export_actions - .after_failed_export - .run(err, plugins.get_plugin_data()); - eprintln!("{}", err_msg); - return 1; - } - - 0 -} - -fn copy_static_dir() -> i32 { - let app = app::main::(); - let plugins = app.get_plugins(); - // Loop through any static aliases and copy them in too - // Unlike with the server, these could override pages! - // We'll copy from the alias to the path (it could be a directory or a file) - // Remember: `alias` has a leading `/`! - for (alias, path) in app.get_static_aliases() { - let from = PathBuf::from(path); - let to = format!("dist/exported{}", alias); - - if from.is_dir() { - if let Err(err) = copy_dir(&from, &to, &CopyOptions::new()) { - let err_msg = format!( - "couldn't copy static alias directory from '{}' to '{}': '{}'", - from.to_str().map(|s| s.to_string()).unwrap(), - to, - fmt_err(&err) - ); - plugins - .functional_actions - .export_actions - .after_failed_static_alias_dir_copy - .run(err.to_string(), plugins.get_plugin_data()); - eprintln!("{}", err_msg); - return 1; - } - } else if let Err(err) = fs::copy(&from, &to) { - let err_msg = format!( - "couldn't copy static alias file from '{}' to '{}': '{}'", - from.to_str().map(|s| s.to_string()).unwrap(), - to, - fmt_err(&err) - ); - plugins - .functional_actions - .export_actions - .after_failed_static_alias_file_copy - .run(err, plugins.get_plugin_data()); - eprintln!("{}", err_msg); - return 1; - } - } - - 0 -} - -fn copy_static_aliases() -> i32 { - let app = app::main::(); - let plugins = app.get_plugins(); - // Copy the `static` directory into the export package if it exists - // If the user wants extra, they can use static aliases, plugins are unnecessary here - let static_dir = PathBuf::from("../static"); - if static_dir.exists() { - if let Err(err) = copy_dir(&static_dir, "dist/exported/.perseus/", &CopyOptions::new()) { - let err_msg = format!("couldn't copy static directory: '{}'", fmt_err(&err)); - plugins - .functional_actions - .export_actions - .after_failed_static_copy - .run(err.to_string(), plugins.get_plugin_data()); - eprintln!("{}", err_msg); - return 1; - } - } - - 0 -} diff --git a/examples/core/basic/.perseus.old/builder/src/bin/export_error_page.rs b/examples/core/basic/.perseus.old/builder/src/bin/export_error_page.rs deleted file mode 100644 index 9e36393ce1..0000000000 --- a/examples/core/basic/.perseus.old/builder/src/bin/export_error_page.rs +++ /dev/null @@ -1,93 +0,0 @@ -use fmterr::fmt_err; -use perseus::{internal::serve::build_error_page, PerseusApp, PluginAction, SsrNode}; -use perseus_engine as app; -use std::{env, fs}; - -#[tokio::main] -async fn main() { - let exit_code = real_main().await; - std::process::exit(exit_code) -} - -async fn real_main() -> i32 { - // We want to be working in the root of `.perseus/` - env::set_current_dir("../").unwrap(); - let app = app::main::(); - - let plugins = app.get_plugins(); - - let error_pages = app.get_error_pages(); - // Prepare the HTML shell - let index_view_str = app.get_index_view_str(); - let root_id = app.get_root(); - let immutable_store = app.get_immutable_store(); - // We assume the app has already been built before running this (so the render config must be available) - // It doesn't matter if the type parameters here are wrong, this function doesn't use them - let html_shell = - PerseusApp::get_html_shell(index_view_str, &root_id, &immutable_store, &plugins).await; - // Get the error code to build from the arguments to this executable - let args = env::args().collect::>(); - let err_code_to_build_for = match args.get(1) { - Some(arg) => match arg.parse::() { - Ok(err_code) => err_code, - Err(_) => { - eprintln!("You must provide a valid number as an HTTP error code."); - return 1; - } - }, - None => { - eprintln!("You must provide an HTTP error code to export an error page for."); - return 1; - } - }; - // Get the output to write to from the second argument - let output = match args.get(2) { - Some(output) => output, - None => { - eprintln!("You must provide an output location for the exported error page."); - return 1; - } - }; - plugins - .functional_actions - .export_error_page_actions - .before_export_error_page - .run( - (err_code_to_build_for, output.to_string()), - plugins.get_plugin_data(), - ); - - // Build that error page as the server does - let err_page_str = build_error_page( - "", - err_code_to_build_for, - "", - None, - &error_pages, - &html_shell, - ); - - // Write that to the mandatory second argument (the output location) - // We'll move out of `.perseus/` first though - env::set_current_dir("../").unwrap(); - match fs::write(&output, err_page_str) { - Ok(_) => (), - Err(err) => { - eprintln!("{}", fmt_err(&err)); - plugins - .functional_actions - .export_error_page_actions - .after_failed_write - .run((err, output.to_string()), plugins.get_plugin_data()); - return 1; - } - }; - - plugins - .functional_actions - .export_error_page_actions - .after_successful_export_error_page - .run((), plugins.get_plugin_data()); - println!("Static exporting successfully completed!"); - 0 -} diff --git a/examples/core/basic/.perseus.old/builder/src/bin/tinker.rs b/examples/core/basic/.perseus.old/builder/src/bin/tinker.rs deleted file mode 100644 index ff981e2f85..0000000000 --- a/examples/core/basic/.perseus.old/builder/src/bin/tinker.rs +++ /dev/null @@ -1,24 +0,0 @@ -use perseus::{plugins::PluginAction, SsrNode}; -use perseus_engine as app; - -fn main() { - let exit_code = real_main(); - std::process::exit(exit_code) -} - -fn real_main() -> i32 { - // We want to be working in the root of `.perseus/` - std::env::set_current_dir("../").unwrap(); - - let plugins = app::main::().get_plugins(); - // Run all the tinker actions - // Note: this is deliberately synchronous, tinker actions that need a multithreaded async runtime should probably - // be making their own engines! - plugins - .functional_actions - .tinker - .run((), plugins.get_plugin_data()); - - println!("Tinkering complete!"); - 0 -} diff --git a/examples/core/basic/.perseus.old/server/Cargo.toml b/examples/core/basic/.perseus.old/server/Cargo.toml deleted file mode 100644 index cf27923545..0000000000 --- a/examples/core/basic/.perseus.old/server/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -# This crate defines the user's app in terms that Wasm can understand, making development significantly simpler. -# IMPORTANT: spacing matters in this file for runtime replacements, do NOT change it! - -[package] -name = "perseus-engine-server" -version = "0.4.0-beta.1" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -perseus = { path = "../../../../../packages/perseus", features = [ "server-side" ] } -perseus-actix-web = { path = "../../../../../packages/perseus-actix-web", optional = true } -perseus-warp = { path = "../../../../../packages/perseus-warp", optional = true } -perseus-axum = { path = "../../../../../packages/perseus-axum", optional = true } -perseus-engine = { path = "../" } -actix-web = { version = "=4.0.0-rc.3", optional = true } -actix-http = { version = "=3.0.0-rc.2", optional = true } # Without this, Actix can introduce breaking changes in a dependency tree -# actix-router = { version = "=0.5.0-rc.3", optional = true } -futures = "0.3" -warp = { package = "warp-fix-171", version = "0.3", optional = true } -tokio = { version = "1", optional = true, features = [ "macros", "rt-multi-thread" ] } # We don't need this for Actix Web -axum = { version = "0.5", optional = true } - -# This binary can use any of the server integrations -[features] -integration-actix-web = [ "perseus-actix-web", "actix-web", "actix-http" ] -integration-warp = [ "perseus-warp", "warp", "tokio" ] -integration-axum = [ "perseus-axum", "axum", "tokio" ] - -default = [ "integration-warp" ] - -# This makes the binary work on its own, and is enabled by `perseus deploy` (do NOT invoke this manually!) -standalone = [ "perseus/standalone", "perseus-engine/standalone" ] diff --git a/examples/core/basic/.perseus.old/server/src/main.rs b/examples/core/basic/.perseus.old/server/src/main.rs deleted file mode 100644 index 51913506ac..0000000000 --- a/examples/core/basic/.perseus.old/server/src/main.rs +++ /dev/null @@ -1,171 +0,0 @@ -use futures::executor::block_on; -use perseus::internal::i18n::TranslationsManager; -use perseus::internal::serve::{ServerOptions, ServerProps}; -use perseus::plugins::PluginAction; -use perseus::stores::MutableStore; -use perseus::PerseusApp; -use perseus::SsrNode; -use perseus_engine as app; -use std::env; -use std::fs; - -// This server executable can be run in two modes: -// dev: inside `.perseus/server/src/main.rs`, works with that file structure -// prod: as a standalone executable with a `dist/` directory as a sibling - -// Integration: Actix Web -#[cfg(feature = "integration-actix-web")] -#[actix_web::main] -async fn main() -> std::io::Result<()> { - println!("WARNING: The Actix Web integration uses a beta version of Actix Web, and is considered unstable. It is not recommended for production usage."); - - use actix_web::{App, HttpServer}; - use perseus_actix_web::configurer; - - let is_standalone = get_standalone_and_act(); - let (host, port) = get_host_and_port(); - - HttpServer::new(move || App::new().configure(block_on(configurer(get_props(is_standalone))))) - .bind((host, port))? - .run() - .await -} - -// Integration: Warp -#[cfg(feature = "integration-warp")] -#[tokio::main] -async fn main() { - use perseus_warp::perseus_routes; - use std::net::SocketAddr; - - let is_standalone = get_standalone_and_act(); - let props = get_props(is_standalone); - let (host, port) = get_host_and_port(); - let addr: SocketAddr = format!("{}:{}", host, port) - .parse() - .expect("Invalid address provided to bind to."); - let routes = block_on(perseus_routes(props)); - warp::serve(routes).run(addr).await; -} - -// Integration: Axum -#[cfg(feature = "integration-axum")] -#[tokio::main] -async fn main() { - use perseus_axum::get_router; - use std::net::SocketAddr; - - let is_standalone = get_standalone_and_act(); - let props = get_props(is_standalone); - let (host, port) = get_host_and_port(); - let addr: SocketAddr = format!("{}:{}", host, port) - .parse() - .expect("Invalid address provided to bind to."); - let app = block_on(get_router(props)); - axum::Server::bind(&addr) - .serve(app.into_make_service()) - .await - .unwrap(); -} - -/// Determines whether or not we're operating in standalone mode, and acts accordingly. This MUST be executed in the parent thread, as it switches the current directory. -fn get_standalone_and_act() -> bool { - // So we don't have to define a different `FsConfigManager` just for the server, we shift the execution context to the same level as everything else - // The server has to be a separate crate because otherwise the dependencies don't work with Wasm bundling - // If we're not running as a standalone binary, assume we're running in dev mode under `.perseus/` - if !cfg!(feature = "standalone") { - env::set_current_dir("../").unwrap(); - false - } else { - // If we are running as a standalone binary, we have no idea where we're being executed from (#63), so we should set the working directory to be the same as the binary location - let binary_loc = env::current_exe().unwrap(); - let binary_dir = binary_loc.parent().unwrap(); // It's a file, there's going to be a parent if we're working on anything close to sanity - env::set_current_dir(binary_dir).unwrap(); - true - } -} - -/// Gets the host and port to serve on. -fn get_host_and_port() -> (String, u16) { - // We have to use two sets of environment variables until v0.4.0 - // TODO Remove the old environment variables in v0.4.0 - let host_old = env::var("HOST"); - let port_old = env::var("PORT"); - let host = env::var("PERSEUS_HOST"); - let port = env::var("PERSEUS_PORT"); - - let host = host.unwrap_or_else(|_| host_old.unwrap_or_else(|_| "127.0.0.1".to_string())); - let port = port - .unwrap_or_else(|_| port_old.unwrap_or_else(|_| "8080".to_string())) - .parse::() - .expect("Port must be a number."); - - (host, port) -} - -/// Gets the properties to pass to the server. -fn get_props(is_standalone: bool) -> ServerProps { - let app = app::main::(); - let plugins = app.get_plugins(); - - plugins - .functional_actions - .server_actions - .before_serve - .run((), plugins.get_plugin_data()); - - // This allows us to operate inside `.perseus/` and as a standalone binary in production - let static_dir_path = if is_standalone { - "./static" - } else { - "../static" - }; - - let immutable_store = app.get_immutable_store(); - let locales = app.get_locales(); - let app_root = app.get_root(); - let static_aliases = app.get_static_aliases(); - let templates_map = app.get_atomic_templates_map(); - let error_pages = app.get_error_pages(); - let index_view_str = app.get_index_view_str(); - // Generate the global state - let global_state_creator = app.get_global_state_creator(); - // By the time this binary is being run, the app has already been built be the CLI (hopefully!), so we can depend on access to hte render config - let index_view = block_on(PerseusApp::get_html_shell( - index_view_str, - &app_root, - &immutable_store, - &plugins, - )); - - let opts = ServerOptions { - // We don't support setting some attributes from `wasm-pack` through plugins/`PerseusApp` because that would require CLI changes as well (a job for an alternative engine) - html_shell: index_view, - js_bundle: "dist/pkg/perseus_engine.js".to_string(), - // Our crate has the same name, so this will be predictable - wasm_bundle: "dist/pkg/perseus_engine_bg.wasm".to_string(), - // This probably won't exist, but on the off chance that the user needs to support older browsers, we'll provide it anyway - wasm_js_bundle: "dist/pkg/perseus_engine_bg.wasm.js".to_string(), - templates_map, - locales, - root_id: app_root, - snippets: "dist/pkg/snippets".to_string(), - error_pages, - // The CLI supports static content in `../static` by default if it exists - // This will be available directly at `/.perseus/static` - static_dir: if fs::metadata(&static_dir_path).is_ok() { - Some(static_dir_path.to_string()) - } else { - None - }, - static_aliases, - }; - - ServerProps { - opts, - immutable_store, - mutable_store: app.get_mutable_store(), - translations_manager: block_on(app.get_translations_manager()), - global_state_creator, - } -} diff --git a/examples/core/basic/.perseus.old/src/lib.rs b/examples/core/basic/.perseus.old/src/lib.rs deleted file mode 100644 index 1e7e040ff1..0000000000 --- a/examples/core/basic/.perseus.old/src/lib.rs +++ /dev/null @@ -1,67 +0,0 @@ -#![allow(clippy::unused_unit)] // rustwasm/wasm-bindgen#2774 awaiting next `wasm-bindgen` release - -// The user should use the `main` macro to create this wrapper -pub use app::__perseus_main as main; - -use perseus::{ - checkpoint, create_app_route, - internal::{ - router::{perseus_router, PerseusRouterProps}, - shell::get_render_cfg, - }, - plugins::PluginAction, - templates::TemplateNodeType, -}; -use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; - -/// The entrypoint into the app itself. This will be compiled to Wasm and actually executed, rendering the rest of the app. -#[wasm_bindgen] -pub fn run() -> Result<(), JsValue> { - let app = main(); - let plugins = app.get_plugins(); - - checkpoint("begin"); - // Panics should always go to the console - std::panic::set_hook(Box::new(console_error_panic_hook::hook)); - - plugins - .functional_actions - .client_actions - .start - .run((), plugins.get_plugin_data()); - checkpoint("initial_plugins_complete"); - - // Get the root we'll be injecting the router into - let root = web_sys::window() - .unwrap() - .document() - .unwrap() - .query_selector(&format!("#{}", app.get_root())) - .unwrap() - .unwrap(); - - // Create the route type we'll use for this app, based on the user's app definition - create_app_route! { - name => AppRoute, - // The render configuration is injected verbatim into the HTML shell, so it certainly should be present - render_cfg => &get_render_cfg().expect("render configuration invalid or not injected"), - // TODO avoid unnecessary allocation here (major problem!) - // The `G` parameter is ambient here for `RouteVerdict` - templates => &main::().get_templates_map(), - locales => &main::().get_locales() - } - - // Set up the properties we'll pass to the router - let router_props = PerseusRouterProps { - locales: app.get_locales(), - error_pages: app.get_error_pages(), - }; - - // This top-level context is what we sue for everything, allowing page state to be registered and stored for the lifetime of the app - sycamore::render_to( - move |cx| perseus_router::<_, AppRoute>(cx, router_props), - &root, - ); - - Ok(()) -} From 3f17ec7ebe187a9edb1604f3dda462623492c9c1 Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Mon, 13 Jun 2022 10:56:37 +1000 Subject: [PATCH 12/37] feat: added convenience macros Also changed the `run_dflt_engine` and `run_client` APIs to make them all take functions that return apps, rather than apps directly, which makes the Actix Web integration able to work normally (by making everything else share its quirks). --- examples/core/basic/src/lib.rs | 33 ++-- packages/perseus-actix-web/src/dflt_server.rs | 2 - packages/perseus-axum/src/dflt_server.rs | 6 +- packages/perseus-macro/src/entrypoint.rs | 149 ++++++++++++++++-- packages/perseus-macro/src/lib.rs | 72 ++++++++- packages/perseus-warp/src/dflt_server.rs | 6 +- packages/perseus/src/client.rs | 5 +- packages/perseus/src/engine/dflt_engine.rs | 22 ++- packages/perseus/src/lib.rs | 2 +- 9 files changed, 249 insertions(+), 48 deletions(-) diff --git a/examples/core/basic/src/lib.rs b/examples/core/basic/src/lib.rs index 03fc15dc4e..a4e43bab5b 100644 --- a/examples/core/basic/src/lib.rs +++ b/examples/core/basic/src/lib.rs @@ -10,21 +10,26 @@ pub fn get_app() -> PerseusApp { .error_pages(crate::error_pages::get_error_pages) } -#[cfg(not(target_arch = "wasm32"))] -#[allow(dead_code)] -#[tokio::main] -async fn main() { - use perseus::builder::{get_op, run_dflt_engine}; +// #[perseus::engine_main] +// async fn main() { +// use perseus::builder::{get_op, run_dflt_engine}; - let op = get_op().unwrap(); - let exit_code = run_dflt_engine(op, get_app(), perseus_warp::dflt_server).await; - std::process::exit(exit_code); -} +// let op = get_op().unwrap(); +// let exit_code = run_dflt_engine(op, get_app, perseus_warp::dflt_server).await; +// std::process::exit(exit_code); +// } + +// #[perseus::browser_main] +// pub fn main() -> perseus::ClientReturn { +// use perseus::run_client; -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen::prelude::wasm_bindgen] -pub fn main() -> perseus::ClientReturn { - use perseus::run_client; +// run_client(get_app) +// } - run_client(get_app()) +#[perseus::main(perseus_warp::dflt_server)] +pub fn main() -> PerseusApp { + PerseusApp::new() + .template(crate::templates::index::get_template) + .template(crate::templates::about::get_template) + .error_pages(crate::error_pages::get_error_pages) } diff --git a/packages/perseus-actix-web/src/dflt_server.rs b/packages/perseus-actix-web/src/dflt_server.rs index 444fc4c03a..f339423509 100644 --- a/packages/perseus-actix-web/src/dflt_server.rs +++ b/packages/perseus-actix-web/src/dflt_server.rs @@ -10,8 +10,6 @@ use perseus::{ /// Creates and starts the default Perseus server using Actix Web. This should be run in a `main()` function annotated with `#[tokio::main]` (which requires the `macros` and /// `rt-multi-thread` features on the `tokio` dependency). -/// -/// Note that this takes a function that generates your `PerseusApp`, which is due to significant lifetime and thread constraints within Actix. pub async fn dflt_server( app: impl Fn() -> PerseusAppBase + 'static + Send + Sync + Clone, ) { diff --git a/packages/perseus-axum/src/dflt_server.rs b/packages/perseus-axum/src/dflt_server.rs index 7ad5c8a5d1..84eb42ecac 100644 --- a/packages/perseus-axum/src/dflt_server.rs +++ b/packages/perseus-axum/src/dflt_server.rs @@ -10,11 +10,11 @@ use std::net::SocketAddr; /// Creates and starts the default Perseus server with Axum. This should be run in a `main` function annotated with `#[tokio::main]` (which requires the `macros` and /// `rt-multi-thread` features on the `tokio` dependency). -pub async fn dflt_server( - app: PerseusAppBase, +pub async fn dflt_server( + app: impl Fn() -> PerseusAppBase + 'static + Send + Sync + Clone, ) { get_standalone_and_act(); - let props = get_props(app); + let props = get_props(app()); let (host, port) = get_host_and_port(); let addr: SocketAddr = format!("{}:{}", host, port) .parse() diff --git a/packages/perseus-macro/src/entrypoint.rs b/packages/perseus-macro/src/entrypoint.rs index 7e7b555e75..afe919438f 100644 --- a/packages/perseus-macro/src/entrypoint.rs +++ b/packages/perseus-macro/src/entrypoint.rs @@ -1,7 +1,7 @@ use proc_macro2::TokenStream; use quote::quote; use syn::parse::{Parse, ParseStream}; -use syn::{Attribute, Block, Generics, Item, ItemFn, Result, ReturnType, Type}; +use syn::{Attribute, Block, Generics, Item, ItemFn, Path, Result, ReturnType, Type}; /// A function that can be made into a Perseus app's entrypoint. /// @@ -13,7 +13,7 @@ pub struct MainFn { pub attrs: Vec, /// The return type of the function. pub return_type: Box, - /// Any generics the function takes (shouldn't be any, but it could in theory). + /// Any generics the function takes. pub generics: Generics, } impl Parse for MainFn { @@ -81,7 +81,80 @@ impl Parse for MainFn { } } -pub fn main_impl(input: MainFn) -> TokenStream { +/// An async function that can be made into a Perseus app's entrypoint. (Specifically, the engine entrypoint.) +pub struct EngineMainFn { + /// The body of the function. + pub block: Box, + /// Any attributes the function uses. + pub attrs: Vec, + /// Any generics the function takes (shouldn't be any, but it could in theory). + pub generics: Generics, +} +impl Parse for EngineMainFn { + fn parse(input: ParseStream) -> Result { + let parsed: Item = input.parse()?; + + match parsed { + Item::Fn(func) => { + let ItemFn { + attrs, sig, block, .. + } = func; + // Validate each part of this function to make sure it fulfills the requirements + // Must not be async + if sig.asyncness.is_none() { + return Err(syn::Error::new_spanned( + sig.asyncness, + "the engine entrypoint must be async", + )); + } + // Can't be const + if sig.constness.is_some() { + return Err(syn::Error::new_spanned( + sig.constness, + "the entrypoint can't be a const function", + )); + } + // Can't be external + if sig.abi.is_some() { + return Err(syn::Error::new_spanned( + sig.abi, + "the entrypoint can't be an external function", + )); + } + // Must return something (type checked by the existence of the wrapper code) + match sig.output { + ReturnType::Default => (), + ReturnType::Type(_, _) => { + return Err(syn::Error::new_spanned( + sig, + "the engine entrypoint must have no return value", + )) + } + }; + // Must accept no arguments + let inputs = sig.inputs; + if !inputs.is_empty() { + return Err(syn::Error::new_spanned( + inputs, + "the entrypoint can't take any arguments", + )); + } + + Ok(Self { + block, + attrs, + generics: sig.generics, + }) + } + item => Err(syn::Error::new_spanned( + item, + "only funtions can be used as entrypoints", + )), + } + } +} + +pub fn main_impl(input: MainFn, server_fn: Path) -> TokenStream { let MainFn { block, generics, @@ -89,15 +162,69 @@ pub fn main_impl(input: MainFn) -> TokenStream { return_type, } = input; - // We wrap the user's function to noramlize the name for the engine + // We split the user's function out into one for the browser and one for the engine (all based around the default engine) let output = quote! { - pub fn __perseus_main() -> #return_type { - // The user's function - #(#attrs)* - fn fn_internal #generics() -> #return_type { - #block - } - fn_internal() + // The engine-specific `main` function + #[cfg(not(target_arch = "wasm32"))] + #[tokio::main] + async fn main() { + // Get the operation we're supposed to run (serve, build, export, etc.) from an environment variable + let op = ::perseus::builder::get_op().unwrap(); + let exit_code = ::perseus::builder::run_dflt_engine(op, __perseus_simple_main, #server_fn).await; + std::process::exit(exit_code); + } + + // The browser-specific `main` function + #[cfg(target_arch = "wasm32")] + #[wasm_bindgen::prelude::wasm_bindgen] + pub fn main() -> ::perseus::ClientReturn { + ::perseus::run_client(__perseus_simple_main) + } + + // The user's function (which gets the `PerseusApp`) + #(#attrs)* + fn __perseus_simple_main #generics() -> #return_type { + #block + } + }; + + output +} + +pub fn browser_main_impl(input: MainFn) -> TokenStream { + let MainFn { + block, + attrs, + return_type, + .. + } = input; + + // We split the user's function out into one for the browser and one for the engine (all based around the default engine) + let output = quote! { + // The browser-specific `main` function + // This absolutely MUST be called `main`, otherwise the hardcodes Wasm importer will fail (and then interactivity is gone completely with a really weird error message) + #[cfg(target_arch = "wasm32")] + #[wasm_bindgen::prelude::wasm_bindgen] + #(#attrs)* + pub fn main() -> #return_type { + #block + } + }; + + output +} + +pub fn engine_main_impl(input: EngineMainFn) -> TokenStream { + let EngineMainFn { block, attrs, .. } = input; + + // We split the user's function out into one for the browser and one for the engine (all based around the default engine) + let output = quote! { + // The engine-specific `main` function + #[cfg(not(target_arch = "wasm32"))] + #[tokio::main] + #(#attrs)* + async fn main() { + #block } }; diff --git a/packages/perseus-macro/src/lib.rs b/packages/perseus-macro/src/lib.rs index bab3cb9305..417ea76461 100644 --- a/packages/perseus-macro/src/lib.rs +++ b/packages/perseus-macro/src/lib.rs @@ -21,7 +21,8 @@ mod test; use darling::FromMeta; use proc_macro::TokenStream; -use syn::ItemStruct; +use quote::quote; +use syn::{ItemStruct, Path}; /// Automatically serializes/deserializes properties for a template. Perseus handles your templates' properties as `String`s under the /// hood for both simplicity and to avoid bundle size increases from excessive monomorphization. This macro aims to prevent the need for @@ -98,14 +99,55 @@ pub fn test(args: TokenStream, input: TokenStream) -> TokenStream { test::test_impl(parsed, args).into() } -/// Marks the given function as the entrypoint into your app. You should only use this once in the `lib.rs` file of your project. +/// Marks the given function as the universal entrypoint into your app. This is designed for simple use-cases, and the annotated function should return +/// a `PerseusApp`. This will expand into separate `main()` functions for both the browser and engine sides. /// -/// Internally, this just normalizes the function's name so that Perseus can find it easily. +/// This should take an argument for the function that will produce your server. In most apps using this macro (which is designed for simple use-cases), +/// this will just be something like `perseus_warp::dflt_server` (with `perseus-warp` as a dependency with the `dflt-server` feature enabled). +/// +/// Note that the `dflt-engine` and `client-helpers` features must be enabled on `perseus` for this to work. (These are enabled by default.) +/// +/// Note further that you'll need to have `wasm-bindgen` as a dependency to use this. +#[proc_macro_attribute] +pub fn main(args: TokenStream, input: TokenStream) -> TokenStream { + let parsed = syn::parse_macro_input!(input as entrypoint::MainFn); + let args = syn::parse_macro_input!(args as Path); + + entrypoint::main_impl(parsed, args).into() +} + +/// Marks the given function as the browser entrypoint into your app. This is designed for more complex apps that need to manually distinguish between +/// the engine and browser entrypoints. +/// +/// If you just want to run some simple customizations, you should probably use `perseus::run_client` to use the default client logic after you've made your +/// modifications. `perseus::ClientReturn` should be your return type no matter what. +/// +/// Note that any generics on the annotated function will not be preserved. You should put the `PerseusApp` generator in a separate function. +/// +/// Note further that you'll need to have `wasm-bindgen` as a dependency to use this. #[proc_macro_attribute] -pub fn main(_args: TokenStream, input: TokenStream) -> TokenStream { +pub fn browser_main(_args: TokenStream, input: TokenStream) -> TokenStream { let parsed = syn::parse_macro_input!(input as entrypoint::MainFn); - entrypoint::main_impl(parsed).into() + entrypoint::browser_main_impl(parsed).into() +} + +/// Marks the given function as the engine entrypoint into your app. This is designed for more complex apps that need to manually distinguish between +/// the engine and browser entrypoints. +/// +/// If you just want to run some simple customizations, you should probably use `perseus::run_dflt_engine` with `perseus::builder::get_op` to use the default client logic +/// after you've made your modifications. You'll also want to return an exit code from this function (use `std::process:exit(..)`). +/// +/// Note that the `dflt-engine` and `client-helpers` features must be enabled on `perseus` for this to work. (These are enabled by default.) +/// +/// Note further that you'll need to have `tokio` as a dependency to use this. +/// +/// Finally, note that any generics on the annotated function will not be preserved. You should put the `PerseusApp` generator in a separate function. +#[proc_macro_attribute] +pub fn engine_main(_args: TokenStream, input: TokenStream) -> TokenStream { + let parsed = syn::parse_macro_input!(input as entrypoint::EngineMainFn); + + entrypoint::engine_main_impl(parsed).into() } /// Processes the given `struct` to create a reactive version by wrapping each field in a `Signal`. This will generate a new `struct` with the given name and implement a `.make_rx()` @@ -182,3 +224,23 @@ pub fn make_rx(args: TokenStream, input: TokenStream) -> TokenStream { rx_state::make_rx_impl(parsed, name).into() } + +// /// Marks the annotated code as only to be run as part of the engine (the server, the builder, the exporter, etc.). This resolves to a +// /// target-gate that makes the annotated code run only on targets that are not `wasm32`. +// #[proc_macro_attribute] +// pub fn engine(_args: TokenStream, input: TokenStream) -> TokenStream { +// quote! { +// #[cfg(not(target_arch = "wasm32"))] +// #input +// }.into() +// } + +// /// Marks the annotated code as only to be run in the browser. This is the opposite of (and mutually exclusive with) `#[engine]`. This +// /// resolves to a target-gate that makes the annotated code run only on targets that are `wasm32`. +// #[proc_macro_attribute] +// pub fn browser(_args: TokenStream, input: TokenStream) -> TokenStream { +// quote! { +// #[cfg(target_arch = "wasm32")] +// #input +// }.into() +// } diff --git a/packages/perseus-warp/src/dflt_server.rs b/packages/perseus-warp/src/dflt_server.rs index ccc6b01d56..33c0a483c3 100644 --- a/packages/perseus-warp/src/dflt_server.rs +++ b/packages/perseus-warp/src/dflt_server.rs @@ -10,11 +10,11 @@ use std::net::SocketAddr; /// Creates and starts the default Perseus server with Warp. This should be run in a `main` function annotated with `#[tokio::main]` (which requires the `macros` and /// `rt-multi-thread` features on the `tokio` dependency). -pub async fn dflt_server( - app: PerseusAppBase, +pub async fn dflt_server( + app: impl Fn() -> PerseusAppBase + 'static + Send + Sync + Clone, ) { get_standalone_and_act(); - let props = get_props(app); + let props = get_props(app()); let (host, port) = get_host_and_port(); let addr: SocketAddr = format!("{}:{}", host, port) .parse() diff --git a/packages/perseus/src/client.rs b/packages/perseus/src/client.rs index a9d6ed4151..e56284d9db 100644 --- a/packages/perseus/src/client.rs +++ b/packages/perseus/src/client.rs @@ -13,9 +13,12 @@ use crate::{i18n::TranslationsManager, stores::MutableStore, PerseusAppBase}; /// Runs the app in the browser on the client-side. This is designed to be executed in a function annotated with `#[wasm_bindgen]`. /// /// This is entirely engine-agnostic, using only the properties from the given `PerseusApp`. +/// +/// For consistency with `run_dflt_engine`, this takes a function that returns the `PerseusApp`. pub fn run_client( - app: PerseusAppBase, + app: impl Fn() -> PerseusAppBase, ) -> Result<(), JsValue> { + let app = app(); let plugins = app.get_plugins(); checkpoint("begin"); diff --git a/packages/perseus/src/engine/dflt_engine.rs b/packages/perseus/src/engine/dflt_engine.rs index 25b234d4d4..b6045609bc 100644 --- a/packages/perseus/src/engine/dflt_engine.rs +++ b/packages/perseus/src/engine/dflt_engine.rs @@ -16,20 +16,26 @@ use std::env; /// to the binary invocation. If this is not the desired behavior, you should handle the `EngineOperation::ExportErrorPage` case manually. /// /// This returns an exit code, which should be returned from the process. Any hanlded errors will be printed to the console. -pub async fn run_dflt_engine>( +pub async fn run_dflt_engine( op: EngineOperation, - app: PerseusAppBase, - serve_fn: impl Fn(PerseusAppBase) -> F, -) -> i32 { + app: A, + serve_fn: impl Fn(A) -> F, +) -> i32 +where + M: MutableStore, + T: TranslationsManager, + F: Future, + A: Fn() -> PerseusAppBase + 'static + Send + Sync + Clone +{ match op { - EngineOperation::Build => match super::engine_build(app).await { + EngineOperation::Build => match super::engine_build(app()).await { Ok(_) => 0, Err(err) => { eprintln!("{}", fmt_err(&*err)); 1 } }, - EngineOperation::Export => match super::engine_export(app).await { + EngineOperation::Export => match super::engine_export(app()).await { Ok(_) => 0, Err(err) => { eprintln!("{}", fmt_err(&*err)); @@ -63,7 +69,7 @@ pub async fn run_dflt_engine 0, Err(err) => { eprintln!("{}", fmt_err(&*err)); @@ -77,7 +83,7 @@ pub async fn run_dflt_engine { // This is infallible (though plugins could panic) - super::engine_tinker(app); + super::engine_tinker(app()); 0 } } diff --git a/packages/perseus/src/lib.rs b/packages/perseus/src/lib.rs index d9fb464179..d7180da5ae 100644 --- a/packages/perseus/src/lib.rs +++ b/packages/perseus/src/lib.rs @@ -68,7 +68,7 @@ pub use sycamore_futures::spawn_local_scoped; pub type Request = HttpRequest<()>; #[cfg(all(feature = "client-helpers", target_arch = "wasm32"))] pub use client::{run_client, ClientReturn}; -pub use perseus_macro::{autoserde, head, main, make_rx, template, template_rx, test}; +pub use perseus_macro::{autoserde, head, main, make_rx, template, template_rx, test, browser_main, engine_main}; pub use sycamore::prelude::{DomNode, Html, HydrateNode, SsrNode}; pub use sycamore_router::{navigate, navigate_replace, Route}; // TODO Should we be exporting `Route` anymore? From 89c56293677e8f108f94936d09cb62fe8a0e2a67 Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Mon, 13 Jun 2022 11:07:39 +1000 Subject: [PATCH 13/37] feat: added more convenience macros --- packages/perseus-macro/src/lib.rs | 40 ++++++++++++++++--------------- packages/perseus/src/lib.rs | 2 +- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/packages/perseus-macro/src/lib.rs b/packages/perseus-macro/src/lib.rs index 417ea76461..f75dfc24b6 100644 --- a/packages/perseus-macro/src/lib.rs +++ b/packages/perseus-macro/src/lib.rs @@ -225,22 +225,24 @@ pub fn make_rx(args: TokenStream, input: TokenStream) -> TokenStream { rx_state::make_rx_impl(parsed, name).into() } -// /// Marks the annotated code as only to be run as part of the engine (the server, the builder, the exporter, etc.). This resolves to a -// /// target-gate that makes the annotated code run only on targets that are not `wasm32`. -// #[proc_macro_attribute] -// pub fn engine(_args: TokenStream, input: TokenStream) -> TokenStream { -// quote! { -// #[cfg(not(target_arch = "wasm32"))] -// #input -// }.into() -// } - -// /// Marks the annotated code as only to be run in the browser. This is the opposite of (and mutually exclusive with) `#[engine]`. This -// /// resolves to a target-gate that makes the annotated code run only on targets that are `wasm32`. -// #[proc_macro_attribute] -// pub fn browser(_args: TokenStream, input: TokenStream) -> TokenStream { -// quote! { -// #[cfg(target_arch = "wasm32")] -// #input -// }.into() -// } +/// Marks the annotated code as only to be run as part of the engine (the server, the builder, the exporter, etc.). This resolves to a +/// target-gate that makes the annotated code run only on targets that are not `wasm32`. +#[proc_macro_attribute] +pub fn engine(_args: TokenStream, input: TokenStream) -> TokenStream { + let input_2: proc_macro2::TokenStream = input.into(); + quote! { + #[cfg(not(target_arch = "wasm32"))] + #input_2 + }.into() +} + +/// Marks the annotated code as only to be run in the browser. This is the opposite of (and mutually exclusive with) `#[engine]`. This +/// resolves to a target-gate that makes the annotated code run only on targets that are `wasm32`. +#[proc_macro_attribute] +pub fn browser(_args: TokenStream, input: TokenStream) -> TokenStream { + let input_2: proc_macro2::TokenStream = input.into(); + quote! { + #[cfg(target_arch = "wasm32")] + #input_2 + }.into() +} diff --git a/packages/perseus/src/lib.rs b/packages/perseus/src/lib.rs index d7180da5ae..894192009d 100644 --- a/packages/perseus/src/lib.rs +++ b/packages/perseus/src/lib.rs @@ -68,7 +68,7 @@ pub use sycamore_futures::spawn_local_scoped; pub type Request = HttpRequest<()>; #[cfg(all(feature = "client-helpers", target_arch = "wasm32"))] pub use client::{run_client, ClientReturn}; -pub use perseus_macro::{autoserde, head, main, make_rx, template, template_rx, test, browser_main, engine_main}; +pub use perseus_macro::{autoserde, head, main, make_rx, template, template_rx, test, browser_main, engine_main, browser, engine}; pub use sycamore::prelude::{DomNode, Html, HydrateNode, SsrNode}; pub use sycamore_router::{navigate, navigate_replace, Route}; // TODO Should we be exporting `Route` anymore? From 11135e3129986965984649a9ccadf46eb3e68dc3 Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Mon, 13 Jun 2022 11:10:06 +1000 Subject: [PATCH 14/37] refactor: broke out macros under a new features This feature is the default though. --- packages/perseus/Cargo.toml | 6 ++++-- packages/perseus/src/lib.rs | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/perseus/Cargo.toml b/packages/perseus/Cargo.toml index 769c857303..879c4a6e7c 100644 --- a/packages/perseus/Cargo.toml +++ b/packages/perseus/Cargo.toml @@ -17,7 +17,7 @@ categories = ["wasm", "web-programming", "development-tools", "asynchronous", "g sycamore = { version = "=0.8.0-beta.6", features = [ "ssr" ] } sycamore-router = "=0.8.0-beta.6" sycamore-futures = "=0.8.0-beta.6" -perseus-macro = { path = "../perseus-macro", version = "0.4.0-beta.1" } +perseus-macro = { path = "../perseus-macro", version = "0.4.0-beta.1", optional = true } serde = { version = "1", features = ["derive"] } serde_json = "1" thiserror = "1" @@ -48,8 +48,10 @@ wasm-bindgen-futures = "0.4" [features] # Live reloading will only take effect in development, and won't impact production # BUG This adds 1.9kB to the production bundle (that's without size optimizations though) -default = [ "live-reload", "hsr", "builder", "client-helpers" ] +default = [ "live-reload", "hsr", "builder", "client-helpers", "macros" ] translator-fluent = ["fluent-bundle", "unic-langid", "intl-memoizer"] +# This feature adds support for a number of macros that will make your life MUCH easier (read: use this unless you have very specific needs or are completely insane) +macros = [ "perseus-macro" ] # This feature makes tinker-only plugins be registered (this flag is enabled internally in the engine) tinker-plugins = [] # This feature enables code required only in the builder systems on the server-side. diff --git a/packages/perseus/src/lib.rs b/packages/perseus/src/lib.rs index 894192009d..5b603989cb 100644 --- a/packages/perseus/src/lib.rs +++ b/packages/perseus/src/lib.rs @@ -68,6 +68,7 @@ pub use sycamore_futures::spawn_local_scoped; pub type Request = HttpRequest<()>; #[cfg(all(feature = "client-helpers", target_arch = "wasm32"))] pub use client::{run_client, ClientReturn}; +#[cfg(feature = "macros")] pub use perseus_macro::{autoserde, head, main, make_rx, template, template_rx, test, browser_main, engine_main, browser, engine}; pub use sycamore::prelude::{DomNode, Html, HydrateNode, SsrNode}; pub use sycamore_router::{navigate, navigate_replace, Route}; // TODO Should we be exporting `Route` anymore? From 00f15e6b96c51061a619ce4cfe6970facc635cc0 Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Mon, 13 Jun 2022 16:51:23 +1000 Subject: [PATCH 15/37] feat: added dflt engine system for export-only apps --- packages/perseus-macro/src/lib.rs | 6 ++++-- packages/perseus/src/engine/dflt_engine.rs | 18 ++++++++++++++++-- packages/perseus/src/engine/mod.rs | 2 +- packages/perseus/src/lib.rs | 5 ++++- 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/packages/perseus-macro/src/lib.rs b/packages/perseus-macro/src/lib.rs index f75dfc24b6..b4111209e2 100644 --- a/packages/perseus-macro/src/lib.rs +++ b/packages/perseus-macro/src/lib.rs @@ -233,7 +233,8 @@ pub fn engine(_args: TokenStream, input: TokenStream) -> TokenStream { quote! { #[cfg(not(target_arch = "wasm32"))] #input_2 - }.into() + } + .into() } /// Marks the annotated code as only to be run in the browser. This is the opposite of (and mutually exclusive with) `#[engine]`. This @@ -244,5 +245,6 @@ pub fn browser(_args: TokenStream, input: TokenStream) -> TokenStream { quote! { #[cfg(target_arch = "wasm32")] #input_2 - }.into() + } + .into() } diff --git a/packages/perseus/src/engine/dflt_engine.rs b/packages/perseus/src/engine/dflt_engine.rs index b6045609bc..f38cd0632a 100644 --- a/packages/perseus/src/engine/dflt_engine.rs +++ b/packages/perseus/src/engine/dflt_engine.rs @@ -6,6 +6,20 @@ use fmterr::fmt_err; use futures::Future; use std::env; +/// A wrapper around `run_dflt_engine` for apps that only use exporting, and so don't need to bring in a server integration. This is designed to avoid extra +/// dependencies. If `perseus serve` is called on an app using this, it will `panic!` after building everything. +pub async fn run_dflt_engine_export_only(op: EngineOperation, app: A) -> i32 +where + M: MutableStore, + T: TranslationsManager, + A: Fn() -> PerseusAppBase + 'static + Send + Sync + Clone, +{ + let serve_fn = |_app: A| async { + panic!("`run_dflt_engine_export_only` cannot run a server; you should use `run_dflt_engine` instead and import a server integration (e.g. `perseus-warp`)") + }; + run_dflt_engine(op, app, serve_fn).await +} + /// A convenience function that automatically runs the necessary engine operation based on the given directive. This provides almost no options for customization, and is /// usually elided by a macro. More advanced use-cases should bypass this and call the functions this calls manually, with their own configurations. /// @@ -15,7 +29,7 @@ use std::env; /// If the action is to export a single error page, the HTTP status code of the error page to export and the output will be read as the first and second arguments /// to the binary invocation. If this is not the desired behavior, you should handle the `EngineOperation::ExportErrorPage` case manually. /// -/// This returns an exit code, which should be returned from the process. Any hanlded errors will be printed to the console. +/// This returns an exit code, which should be returned from the process. Any handled errors will be printed to the console. pub async fn run_dflt_engine( op: EngineOperation, app: A, @@ -25,7 +39,7 @@ where M: MutableStore, T: TranslationsManager, F: Future, - A: Fn() -> PerseusAppBase + 'static + Send + Sync + Clone + A: Fn() -> PerseusAppBase + 'static + Send + Sync + Clone, { match op { EngineOperation::Build => match super::engine_build(app()).await { diff --git a/packages/perseus/src/engine/mod.rs b/packages/perseus/src/engine/mod.rs index f9906c01a3..4afb9b2b33 100644 --- a/packages/perseus/src/engine/mod.rs +++ b/packages/perseus/src/engine/mod.rs @@ -10,7 +10,7 @@ pub use tinker::tinker as engine_tinker; #[cfg(feature = "dflt-engine")] mod dflt_engine; #[cfg(feature = "dflt-engine")] -pub use dflt_engine::run_dflt_engine; +pub use dflt_engine::{run_dflt_engine, run_dflt_engine_export_only}; mod get_op; pub use get_op::{get_op, EngineOperation}; diff --git a/packages/perseus/src/lib.rs b/packages/perseus/src/lib.rs index 5b603989cb..772dc91810 100644 --- a/packages/perseus/src/lib.rs +++ b/packages/perseus/src/lib.rs @@ -69,7 +69,10 @@ pub type Request = HttpRequest<()>; #[cfg(all(feature = "client-helpers", target_arch = "wasm32"))] pub use client::{run_client, ClientReturn}; #[cfg(feature = "macros")] -pub use perseus_macro::{autoserde, head, main, make_rx, template, template_rx, test, browser_main, engine_main, browser, engine}; +pub use perseus_macro::{ + autoserde, browser, browser_main, engine, engine_main, head, main, make_rx, template, + template_rx, test, +}; pub use sycamore::prelude::{DomNode, Html, HydrateNode, SsrNode}; pub use sycamore_router::{navigate, navigate_replace, Route}; // TODO Should we be exporting `Route` anymore? From 90e5c3f30d8813fe7b3a393f4484c839f2c4a666 Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Tue, 14 Jun 2022 16:51:02 +1000 Subject: [PATCH 16/37] test: updated all examples Except `fetching`, need to merge from `main`. --- examples/comprehensive/tiny/Cargo.toml | 16 ++++++++++++++++ examples/comprehensive/tiny/index.html | 11 ----------- examples/comprehensive/tiny/src/lib.rs | 2 +- examples/core/basic/Cargo.toml | 3 +-- examples/core/basic/index.html | 10 ---------- examples/core/basic/src/lib.rs | 12 ++++++------ examples/core/freezing_and_thawing/.gitignore | 2 +- examples/core/freezing_and_thawing/Cargo.toml | 17 ++++++++++++++++- examples/core/freezing_and_thawing/index.html | 10 ---------- examples/core/freezing_and_thawing/src/lib.rs | 2 +- examples/core/global_state/.gitignore | 2 +- examples/core/global_state/Cargo.toml | 17 ++++++++++++++++- examples/core/global_state/index.html | 10 ---------- examples/core/global_state/src/lib.rs | 2 +- examples/core/i18n/.gitignore | 5 +---- examples/core/i18n/Cargo.toml | 17 ++++++++++++++++- examples/core/i18n/index.html | 11 ----------- examples/core/i18n/src/lib.rs | 2 +- examples/core/idb_freezing/.gitignore | 2 +- examples/core/idb_freezing/Cargo.toml | 17 ++++++++++++++++- examples/core/idb_freezing/index.html | 10 ---------- examples/core/idb_freezing/src/lib.rs | 2 +- examples/core/index_view/.gitignore | 3 +-- examples/core/index_view/Cargo.toml | 17 ++++++++++++++++- examples/core/index_view/src/lib.rs | 2 +- examples/core/plugins/Cargo.toml | 17 ++++++++++++++++- examples/core/plugins/index.html | 10 ---------- examples/core/plugins/src/lib.rs | 2 +- examples/core/router_state/Cargo.toml | 16 ++++++++++++++++ examples/core/router_state/index.html | 10 ---------- examples/core/router_state/src/lib.rs | 2 +- examples/core/rx_state/Cargo.toml | 17 ++++++++++++++++- examples/core/rx_state/index.html | 10 ---------- examples/core/rx_state/src/lib.rs | 2 +- examples/core/set_headers/Cargo.toml | 17 ++++++++++++++++- examples/core/set_headers/index.html | 10 ---------- examples/core/set_headers/src/lib.rs | 2 +- examples/core/state_generation/Cargo.toml | 19 +++++++++++++++++-- examples/core/state_generation/index.html | 10 ---------- examples/core/state_generation/src/lib.rs | 2 +- examples/core/static_content/Cargo.toml | 17 ++++++++++++++++- examples/core/static_content/index.html | 12 ------------ examples/core/static_content/src/lib.rs | 2 +- examples/core/unreactive/Cargo.toml | 17 ++++++++++++++++- examples/core/unreactive/index.html | 10 ---------- examples/core/unreactive/src/lib.rs | 2 +- examples/demos/auth/Cargo.toml | 16 ++++++++++++++++ examples/demos/auth/src/lib.rs | 2 +- packages/perseus/Cargo.toml | 2 +- 49 files changed, 252 insertions(+), 178 deletions(-) delete mode 100644 examples/comprehensive/tiny/index.html delete mode 100644 examples/core/basic/index.html delete mode 100644 examples/core/freezing_and_thawing/index.html delete mode 100644 examples/core/global_state/index.html delete mode 100644 examples/core/i18n/index.html delete mode 100644 examples/core/idb_freezing/index.html delete mode 100644 examples/core/plugins/index.html delete mode 100644 examples/core/router_state/index.html delete mode 100644 examples/core/rx_state/index.html delete mode 100644 examples/core/set_headers/index.html delete mode 100644 examples/core/state_generation/index.html delete mode 100644 examples/core/static_content/index.html delete mode 100644 examples/core/unreactive/index.html diff --git a/examples/comprehensive/tiny/Cargo.toml b/examples/comprehensive/tiny/Cargo.toml index 3ebc52a5d2..cfcc9d4217 100644 --- a/examples/comprehensive/tiny/Cargo.toml +++ b/examples/comprehensive/tiny/Cargo.toml @@ -8,3 +8,19 @@ edition = "2021" [dependencies] perseus = { path = "../../../packages/perseus" } sycamore = "=0.8.0-beta.6" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } +perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = "0.2" + +[lib] +name = "lib" +path = "src/lib.rs" +crate-type = [ "cdylib", "rlib" ] + +[[bin]] +name = "perseus-example-tiny" +path = "src/lib.rs" diff --git a/examples/comprehensive/tiny/index.html b/examples/comprehensive/tiny/index.html deleted file mode 100644 index 2f972c2e1d..0000000000 --- a/examples/comprehensive/tiny/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - Perseus Example – Tiny - - -
- - diff --git a/examples/comprehensive/tiny/src/lib.rs b/examples/comprehensive/tiny/src/lib.rs index d5922b76e1..490723d7ab 100644 --- a/examples/comprehensive/tiny/src/lib.rs +++ b/examples/comprehensive/tiny/src/lib.rs @@ -1,7 +1,7 @@ use perseus::{Html, PerseusApp, Template}; use sycamore::view; -#[perseus::main] +#[perseus::main(perseus_warp::dflt_server)] pub fn main() -> PerseusApp { PerseusApp::new().template(|| { Template::new("index").template(|cx, _| { diff --git a/examples/core/basic/Cargo.toml b/examples/core/basic/Cargo.toml index 24cfa276c9..b78d305197 100644 --- a/examples/core/basic/Cargo.toml +++ b/examples/core/basic/Cargo.toml @@ -2,12 +2,11 @@ name = "perseus-example-basic" version = "0.4.0-beta.1" edition = "2021" -# forced-target = "wasm32-unknown-unknown" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -perseus = { path = "../../../packages/perseus", features = [ "hydrate", "builder", "dflt-engine" ] } +perseus = { path = "../../../packages/perseus", features = [ "hydrate" ] } sycamore = "=0.8.0-beta.6" serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/examples/core/basic/index.html b/examples/core/basic/index.html deleted file mode 100644 index edc8a66246..0000000000 --- a/examples/core/basic/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - -
- - diff --git a/examples/core/basic/src/lib.rs b/examples/core/basic/src/lib.rs index a4e43bab5b..30e1c73070 100644 --- a/examples/core/basic/src/lib.rs +++ b/examples/core/basic/src/lib.rs @@ -3,12 +3,12 @@ mod templates; use perseus::{Html, PerseusApp}; -pub fn get_app() -> PerseusApp { - PerseusApp::new() - .template(crate::templates::index::get_template) - .template(crate::templates::about::get_template) - .error_pages(crate::error_pages::get_error_pages) -} +// pub fn get_app() -> PerseusApp { +// PerseusApp::new() +// .template(crate::templates::index::get_template) +// .template(crate::templates::about::get_template) +// .error_pages(crate::error_pages::get_error_pages) +// } // #[perseus::engine_main] // async fn main() { diff --git a/examples/core/freezing_and_thawing/.gitignore b/examples/core/freezing_and_thawing/.gitignore index 9405098b45..849ddff3b7 100644 --- a/examples/core/freezing_and_thawing/.gitignore +++ b/examples/core/freezing_and_thawing/.gitignore @@ -1 +1 @@ -.perseus/ +dist/ diff --git a/examples/core/freezing_and_thawing/Cargo.toml b/examples/core/freezing_and_thawing/Cargo.toml index a20ffd8eaf..e2ad6995b8 100644 --- a/examples/core/freezing_and_thawing/Cargo.toml +++ b/examples/core/freezing_and_thawing/Cargo.toml @@ -13,4 +13,19 @@ serde_json = "1" [dev-dependencies] fantoccini = "0.17" -tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } +perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = "0.2" + +[lib] +name = "lib" +path = "src/lib.rs" +crate-type = [ "cdylib", "rlib" ] + +[[bin]] +name = "perseus-example-freezing-and-thawing" +path = "src/lib.rs" diff --git a/examples/core/freezing_and_thawing/index.html b/examples/core/freezing_and_thawing/index.html deleted file mode 100644 index edc8a66246..0000000000 --- a/examples/core/freezing_and_thawing/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - -
- - diff --git a/examples/core/freezing_and_thawing/src/lib.rs b/examples/core/freezing_and_thawing/src/lib.rs index 418b487283..fe6874d611 100644 --- a/examples/core/freezing_and_thawing/src/lib.rs +++ b/examples/core/freezing_and_thawing/src/lib.rs @@ -4,7 +4,7 @@ mod templates; use perseus::{Html, PerseusApp}; -#[perseus::main] +#[perseus::main(perseus_warp::dflt_server)] pub fn main() -> PerseusApp { PerseusApp::new() .template(crate::templates::index::get_template) diff --git a/examples/core/global_state/.gitignore b/examples/core/global_state/.gitignore index 9405098b45..849ddff3b7 100644 --- a/examples/core/global_state/.gitignore +++ b/examples/core/global_state/.gitignore @@ -1 +1 @@ -.perseus/ +dist/ diff --git a/examples/core/global_state/Cargo.toml b/examples/core/global_state/Cargo.toml index 545991b4d7..d57a4666f8 100644 --- a/examples/core/global_state/Cargo.toml +++ b/examples/core/global_state/Cargo.toml @@ -13,4 +13,19 @@ serde_json = "1" [dev-dependencies] fantoccini = "0.17" -tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } +perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = "0.2" + +[lib] +name = "lib" +path = "src/lib.rs" +crate-type = [ "cdylib", "rlib" ] + +[[bin]] +name = "perseus-example-global-state" +path = "src/lib.rs" diff --git a/examples/core/global_state/index.html b/examples/core/global_state/index.html deleted file mode 100644 index edc8a66246..0000000000 --- a/examples/core/global_state/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - -
- - diff --git a/examples/core/global_state/src/lib.rs b/examples/core/global_state/src/lib.rs index 418b487283..fe6874d611 100644 --- a/examples/core/global_state/src/lib.rs +++ b/examples/core/global_state/src/lib.rs @@ -4,7 +4,7 @@ mod templates; use perseus::{Html, PerseusApp}; -#[perseus::main] +#[perseus::main(perseus_warp::dflt_server)] pub fn main() -> PerseusApp { PerseusApp::new() .template(crate::templates::index::get_template) diff --git a/examples/core/i18n/.gitignore b/examples/core/i18n/.gitignore index 3b1525da45..849ddff3b7 100644 --- a/examples/core/i18n/.gitignore +++ b/examples/core/i18n/.gitignore @@ -1,4 +1 @@ -/target -Cargo.lock - -.perseus/ \ No newline at end of file +dist/ diff --git a/examples/core/i18n/Cargo.toml b/examples/core/i18n/Cargo.toml index 814bde8bce..db8dca5966 100644 --- a/examples/core/i18n/Cargo.toml +++ b/examples/core/i18n/Cargo.toml @@ -15,4 +15,19 @@ urlencoding = "2.1" [dev-dependencies] fantoccini = "0.17" -tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } +perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = "0.2" + +[lib] +name = "lib" +path = "src/lib.rs" +crate-type = [ "cdylib", "rlib" ] + +[[bin]] +name = "perseus-example-i18n" +path = "src/lib.rs" diff --git a/examples/core/i18n/index.html b/examples/core/i18n/index.html deleted file mode 100644 index cb9e980c16..0000000000 --- a/examples/core/i18n/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - Perseus Example – i18n - - -
- - diff --git a/examples/core/i18n/src/lib.rs b/examples/core/i18n/src/lib.rs index 5b47905e11..bcd84fe050 100644 --- a/examples/core/i18n/src/lib.rs +++ b/examples/core/i18n/src/lib.rs @@ -3,7 +3,7 @@ mod templates; use perseus::{Html, PerseusApp}; -#[perseus::main] +#[perseus::main(perseus_warp::dflt_server)] pub fn main() -> PerseusApp { PerseusApp::new() .template(crate::templates::index::get_template) diff --git a/examples/core/idb_freezing/.gitignore b/examples/core/idb_freezing/.gitignore index 9405098b45..849ddff3b7 100644 --- a/examples/core/idb_freezing/.gitignore +++ b/examples/core/idb_freezing/.gitignore @@ -1 +1 @@ -.perseus/ +dist/ diff --git a/examples/core/idb_freezing/Cargo.toml b/examples/core/idb_freezing/Cargo.toml index 3b63bdbd8e..c26a595b1b 100644 --- a/examples/core/idb_freezing/Cargo.toml +++ b/examples/core/idb_freezing/Cargo.toml @@ -14,4 +14,19 @@ wasm-bindgen-futures = "0.4" [dev-dependencies] fantoccini = "0.17" -tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } +perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = "0.2" + +[lib] +name = "lib" +path = "src/lib.rs" +crate-type = [ "cdylib", "rlib" ] + +[[bin]] +name = "perseus-example-idb-freezing" +path = "src/lib.rs" diff --git a/examples/core/idb_freezing/index.html b/examples/core/idb_freezing/index.html deleted file mode 100644 index edc8a66246..0000000000 --- a/examples/core/idb_freezing/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - -
- - diff --git a/examples/core/idb_freezing/src/lib.rs b/examples/core/idb_freezing/src/lib.rs index 418b487283..fe6874d611 100644 --- a/examples/core/idb_freezing/src/lib.rs +++ b/examples/core/idb_freezing/src/lib.rs @@ -4,7 +4,7 @@ mod templates; use perseus::{Html, PerseusApp}; -#[perseus::main] +#[perseus::main(perseus_warp::dflt_server)] pub fn main() -> PerseusApp { PerseusApp::new() .template(crate::templates::index::get_template) diff --git a/examples/core/index_view/.gitignore b/examples/core/index_view/.gitignore index 19f4c83304..849ddff3b7 100644 --- a/examples/core/index_view/.gitignore +++ b/examples/core/index_view/.gitignore @@ -1,2 +1 @@ - -.perseus/ \ No newline at end of file +dist/ diff --git a/examples/core/index_view/Cargo.toml b/examples/core/index_view/Cargo.toml index 948a00e1ac..4515745800 100644 --- a/examples/core/index_view/Cargo.toml +++ b/examples/core/index_view/Cargo.toml @@ -13,4 +13,19 @@ serde_json = "1" [dev-dependencies] fantoccini = "0.17" -tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } +perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = "0.2" + +[lib] +name = "lib" +path = "src/lib.rs" +crate-type = [ "cdylib", "rlib" ] + +[[bin]] +name = "perseus-example-index-view" +path = "src/lib.rs" diff --git a/examples/core/index_view/src/lib.rs b/examples/core/index_view/src/lib.rs index 4e168387b1..2ac282163d 100644 --- a/examples/core/index_view/src/lib.rs +++ b/examples/core/index_view/src/lib.rs @@ -3,7 +3,7 @@ mod templates; use perseus::{Html, PerseusApp, PerseusRoot}; -#[perseus::main] +#[perseus::main(perseus_warp::dflt_server)] pub fn main() -> PerseusApp { PerseusApp::new() .template(crate::templates::index::get_template) diff --git a/examples/core/plugins/Cargo.toml b/examples/core/plugins/Cargo.toml index ec7431a4c2..68929eaecd 100644 --- a/examples/core/plugins/Cargo.toml +++ b/examples/core/plugins/Cargo.toml @@ -14,4 +14,19 @@ toml = "0.5" [dev-dependencies] fantoccini = "0.17" -tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } +perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = "0.2" + +[lib] +name = "lib" +path = "src/lib.rs" +crate-type = [ "cdylib", "rlib" ] + +[[bin]] +name = "perseus-example-plugins" +path = "src/lib.rs" diff --git a/examples/core/plugins/index.html b/examples/core/plugins/index.html deleted file mode 100644 index edc8a66246..0000000000 --- a/examples/core/plugins/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - -
- - diff --git a/examples/core/plugins/src/lib.rs b/examples/core/plugins/src/lib.rs index 61ca1568ec..5580f72681 100644 --- a/examples/core/plugins/src/lib.rs +++ b/examples/core/plugins/src/lib.rs @@ -4,7 +4,7 @@ mod templates; use perseus::{Html, PerseusApp, Plugins}; -#[perseus::main] +#[perseus::main(perseus_warp::dflt_server)] pub fn main() -> PerseusApp { PerseusApp::new() .template(crate::templates::index::get_template) diff --git a/examples/core/router_state/Cargo.toml b/examples/core/router_state/Cargo.toml index 4b306884c0..fd138a978c 100644 --- a/examples/core/router_state/Cargo.toml +++ b/examples/core/router_state/Cargo.toml @@ -10,3 +10,19 @@ perseus = { path = "../../../packages/perseus", features = [ "hydrate" ] } sycamore = "=0.8.0-beta.6" serde = { version = "1", features = ["derive"] } serde_json = "1" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } +perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = "0.2" + +[lib] +name = "lib" +path = "src/lib.rs" +crate-type = [ "cdylib", "rlib" ] + +[[bin]] +name = "perseus-example-router-state" +path = "src/lib.rs" diff --git a/examples/core/router_state/index.html b/examples/core/router_state/index.html deleted file mode 100644 index edc8a66246..0000000000 --- a/examples/core/router_state/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - -
- - diff --git a/examples/core/router_state/src/lib.rs b/examples/core/router_state/src/lib.rs index ee3e6e1a79..eee8b18aa3 100644 --- a/examples/core/router_state/src/lib.rs +++ b/examples/core/router_state/src/lib.rs @@ -3,7 +3,7 @@ mod templates; use perseus::{Html, PerseusApp}; -#[perseus::main] +#[perseus::main(perseus_warp::dflt_server)] pub fn main() -> PerseusApp { PerseusApp::new() .template(crate::templates::index::get_template) diff --git a/examples/core/rx_state/Cargo.toml b/examples/core/rx_state/Cargo.toml index f3288f371e..c68a9e7aa3 100644 --- a/examples/core/rx_state/Cargo.toml +++ b/examples/core/rx_state/Cargo.toml @@ -13,4 +13,19 @@ serde_json = "1" [dev-dependencies] fantoccini = "0.17" -tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } +perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = "0.2" + +[lib] +name = "lib" +path = "src/lib.rs" +crate-type = [ "cdylib", "rlib" ] + +[[bin]] +name = "perseus-example-rx-state" +path = "src/lib.rs" diff --git a/examples/core/rx_state/index.html b/examples/core/rx_state/index.html deleted file mode 100644 index edc8a66246..0000000000 --- a/examples/core/rx_state/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - -
- - diff --git a/examples/core/rx_state/src/lib.rs b/examples/core/rx_state/src/lib.rs index ee3e6e1a79..eee8b18aa3 100644 --- a/examples/core/rx_state/src/lib.rs +++ b/examples/core/rx_state/src/lib.rs @@ -3,7 +3,7 @@ mod templates; use perseus::{Html, PerseusApp}; -#[perseus::main] +#[perseus::main(perseus_warp::dflt_server)] pub fn main() -> PerseusApp { PerseusApp::new() .template(crate::templates::index::get_template) diff --git a/examples/core/set_headers/Cargo.toml b/examples/core/set_headers/Cargo.toml index 0796c6e763..b5127b3484 100644 --- a/examples/core/set_headers/Cargo.toml +++ b/examples/core/set_headers/Cargo.toml @@ -13,5 +13,20 @@ serde_json = "1" [dev-dependencies] fantoccini = "0.17" -tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] } ureq = "2" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } +perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = "0.2" + +[lib] +name = "lib" +path = "src/lib.rs" +crate-type = [ "cdylib", "rlib" ] + +[[bin]] +name = "perseus-example-set-headers" +path = "src/lib.rs" diff --git a/examples/core/set_headers/index.html b/examples/core/set_headers/index.html deleted file mode 100644 index edc8a66246..0000000000 --- a/examples/core/set_headers/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - -
- - diff --git a/examples/core/set_headers/src/lib.rs b/examples/core/set_headers/src/lib.rs index 7522e8240a..8718528ae7 100644 --- a/examples/core/set_headers/src/lib.rs +++ b/examples/core/set_headers/src/lib.rs @@ -3,7 +3,7 @@ mod templates; use perseus::{Html, PerseusApp}; -#[perseus::main] +#[perseus::main(perseus_warp::dflt_server)] pub fn main() -> PerseusApp { PerseusApp::new() .template(crate::templates::index::get_template) diff --git a/examples/core/state_generation/Cargo.toml b/examples/core/state_generation/Cargo.toml index f32e742431..295b09fed0 100644 --- a/examples/core/state_generation/Cargo.toml +++ b/examples/core/state_generation/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "perseus-example-base" +name = "perseus-example-state-generation" version = "0.3.2" edition = "2021" @@ -13,4 +13,19 @@ serde_json = "1" [dev-dependencies] fantoccini = "0.17" -tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } +perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = "0.2" + +[lib] +name = "lib" +path = "src/lib.rs" +crate-type = [ "cdylib", "rlib" ] + +[[bin]] +name = "perseus-example-state-generation" +path = "src/lib.rs" diff --git a/examples/core/state_generation/index.html b/examples/core/state_generation/index.html deleted file mode 100644 index edc8a66246..0000000000 --- a/examples/core/state_generation/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - -
- - diff --git a/examples/core/state_generation/src/lib.rs b/examples/core/state_generation/src/lib.rs index 31622c0ca3..4c5613ed58 100644 --- a/examples/core/state_generation/src/lib.rs +++ b/examples/core/state_generation/src/lib.rs @@ -3,7 +3,7 @@ mod templates; use perseus::{Html, PerseusApp}; -#[perseus::main] +#[perseus::main(perseus_warp::dflt_server)] pub fn main() -> PerseusApp { PerseusApp::new() .template(crate::templates::build_state::get_template) diff --git a/examples/core/static_content/Cargo.toml b/examples/core/static_content/Cargo.toml index b74dd3e815..5213a8a629 100644 --- a/examples/core/static_content/Cargo.toml +++ b/examples/core/static_content/Cargo.toml @@ -13,4 +13,19 @@ serde_json = "1" [dev-dependencies] fantoccini = "0.17" -tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } +perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = "0.2" + +[lib] +name = "lib" +path = "src/lib.rs" +crate-type = [ "cdylib", "rlib" ] + +[[bin]] +name = "perseus-example-static-content" +path = "src/lib.rs" diff --git a/examples/core/static_content/index.html b/examples/core/static_content/index.html deleted file mode 100644 index e70301b489..0000000000 --- a/examples/core/static_content/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - -
- - diff --git a/examples/core/static_content/src/lib.rs b/examples/core/static_content/src/lib.rs index 301b1ca4d3..c4eac85941 100644 --- a/examples/core/static_content/src/lib.rs +++ b/examples/core/static_content/src/lib.rs @@ -3,7 +3,7 @@ mod templates; use perseus::{Html, PerseusApp}; -#[perseus::main] +#[perseus::main(perseus_warp::dflt_server)] pub fn main() -> PerseusApp { PerseusApp::new() .template(crate::templates::index::get_template) diff --git a/examples/core/unreactive/Cargo.toml b/examples/core/unreactive/Cargo.toml index b264b51e6d..d7aaaa5f94 100644 --- a/examples/core/unreactive/Cargo.toml +++ b/examples/core/unreactive/Cargo.toml @@ -13,4 +13,19 @@ serde_json = "1" [dev-dependencies] fantoccini = "0.17" -tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } +perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = "0.2" + +[lib] +name = "lib" +path = "src/lib.rs" +crate-type = [ "cdylib", "rlib" ] + +[[bin]] +name = "perseus-example-unreactive" +path = "src/lib.rs" diff --git a/examples/core/unreactive/index.html b/examples/core/unreactive/index.html deleted file mode 100644 index edc8a66246..0000000000 --- a/examples/core/unreactive/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - -
- - diff --git a/examples/core/unreactive/src/lib.rs b/examples/core/unreactive/src/lib.rs index ee3e6e1a79..eee8b18aa3 100644 --- a/examples/core/unreactive/src/lib.rs +++ b/examples/core/unreactive/src/lib.rs @@ -3,7 +3,7 @@ mod templates; use perseus::{Html, PerseusApp}; -#[perseus::main] +#[perseus::main(perseus_warp::dflt_server)] pub fn main() -> PerseusApp { PerseusApp::new() .template(crate::templates::index::get_template) diff --git a/examples/demos/auth/Cargo.toml b/examples/demos/auth/Cargo.toml index 2a5856e49f..88128f4f78 100644 --- a/examples/demos/auth/Cargo.toml +++ b/examples/demos/auth/Cargo.toml @@ -11,5 +11,21 @@ perseus = { path = "../../../packages/perseus", features = [] } sycamore = "=0.8.0-beta.6" serde = { version = "1", features = ["derive"] } serde_json = "1" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } +perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = "0.2" # We need the `HtmlDocument` feature to be able to use cookies (which this example does) web-sys = { version = "0.3", features = [ "Storage" ] } + +[lib] +name = "lib" +path = "src/lib.rs" +crate-type = [ "cdylib", "rlib" ] + +[[bin]] +name = "perseus-example-auth" +path = "src/lib.rs" diff --git a/examples/demos/auth/src/lib.rs b/examples/demos/auth/src/lib.rs index 418b487283..fe6874d611 100644 --- a/examples/demos/auth/src/lib.rs +++ b/examples/demos/auth/src/lib.rs @@ -4,7 +4,7 @@ mod templates; use perseus::{Html, PerseusApp}; -#[perseus::main] +#[perseus::main(perseus_warp::dflt_server)] pub fn main() -> PerseusApp { PerseusApp::new() .template(crate::templates::index::get_template) diff --git a/packages/perseus/Cargo.toml b/packages/perseus/Cargo.toml index 879c4a6e7c..f3eceda308 100644 --- a/packages/perseus/Cargo.toml +++ b/packages/perseus/Cargo.toml @@ -48,7 +48,7 @@ wasm-bindgen-futures = "0.4" [features] # Live reloading will only take effect in development, and won't impact production # BUG This adds 1.9kB to the production bundle (that's without size optimizations though) -default = [ "live-reload", "hsr", "builder", "client-helpers", "macros" ] +default = [ "live-reload", "hsr", "builder", "client-helpers", "macros", "dflt-engine" ] translator-fluent = ["fluent-bundle", "unic-langid", "intl-memoizer"] # This feature adds support for a number of macros that will make your life MUCH easier (read: use this unless you have very specific needs or are completely insane) macros = [ "perseus-macro" ] From dc6ae084ec169c4be123c076cc7ae8c79adb9e07 Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Tue, 14 Jun 2022 17:04:27 +1000 Subject: [PATCH 17/37] test: updated `fetching` example --- examples/demos/auth/src/global_state.rs | 2 +- examples/demos/auth/src/templates/index.rs | 2 ++ examples/demos/fetching/Cargo.toml | 17 +++++++++++++++-- examples/demos/fetching/src/lib.rs | 2 +- examples/demos/fetching/src/templates/index.rs | 6 +----- 5 files changed, 20 insertions(+), 9 deletions(-) diff --git a/examples/demos/auth/src/global_state.rs b/examples/demos/auth/src/global_state.rs index b0a6e55934..441a108e96 100644 --- a/examples/demos/auth/src/global_state.rs +++ b/examples/demos/auth/src/global_state.rs @@ -44,9 +44,9 @@ pub struct AuthData { } // We implement a custom function on the reactive version of the global state here (hence the `.get()`s and `.set()`s, all the fields become `Signal`s) // There's no point in implementing it on the unreactive version, since this will only be called from within the browser, in which we have a reactive version +#[cfg(target_arch = "wasm32")] // These functions all use `web_sys`, and so won't work on the server-side impl<'a> AuthDataRx<'a> { /// Checks whether or not the user is logged in and modifies the internal state accordingly. If this has already been run, it won't do anything (aka. it will only run if it's `Server`) - #[cfg(target_arch = "wasm32")] // This just avoids an unused function warning (since we have to gate the `.update()` call) pub fn detect_state(&self) { // If we've checked the login status before, then we should assume the status hasn't changed (we'd change this in a login/logout page) if let LoginState::Yes | LoginState::No = *self.state.get() { diff --git a/examples/demos/auth/src/templates/index.rs b/examples/demos/auth/src/templates/index.rs index 893b06e55b..502e08dbb9 100644 --- a/examples/demos/auth/src/templates/index.rs +++ b/examples/demos/auth/src/templates/index.rs @@ -21,6 +21,7 @@ fn index_view<'a, G: Html>(cx: Scope<'a>, _: (), AppStateRx { auth }: AppStateRx view! { cx, h1 { (format!("Welcome back, {}!", &username)) } button(on:click = |_| { + #[cfg(target_arch = "wasm32")] auth.logout(); }) { "Logout" } } @@ -30,6 +31,7 @@ fn index_view<'a, G: Html>(cx: Scope<'a>, _: (), AppStateRx { auth }: AppStateRx h1 { "Welcome, stranger!" } input(bind:value = entered_username, placeholder = "Username") button(on:click = |_| { + #[cfg(target_arch = "wasm32")] auth.login(&entered_username.get()) }) { "Login" } }, diff --git a/examples/demos/fetching/Cargo.toml b/examples/demos/fetching/Cargo.toml index d95d720379..fd86c300de 100644 --- a/examples/demos/fetching/Cargo.toml +++ b/examples/demos/fetching/Cargo.toml @@ -10,8 +10,21 @@ perseus = { path = "../../../packages/perseus", features = [ "hydrate" ] } sycamore = "=0.8.0-beta.6" serde = { version = "1", features = ["derive"] } serde_json = "1" -reqwasm = "0.4" -# This makes sure `reqwest` is only included on the server-side (it won't compile at all for the browser) [target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } +perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } reqwest = "0.11" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = "0.2" +reqwasm = "0.4" + +[lib] +name = "lib" +path = "src/lib.rs" +crate-type = [ "cdylib", "rlib" ] + +[[bin]] +name = "perseus-example-fetching" +path = "src/lib.rs" diff --git a/examples/demos/fetching/src/lib.rs b/examples/demos/fetching/src/lib.rs index 7522e8240a..8718528ae7 100644 --- a/examples/demos/fetching/src/lib.rs +++ b/examples/demos/fetching/src/lib.rs @@ -3,7 +3,7 @@ mod templates; use perseus::{Html, PerseusApp}; -#[perseus::main] +#[perseus::main(perseus_warp::dflt_server)] pub fn main() -> PerseusApp { PerseusApp::new() .template(crate::templates::index::get_template) diff --git a/examples/demos/fetching/src/templates/index.rs b/examples/demos/fetching/src/templates/index.rs index b36653acb4..b1e5bb0604 100644 --- a/examples/demos/fetching/src/templates/index.rs +++ b/examples/demos/fetching/src/templates/index.rs @@ -18,6 +18,7 @@ pub fn index_page<'a, G: Html>( // This will only run in the browser // `reqwasm` wraps browser-specific APIs, so we don't want it running on the server // If the browser IP has already been fetched (e.g. if we've come here for the second time in the same session), we won't bother re-fetching + #[cfg(target_arch = "wasm32")] // Because we only have `reqwasm` on the client-side, we make sure this is only *compiled* in the browser as well if G::IS_BROWSER && browser_ip.get().is_none() { // Spawn a `Future` on this thread to fetch the data (`spawn_local` is re-exported from `wasm-bindgen-futures`) // Don't worry, this doesn't need to be sent to JavaScript for execution @@ -62,8 +63,6 @@ pub async fn get_build_state( _locale: String, ) -> RenderFnResultWithCause { // We'll cache the result with `try_cache_res`, which means we only make the request once, and future builds will use the cached result (speeds up development) - // Currently, target gating isn't fully sorted out in the latest version, so, because `reqwest` is only available on the server-side, we have to note that (in future, this won't be necessary) - #[cfg(not(target_arch = "wasm32"))] let body = perseus::cache_fallible_res( "ipify", || async { @@ -74,9 +73,6 @@ pub async fn get_build_state( false, ) .await?; - // To be clear, this will never ever run, we just need it in the current version to appease the compiler (soon, this will be totally unnecessary) - #[cfg(target_arch = "wasm32")] - let body = "".to_string(); Ok(IndexPageState { server_ip: body, From ace810e35074e651b0de6dc0e1774149fb931e97 Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Tue, 14 Jun 2022 17:05:13 +1000 Subject: [PATCH 18/37] style: appeased clippy --- examples/demos/fetching/src/templates/index.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/demos/fetching/src/templates/index.rs b/examples/demos/fetching/src/templates/index.rs index b1e5bb0604..05a200d03c 100644 --- a/examples/demos/fetching/src/templates/index.rs +++ b/examples/demos/fetching/src/templates/index.rs @@ -18,7 +18,8 @@ pub fn index_page<'a, G: Html>( // This will only run in the browser // `reqwasm` wraps browser-specific APIs, so we don't want it running on the server // If the browser IP has already been fetched (e.g. if we've come here for the second time in the same session), we won't bother re-fetching - #[cfg(target_arch = "wasm32")] // Because we only have `reqwasm` on the client-side, we make sure this is only *compiled* in the browser as well + #[cfg(target_arch = "wasm32")] + // Because we only have `reqwasm` on the client-side, we make sure this is only *compiled* in the browser as well if G::IS_BROWSER && browser_ip.get().is_none() { // Spawn a `Future` on this thread to fetch the data (`spawn_local` is re-exported from `wasm-bindgen-futures`) // Don't worry, this doesn't need to be sent to JavaScript for execution From 72f26618043c0f6550a743a63758b0f423f71a03 Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Tue, 14 Jun 2022 17:11:03 +1000 Subject: [PATCH 19/37] fix: fixed unused code warnings from macros Just made the user's functions `pub` to stop the compiler whining. --- packages/perseus-macro/src/entrypoint.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/perseus-macro/src/entrypoint.rs b/packages/perseus-macro/src/entrypoint.rs index afe919438f..e52d9d4849 100644 --- a/packages/perseus-macro/src/entrypoint.rs +++ b/packages/perseus-macro/src/entrypoint.rs @@ -183,7 +183,8 @@ pub fn main_impl(input: MainFn, server_fn: Path) -> TokenStream { // The user's function (which gets the `PerseusApp`) #(#attrs)* - fn __perseus_simple_main #generics() -> #return_type { + #[doc(hidden)] + pub fn __perseus_simple_main #generics() -> #return_type { #block } }; From fa7ab64a9a7711d8b1970b067257e08dc6bedb5b Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Tue, 14 Jun 2022 18:20:00 +1000 Subject: [PATCH 20/37] feat: added `#[main_export]` for apps not using a server --- examples/core/basic/src/lib.rs | 3 +- packages/perseus-macro/src/entrypoint.rs | 38 ++++++++++++++++++++++++ packages/perseus-macro/src/lib.rs | 10 +++++++ packages/perseus/src/lib.rs | 4 +-- 4 files changed, 52 insertions(+), 3 deletions(-) diff --git a/examples/core/basic/src/lib.rs b/examples/core/basic/src/lib.rs index 30e1c73070..ee25d69b89 100644 --- a/examples/core/basic/src/lib.rs +++ b/examples/core/basic/src/lib.rs @@ -26,7 +26,8 @@ use perseus::{Html, PerseusApp}; // run_client(get_app) // } -#[perseus::main(perseus_warp::dflt_server)] +// #[perseus::main(perseus_warp::dflt_server)] +#[perseus::main_export] pub fn main() -> PerseusApp { PerseusApp::new() .template(crate::templates::index::get_template) diff --git a/packages/perseus-macro/src/entrypoint.rs b/packages/perseus-macro/src/entrypoint.rs index e52d9d4849..76d36653ec 100644 --- a/packages/perseus-macro/src/entrypoint.rs +++ b/packages/perseus-macro/src/entrypoint.rs @@ -192,6 +192,44 @@ pub fn main_impl(input: MainFn, server_fn: Path) -> TokenStream { output } +pub fn main_export_impl(input: MainFn) -> TokenStream { + let MainFn { + block, + generics, + attrs, + return_type, + } = input; + + // We split the user's function out into one for the browser and one for the engine (all based around the default engine) + let output = quote! { + // The engine-specific `main` function + #[cfg(not(target_arch = "wasm32"))] + #[tokio::main] + async fn main() { + // Get the operation we're supposed to run (serve, build, export, etc.) from an environment variable + let op = ::perseus::builder::get_op().unwrap(); + let exit_code = ::perseus::builder::run_dflt_engine_export_only(op, __perseus_simple_main).await; + std::process::exit(exit_code); + } + + // The browser-specific `main` function + #[cfg(target_arch = "wasm32")] + #[wasm_bindgen::prelude::wasm_bindgen] + pub fn main() -> ::perseus::ClientReturn { + ::perseus::run_client(__perseus_simple_main) + } + + // The user's function (which gets the `PerseusApp`) + #(#attrs)* + #[doc(hidden)] + pub fn __perseus_simple_main #generics() -> #return_type { + #block + } + }; + + output +} + pub fn browser_main_impl(input: MainFn) -> TokenStream { let MainFn { block, diff --git a/packages/perseus-macro/src/lib.rs b/packages/perseus-macro/src/lib.rs index b4111209e2..1b8df56c02 100644 --- a/packages/perseus-macro/src/lib.rs +++ b/packages/perseus-macro/src/lib.rs @@ -116,6 +116,16 @@ pub fn main(args: TokenStream, input: TokenStream) -> TokenStream { entrypoint::main_impl(parsed, args).into() } +/// This is identical to `#[main]`, except it doesn't require a server integration, because it sets your app up for exporting only. This is useful for +/// apps not using server-requiring features (like incremental static generation and revalidation) that want to avoid bringing in another dependency on +/// the server-side. +#[proc_macro_attribute] +pub fn main_export(_args: TokenStream, input: TokenStream) -> TokenStream { + let parsed = syn::parse_macro_input!(input as entrypoint::MainFn); + + entrypoint::main_export_impl(parsed).into() +} + /// Marks the given function as the browser entrypoint into your app. This is designed for more complex apps that need to manually distinguish between /// the engine and browser entrypoints. /// diff --git a/packages/perseus/src/lib.rs b/packages/perseus/src/lib.rs index 772dc91810..e2e88173a4 100644 --- a/packages/perseus/src/lib.rs +++ b/packages/perseus/src/lib.rs @@ -70,8 +70,8 @@ pub type Request = HttpRequest<()>; pub use client::{run_client, ClientReturn}; #[cfg(feature = "macros")] pub use perseus_macro::{ - autoserde, browser, browser_main, engine, engine_main, head, main, make_rx, template, - template_rx, test, + autoserde, browser, browser_main, engine, engine_main, head, main, main_export, make_rx, + template, template_rx, test, }; pub use sycamore::prelude::{DomNode, Html, HydrateNode, SsrNode}; pub use sycamore_router::{navigate, navigate_replace, Route}; // TODO Should we be exporting `Route` anymore? From 13bfe56939d4e71b1107fbf545a96e8a9c3a2f03 Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Tue, 14 Jun 2022 20:26:19 +1000 Subject: [PATCH 21/37] feat: added support for custom cargo/wasm-pack args to cli --- packages/perseus-cli/src/build.rs | 10 ++++++---- packages/perseus-cli/src/export.rs | 10 ++++++---- packages/perseus-cli/src/export_error_page.rs | 3 ++- packages/perseus-cli/src/serve.rs | 3 ++- packages/perseus-cli/src/tinker.rs | 3 ++- 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/packages/perseus-cli/src/build.rs b/packages/perseus-cli/src/build.rs index 98714aa535..916479cab9 100644 --- a/packages/perseus-cli/src/build.rs +++ b/packages/perseus-cli/src/build.rs @@ -78,9 +78,10 @@ pub fn build_internal( let sg_thread = spawn_thread(move || { handle_exit_code!(run_stage( vec![&format!( - "{} run {}", + "{} run {} {}", env::var("PERSEUS_CARGO_PATH").unwrap_or_else(|_| "cargo".to_string()), - if is_release { "--release" } else { "" } + if is_release { "--release" } else { "" }, + env::var("PERSEUS_CARGO_ARGS").unwrap_or_else(|_| String::new()) )], &sg_dir, &sg_spinner, @@ -93,9 +94,10 @@ pub fn build_internal( let wb_thread = spawn_thread(move || { handle_exit_code!(run_stage( vec![&format!( - "{} build --out-dir dist/pkg --out-name perseus_engine --target web {}", + "{} build --out-dir dist/pkg --out-name perseus_engine --target web {} {}", env::var("PERSEUS_WASM_PACK_PATH").unwrap_or_else(|_| "wasm-pack".to_string()), - if is_release { "--release" } else { "--dev" } // If we don't supply `--dev`, another profile will be used + if is_release { "--release" } else { "--dev" }, // If we don't supply `--dev`, another profile will be used + env::var("PERSEUS_WASM_PACK_ARGS").unwrap_or_else(|_| String::new()) )], &wb_dir, &wb_spinner, diff --git a/packages/perseus-cli/src/export.rs b/packages/perseus-cli/src/export.rs index 4a210eb490..86c20365b3 100644 --- a/packages/perseus-cli/src/export.rs +++ b/packages/perseus-cli/src/export.rs @@ -151,9 +151,10 @@ pub fn export_internal( let ep_thread = spawn_thread(move || { handle_exit_code!(run_stage( vec![&format!( - "{} run {}", + "{} run {} {}", env::var("PERSEUS_CARGO_PATH").unwrap_or_else(|_| "cargo".to_string()), - if is_release { "--release" } else { "" } + if is_release { "--release" } else { "" }, + env::var("PERSEUS_CARGO_ARGS").unwrap_or_else(|_| String::new()) )], &ep_target, &ep_spinner, @@ -166,9 +167,10 @@ pub fn export_internal( let wb_thread = spawn_thread(move || { handle_exit_code!(run_stage( vec![&format!( - "{} build --out-dir dist/pkg --out-name perseus_engine --target web {}", + "{} build --out-dir dist/pkg --out-name perseus_engine --target web {} {}", env::var("PERSEUS_WASM_PACK_PATH").unwrap_or_else(|_| "wasm-pack".to_string()), - if is_release { "--release" } else { "--dev" } + if is_release { "--release" } else { "--dev" }, + env::var("PERSEUS_WASM_PACK_ARGS").unwrap_or_else(|_| String::new()) )], &wb_target, &wb_spinner, diff --git a/packages/perseus-cli/src/export_error_page.rs b/packages/perseus-cli/src/export_error_page.rs index d9c264f833..62ffd23426 100644 --- a/packages/perseus-cli/src/export_error_page.rs +++ b/packages/perseus-cli/src/export_error_page.rs @@ -8,8 +8,9 @@ use std::path::PathBuf; pub fn export_error_page(dir: PathBuf, opts: ExportErrorPageOpts) -> Result { run_cmd_directly( format!( - "{} run {} {}", + "{} {} run {} {}", env::var("PERSEUS_CARGO_PATH").unwrap_or_else(|_| "cargo".to_string()), + env::var("PERSEUS_CARGO_ARGS").unwrap_or_else(|_| String::new()), // These are mandatory opts.code, opts.output, diff --git a/packages/perseus-cli/src/serve.rs b/packages/perseus-cli/src/serve.rs index 06d1a48177..5df7db896e 100644 --- a/packages/perseus-cli/src/serve.rs +++ b/packages/perseus-cli/src/serve.rs @@ -63,9 +63,10 @@ fn build_server( let (stdout, _stderr) = handle_exit_code!(run_stage( vec![&format!( // This sets Cargo to tell us everything, including the executable path to the server - "{} build --message-format json {}", + "{} build --message-format json {} {}", env::var("PERSEUS_CARGO_PATH").unwrap_or_else(|_| "cargo".to_string()), if is_release { "--release" } else { "" }, + env::var("PERSEUS_CARGO_ARGS").unwrap_or_else(|_| String::new()) )], &sb_target, &sb_spinner, diff --git a/packages/perseus-cli/src/tinker.rs b/packages/perseus-cli/src/tinker.rs index c21c9e4090..be3959b53f 100644 --- a/packages/perseus-cli/src/tinker.rs +++ b/packages/perseus-cli/src/tinker.rs @@ -45,8 +45,9 @@ pub fn tinker_internal( let tk_thread = spawn_thread(move || { handle_exit_code!(run_stage( vec![&format!( - "{} run", + "{} run {}", env::var("PERSEUS_CARGO_PATH").unwrap_or_else(|_| "cargo".to_string()), + env::var("PERSEUS_CARGO_ARGS").unwrap_or_else(|_| String::new()) )], &tk_target, &tk_spinner, From 7bbb911c9b12d7207375751657787349c144a043 Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Tue, 14 Jun 2022 20:28:07 +1000 Subject: [PATCH 22/37] fix: fixed actix dflt server return type --- packages/perseus-actix-web/src/dflt_server.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/perseus-actix-web/src/dflt_server.rs b/packages/perseus-actix-web/src/dflt_server.rs index f339423509..48957c1e03 100644 --- a/packages/perseus-actix-web/src/dflt_server.rs +++ b/packages/perseus-actix-web/src/dflt_server.rs @@ -32,4 +32,5 @@ pub async fn dflt_server Date: Sat, 18 Jun 2022 15:24:28 +1000 Subject: [PATCH 23/37] test: made all examples work with all integrations There's now an `EXAMPLE_INTEGRATION` environment variable that controls this, which is set to `warp` by default in a new `.env` file, which Bonnie can read. --- .env | 1 + bonnie.toml | 7 +++++-- examples/comprehensive/tiny/.gitignore | 3 +-- examples/comprehensive/tiny/Cargo.toml | 2 +- examples/comprehensive/tiny/src/lib.rs | 2 +- examples/core/basic/Cargo.toml | 3 ++- examples/core/basic/src/lib.rs | 3 +-- examples/core/freezing_and_thawing/Cargo.toml | 2 +- examples/core/freezing_and_thawing/src/lib.rs | 2 +- examples/core/global_state/Cargo.toml | 2 +- examples/core/global_state/src/lib.rs | 2 +- examples/core/i18n/Cargo.toml | 2 +- examples/core/i18n/src/lib.rs | 2 +- examples/core/idb_freezing/Cargo.toml | 2 +- examples/core/idb_freezing/src/lib.rs | 2 +- examples/core/index_view/Cargo.toml | 2 +- examples/core/index_view/src/lib.rs | 2 +- examples/core/plugins/.gitignore | 2 +- examples/core/plugins/Cargo.toml | 2 +- examples/core/plugins/src/lib.rs | 2 +- examples/core/router_state/.gitignore | 2 +- examples/core/router_state/Cargo.toml | 2 +- examples/core/router_state/src/lib.rs | 2 +- examples/core/rx_state/.gitignore | 2 +- examples/core/rx_state/Cargo.toml | 2 +- examples/core/rx_state/src/lib.rs | 2 +- examples/core/set_headers/.gitignore | 2 +- examples/core/set_headers/Cargo.toml | 2 +- examples/core/set_headers/src/lib.rs | 2 +- examples/core/state_generation/.gitignore | 2 +- examples/core/state_generation/Cargo.toml | 2 +- examples/core/state_generation/src/lib.rs | 2 +- examples/core/static_content/.gitignore | 2 +- examples/core/static_content/Cargo.toml | 2 +- examples/core/static_content/src/lib.rs | 2 +- examples/core/unreactive/.gitignore | 2 +- examples/core/unreactive/Cargo.toml | 2 +- examples/core/unreactive/src/lib.rs | 2 +- examples/demos/auth/.gitignore | 2 +- examples/demos/auth/Cargo.toml | 2 +- examples/demos/auth/src/lib.rs | 2 +- examples/demos/fetching/.gitignore | 2 +- examples/demos/fetching/Cargo.toml | 2 +- examples/demos/fetching/src/lib.rs | 2 +- packages/perseus-integration/Cargo.toml | 18 ++++++++++++++++++ packages/perseus-integration/README.md | 5 +++++ packages/perseus-integration/src/lib.rs | 6 ++++++ 47 files changed, 78 insertions(+), 46 deletions(-) create mode 100644 .env create mode 100644 packages/perseus-integration/Cargo.toml create mode 100644 packages/perseus-integration/README.md create mode 100644 packages/perseus-integration/src/lib.rs diff --git a/.env b/.env new file mode 100644 index 0000000000..0cf3b0bb60 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +EXAMPLE_INTEGRATION=warp diff --git a/bonnie.toml b/bonnie.toml index 074c9bdb53..85cd7fe219 100644 --- a/bonnie.toml +++ b/bonnie.toml @@ -1,4 +1,5 @@ version="0.3.2" +env_files = [ ".env" ] [scripts] setup.cmd.generic = [ @@ -40,17 +41,19 @@ dev.subcommands.export-serve-deploy-relative.cmd.targets.windows = [ dev.subcommands.export-serve-deploy-relative.args = [ "category", "example" ] dev.subcommands.export-serve-deploy-relative.desc = "deploys (exported) and serves the given example at a relative local path" +# TODO Make this not set the integration feature unless a certain file in the example calls for it dev.subcommands.example.cmd.generic = [ "cd packages/perseus-cli", # Point this live version of the CLI at the given example - "TEST_EXAMPLE=../../examples/%category/%example cargo run -- %%" + "TEST_EXAMPLE=../../examples/%category/%example PERSEUS_CARGO_ARGS=\"--features \"perseus-integration/%EXAMPLE_INTEGRATION\"\" cargo run -- %%" ] dev.subcommands.example.cmd.targets.windows = [ "cd packages\\perseus-cli", # Point this live version of the CLI at the given example - "powershell -Command { $env:TEST_EXAMPLE=\"..\\..\\examples\\%category\\%example\"; cargo run -- %% }" + "powershell -Command { $env:TEST_EXAMPLE=\"..\\..\\examples\\%category\\%example\"; $end:PERSEUS_CARGO_ARGS=\"--features \"perseus-integration/%EXAMPLE_INTEGRATION\"\"; cargo run -- %% }" ] dev.subcommands.example.args = [ "category", "example" ] +dev.subcommands.example.env_vars = [ "EXAMPLE_INTEGRATION" ] # This will be set automatically to Warp by `.env` unless overridden dev.subcommands.example.desc = "runs the given example using a live version of the cli" site.cmd = "concurrently \"bonnie site export\" \"bonnie site build-tailwind\"" diff --git a/examples/comprehensive/tiny/.gitignore b/examples/comprehensive/tiny/.gitignore index 19f4c83304..849ddff3b7 100644 --- a/examples/comprehensive/tiny/.gitignore +++ b/examples/comprehensive/tiny/.gitignore @@ -1,2 +1 @@ - -.perseus/ \ No newline at end of file +dist/ diff --git a/examples/comprehensive/tiny/Cargo.toml b/examples/comprehensive/tiny/Cargo.toml index cfcc9d4217..8d6ffa0cda 100644 --- a/examples/comprehensive/tiny/Cargo.toml +++ b/examples/comprehensive/tiny/Cargo.toml @@ -11,7 +11,7 @@ sycamore = "=0.8.0-beta.6" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } -perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } +perseus-integration = { path = "../../../packages/perseus-integration", default-features = false } [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen = "0.2" diff --git a/examples/comprehensive/tiny/src/lib.rs b/examples/comprehensive/tiny/src/lib.rs index 490723d7ab..f25d1d1d7f 100644 --- a/examples/comprehensive/tiny/src/lib.rs +++ b/examples/comprehensive/tiny/src/lib.rs @@ -1,7 +1,7 @@ use perseus::{Html, PerseusApp, Template}; use sycamore::view; -#[perseus::main(perseus_warp::dflt_server)] +#[perseus::main(perseus_integration::dflt_server)] pub fn main() -> PerseusApp { PerseusApp::new().template(|| { Template::new("index").template(|cx, _| { diff --git a/examples/core/basic/Cargo.toml b/examples/core/basic/Cargo.toml index b78d305197..3b15403bf5 100644 --- a/examples/core/basic/Cargo.toml +++ b/examples/core/basic/Cargo.toml @@ -16,7 +16,8 @@ fantoccini = "0.17" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } -perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } +# This is an internal convenience crate that exposes all integrations through features for testing +perseus-integration = { path = "../../../packages/perseus-integration", default-features = false } [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen = "0.2" diff --git a/examples/core/basic/src/lib.rs b/examples/core/basic/src/lib.rs index ee25d69b89..a9bac9190f 100644 --- a/examples/core/basic/src/lib.rs +++ b/examples/core/basic/src/lib.rs @@ -26,8 +26,7 @@ use perseus::{Html, PerseusApp}; // run_client(get_app) // } -// #[perseus::main(perseus_warp::dflt_server)] -#[perseus::main_export] +#[perseus::main(perseus_integration::dflt_server)] pub fn main() -> PerseusApp { PerseusApp::new() .template(crate::templates::index::get_template) diff --git a/examples/core/freezing_and_thawing/Cargo.toml b/examples/core/freezing_and_thawing/Cargo.toml index e2ad6995b8..17af13133f 100644 --- a/examples/core/freezing_and_thawing/Cargo.toml +++ b/examples/core/freezing_and_thawing/Cargo.toml @@ -16,7 +16,7 @@ fantoccini = "0.17" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } -perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } +perseus-integration = { path = "../../../packages/perseus-integration", default-features = false } [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen = "0.2" diff --git a/examples/core/freezing_and_thawing/src/lib.rs b/examples/core/freezing_and_thawing/src/lib.rs index fe6874d611..04a6fad613 100644 --- a/examples/core/freezing_and_thawing/src/lib.rs +++ b/examples/core/freezing_and_thawing/src/lib.rs @@ -4,7 +4,7 @@ mod templates; use perseus::{Html, PerseusApp}; -#[perseus::main(perseus_warp::dflt_server)] +#[perseus::main(perseus_integration::dflt_server)] pub fn main() -> PerseusApp { PerseusApp::new() .template(crate::templates::index::get_template) diff --git a/examples/core/global_state/Cargo.toml b/examples/core/global_state/Cargo.toml index d57a4666f8..089f927069 100644 --- a/examples/core/global_state/Cargo.toml +++ b/examples/core/global_state/Cargo.toml @@ -16,7 +16,7 @@ fantoccini = "0.17" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } -perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } +perseus-integration = { path = "../../../packages/perseus-integration", default-features = false } [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen = "0.2" diff --git a/examples/core/global_state/src/lib.rs b/examples/core/global_state/src/lib.rs index fe6874d611..04a6fad613 100644 --- a/examples/core/global_state/src/lib.rs +++ b/examples/core/global_state/src/lib.rs @@ -4,7 +4,7 @@ mod templates; use perseus::{Html, PerseusApp}; -#[perseus::main(perseus_warp::dflt_server)] +#[perseus::main(perseus_integration::dflt_server)] pub fn main() -> PerseusApp { PerseusApp::new() .template(crate::templates::index::get_template) diff --git a/examples/core/i18n/Cargo.toml b/examples/core/i18n/Cargo.toml index db8dca5966..e1b2ed9c4b 100644 --- a/examples/core/i18n/Cargo.toml +++ b/examples/core/i18n/Cargo.toml @@ -18,7 +18,7 @@ fantoccini = "0.17" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } -perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } +perseus-integration = { path = "../../../packages/perseus-integration", default-features = false } [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen = "0.2" diff --git a/examples/core/i18n/src/lib.rs b/examples/core/i18n/src/lib.rs index bcd84fe050..3c598146f9 100644 --- a/examples/core/i18n/src/lib.rs +++ b/examples/core/i18n/src/lib.rs @@ -3,7 +3,7 @@ mod templates; use perseus::{Html, PerseusApp}; -#[perseus::main(perseus_warp::dflt_server)] +#[perseus::main(perseus_integration::dflt_server)] pub fn main() -> PerseusApp { PerseusApp::new() .template(crate::templates::index::get_template) diff --git a/examples/core/idb_freezing/Cargo.toml b/examples/core/idb_freezing/Cargo.toml index c26a595b1b..5f9501a1e2 100644 --- a/examples/core/idb_freezing/Cargo.toml +++ b/examples/core/idb_freezing/Cargo.toml @@ -17,7 +17,7 @@ fantoccini = "0.17" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } -perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } +perseus-integration = { path = "../../../packages/perseus-integration", default-features = false } [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen = "0.2" diff --git a/examples/core/idb_freezing/src/lib.rs b/examples/core/idb_freezing/src/lib.rs index fe6874d611..04a6fad613 100644 --- a/examples/core/idb_freezing/src/lib.rs +++ b/examples/core/idb_freezing/src/lib.rs @@ -4,7 +4,7 @@ mod templates; use perseus::{Html, PerseusApp}; -#[perseus::main(perseus_warp::dflt_server)] +#[perseus::main(perseus_integration::dflt_server)] pub fn main() -> PerseusApp { PerseusApp::new() .template(crate::templates::index::get_template) diff --git a/examples/core/index_view/Cargo.toml b/examples/core/index_view/Cargo.toml index 4515745800..539cf85285 100644 --- a/examples/core/index_view/Cargo.toml +++ b/examples/core/index_view/Cargo.toml @@ -16,7 +16,7 @@ fantoccini = "0.17" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } -perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } +perseus-integration = { path = "../../../packages/perseus-integration", default-features = false } [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen = "0.2" diff --git a/examples/core/index_view/src/lib.rs b/examples/core/index_view/src/lib.rs index 2ac282163d..2056f58dba 100644 --- a/examples/core/index_view/src/lib.rs +++ b/examples/core/index_view/src/lib.rs @@ -3,7 +3,7 @@ mod templates; use perseus::{Html, PerseusApp, PerseusRoot}; -#[perseus::main(perseus_warp::dflt_server)] +#[perseus::main(perseus_integration::dflt_server)] pub fn main() -> PerseusApp { PerseusApp::new() .template(crate::templates::index::get_template) diff --git a/examples/core/plugins/.gitignore b/examples/core/plugins/.gitignore index 9405098b45..849ddff3b7 100644 --- a/examples/core/plugins/.gitignore +++ b/examples/core/plugins/.gitignore @@ -1 +1 @@ -.perseus/ +dist/ diff --git a/examples/core/plugins/Cargo.toml b/examples/core/plugins/Cargo.toml index 68929eaecd..63b2eb4d1c 100644 --- a/examples/core/plugins/Cargo.toml +++ b/examples/core/plugins/Cargo.toml @@ -17,7 +17,7 @@ fantoccini = "0.17" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } -perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } +perseus-integration = { path = "../../../packages/perseus-integration", default-features = false } [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen = "0.2" diff --git a/examples/core/plugins/src/lib.rs b/examples/core/plugins/src/lib.rs index 5580f72681..a52807f4ca 100644 --- a/examples/core/plugins/src/lib.rs +++ b/examples/core/plugins/src/lib.rs @@ -4,7 +4,7 @@ mod templates; use perseus::{Html, PerseusApp, Plugins}; -#[perseus::main(perseus_warp::dflt_server)] +#[perseus::main(perseus_integration::dflt_server)] pub fn main() -> PerseusApp { PerseusApp::new() .template(crate::templates::index::get_template) diff --git a/examples/core/router_state/.gitignore b/examples/core/router_state/.gitignore index 9405098b45..849ddff3b7 100644 --- a/examples/core/router_state/.gitignore +++ b/examples/core/router_state/.gitignore @@ -1 +1 @@ -.perseus/ +dist/ diff --git a/examples/core/router_state/Cargo.toml b/examples/core/router_state/Cargo.toml index fd138a978c..1020cb1ec9 100644 --- a/examples/core/router_state/Cargo.toml +++ b/examples/core/router_state/Cargo.toml @@ -13,7 +13,7 @@ serde_json = "1" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } -perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } +perseus-integration = { path = "../../../packages/perseus-integration", default-features = false } [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen = "0.2" diff --git a/examples/core/router_state/src/lib.rs b/examples/core/router_state/src/lib.rs index eee8b18aa3..172b4388b5 100644 --- a/examples/core/router_state/src/lib.rs +++ b/examples/core/router_state/src/lib.rs @@ -3,7 +3,7 @@ mod templates; use perseus::{Html, PerseusApp}; -#[perseus::main(perseus_warp::dflt_server)] +#[perseus::main(perseus_integration::dflt_server)] pub fn main() -> PerseusApp { PerseusApp::new() .template(crate::templates::index::get_template) diff --git a/examples/core/rx_state/.gitignore b/examples/core/rx_state/.gitignore index 9405098b45..849ddff3b7 100644 --- a/examples/core/rx_state/.gitignore +++ b/examples/core/rx_state/.gitignore @@ -1 +1 @@ -.perseus/ +dist/ diff --git a/examples/core/rx_state/Cargo.toml b/examples/core/rx_state/Cargo.toml index c68a9e7aa3..54d4eb6b3c 100644 --- a/examples/core/rx_state/Cargo.toml +++ b/examples/core/rx_state/Cargo.toml @@ -16,7 +16,7 @@ fantoccini = "0.17" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } -perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } +perseus-integration = { path = "../../../packages/perseus-integration", default-features = false } [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen = "0.2" diff --git a/examples/core/rx_state/src/lib.rs b/examples/core/rx_state/src/lib.rs index eee8b18aa3..172b4388b5 100644 --- a/examples/core/rx_state/src/lib.rs +++ b/examples/core/rx_state/src/lib.rs @@ -3,7 +3,7 @@ mod templates; use perseus::{Html, PerseusApp}; -#[perseus::main(perseus_warp::dflt_server)] +#[perseus::main(perseus_integration::dflt_server)] pub fn main() -> PerseusApp { PerseusApp::new() .template(crate::templates::index::get_template) diff --git a/examples/core/set_headers/.gitignore b/examples/core/set_headers/.gitignore index 9405098b45..849ddff3b7 100644 --- a/examples/core/set_headers/.gitignore +++ b/examples/core/set_headers/.gitignore @@ -1 +1 @@ -.perseus/ +dist/ diff --git a/examples/core/set_headers/Cargo.toml b/examples/core/set_headers/Cargo.toml index b5127b3484..d485cffef9 100644 --- a/examples/core/set_headers/Cargo.toml +++ b/examples/core/set_headers/Cargo.toml @@ -17,7 +17,7 @@ ureq = "2" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } -perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } +perseus-integration = { path = "../../../packages/perseus-integration", default-features = false } [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen = "0.2" diff --git a/examples/core/set_headers/src/lib.rs b/examples/core/set_headers/src/lib.rs index 8718528ae7..9a777e008a 100644 --- a/examples/core/set_headers/src/lib.rs +++ b/examples/core/set_headers/src/lib.rs @@ -3,7 +3,7 @@ mod templates; use perseus::{Html, PerseusApp}; -#[perseus::main(perseus_warp::dflt_server)] +#[perseus::main(perseus_integration::dflt_server)] pub fn main() -> PerseusApp { PerseusApp::new() .template(crate::templates::index::get_template) diff --git a/examples/core/state_generation/.gitignore b/examples/core/state_generation/.gitignore index 9405098b45..849ddff3b7 100644 --- a/examples/core/state_generation/.gitignore +++ b/examples/core/state_generation/.gitignore @@ -1 +1 @@ -.perseus/ +dist/ diff --git a/examples/core/state_generation/Cargo.toml b/examples/core/state_generation/Cargo.toml index 295b09fed0..f1137e0ef5 100644 --- a/examples/core/state_generation/Cargo.toml +++ b/examples/core/state_generation/Cargo.toml @@ -16,7 +16,7 @@ fantoccini = "0.17" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } -perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } +perseus-integration = { path = "../../../packages/perseus-integration", default-features = false } [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen = "0.2" diff --git a/examples/core/state_generation/src/lib.rs b/examples/core/state_generation/src/lib.rs index 4c5613ed58..774fbe4b93 100644 --- a/examples/core/state_generation/src/lib.rs +++ b/examples/core/state_generation/src/lib.rs @@ -3,7 +3,7 @@ mod templates; use perseus::{Html, PerseusApp}; -#[perseus::main(perseus_warp::dflt_server)] +#[perseus::main(perseus_integration::dflt_server)] pub fn main() -> PerseusApp { PerseusApp::new() .template(crate::templates::build_state::get_template) diff --git a/examples/core/static_content/.gitignore b/examples/core/static_content/.gitignore index 9405098b45..849ddff3b7 100644 --- a/examples/core/static_content/.gitignore +++ b/examples/core/static_content/.gitignore @@ -1 +1 @@ -.perseus/ +dist/ diff --git a/examples/core/static_content/Cargo.toml b/examples/core/static_content/Cargo.toml index 5213a8a629..29ff46a48d 100644 --- a/examples/core/static_content/Cargo.toml +++ b/examples/core/static_content/Cargo.toml @@ -16,7 +16,7 @@ fantoccini = "0.17" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } -perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } +perseus-integration = { path = "../../../packages/perseus-integration", default-features = false } [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen = "0.2" diff --git a/examples/core/static_content/src/lib.rs b/examples/core/static_content/src/lib.rs index c4eac85941..0b68f29874 100644 --- a/examples/core/static_content/src/lib.rs +++ b/examples/core/static_content/src/lib.rs @@ -3,7 +3,7 @@ mod templates; use perseus::{Html, PerseusApp}; -#[perseus::main(perseus_warp::dflt_server)] +#[perseus::main(perseus_integration::dflt_server)] pub fn main() -> PerseusApp { PerseusApp::new() .template(crate::templates::index::get_template) diff --git a/examples/core/unreactive/.gitignore b/examples/core/unreactive/.gitignore index 9405098b45..849ddff3b7 100644 --- a/examples/core/unreactive/.gitignore +++ b/examples/core/unreactive/.gitignore @@ -1 +1 @@ -.perseus/ +dist/ diff --git a/examples/core/unreactive/Cargo.toml b/examples/core/unreactive/Cargo.toml index d7aaaa5f94..9528ce1c7d 100644 --- a/examples/core/unreactive/Cargo.toml +++ b/examples/core/unreactive/Cargo.toml @@ -16,7 +16,7 @@ fantoccini = "0.17" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } -perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } +perseus-integration = { path = "../../../packages/perseus-integration", default-features = false } [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen = "0.2" diff --git a/examples/core/unreactive/src/lib.rs b/examples/core/unreactive/src/lib.rs index eee8b18aa3..172b4388b5 100644 --- a/examples/core/unreactive/src/lib.rs +++ b/examples/core/unreactive/src/lib.rs @@ -3,7 +3,7 @@ mod templates; use perseus::{Html, PerseusApp}; -#[perseus::main(perseus_warp::dflt_server)] +#[perseus::main(perseus_integration::dflt_server)] pub fn main() -> PerseusApp { PerseusApp::new() .template(crate::templates::index::get_template) diff --git a/examples/demos/auth/.gitignore b/examples/demos/auth/.gitignore index 9405098b45..849ddff3b7 100644 --- a/examples/demos/auth/.gitignore +++ b/examples/demos/auth/.gitignore @@ -1 +1 @@ -.perseus/ +dist/ diff --git a/examples/demos/auth/Cargo.toml b/examples/demos/auth/Cargo.toml index 88128f4f78..92fd6d3d16 100644 --- a/examples/demos/auth/Cargo.toml +++ b/examples/demos/auth/Cargo.toml @@ -14,7 +14,7 @@ serde_json = "1" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } -perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } +perseus-integration = { path = "../../../packages/perseus-integration", default-features = false } [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen = "0.2" diff --git a/examples/demos/auth/src/lib.rs b/examples/demos/auth/src/lib.rs index fe6874d611..04a6fad613 100644 --- a/examples/demos/auth/src/lib.rs +++ b/examples/demos/auth/src/lib.rs @@ -4,7 +4,7 @@ mod templates; use perseus::{Html, PerseusApp}; -#[perseus::main(perseus_warp::dflt_server)] +#[perseus::main(perseus_integration::dflt_server)] pub fn main() -> PerseusApp { PerseusApp::new() .template(crate::templates::index::get_template) diff --git a/examples/demos/fetching/.gitignore b/examples/demos/fetching/.gitignore index 9405098b45..849ddff3b7 100644 --- a/examples/demos/fetching/.gitignore +++ b/examples/demos/fetching/.gitignore @@ -1 +1 @@ -.perseus/ +dist/ diff --git a/examples/demos/fetching/Cargo.toml b/examples/demos/fetching/Cargo.toml index fd86c300de..65a69fe8dc 100644 --- a/examples/demos/fetching/Cargo.toml +++ b/examples/demos/fetching/Cargo.toml @@ -13,7 +13,7 @@ serde_json = "1" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] } -perseus-warp = { path = "../../../packages/perseus-warp", features = [ "dflt-server" ] } +perseus-integration = { path = "../../../packages/perseus-integration", default-features = false } reqwest = "0.11" [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/examples/demos/fetching/src/lib.rs b/examples/demos/fetching/src/lib.rs index 8718528ae7..9a777e008a 100644 --- a/examples/demos/fetching/src/lib.rs +++ b/examples/demos/fetching/src/lib.rs @@ -3,7 +3,7 @@ mod templates; use perseus::{Html, PerseusApp}; -#[perseus::main(perseus_warp::dflt_server)] +#[perseus::main(perseus_integration::dflt_server)] pub fn main() -> PerseusApp { PerseusApp::new() .template(crate::templates::index::get_template) diff --git a/packages/perseus-integration/Cargo.toml b/packages/perseus-integration/Cargo.toml new file mode 100644 index 0000000000..8d8e006eb4 --- /dev/null +++ b/packages/perseus-integration/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "perseus-integration" +version = "0.4.0-beta.1" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +perseus-actix-web = { path = "../perseus-actix-web", features = [ "dflt-server" ], optional = true } +perseus-warp = { path = "../perseus-warp", features = [ "dflt-server" ], optional = true } +perseus-axum = { path = "../perseus-axum", features = [ "dflt-server" ], optional = true } + +[features] +default = [ "warp" ] + +actix-web = [ "perseus-actix-web" ] +warp = [ "perseus-warp" ] +axum = [ "perseus-axum" ] diff --git a/packages/perseus-integration/README.md b/packages/perseus-integration/README.md new file mode 100644 index 0000000000..6b2ad02f3c --- /dev/null +++ b/packages/perseus-integration/README.md @@ -0,0 +1,5 @@ +# THIS CRATE IS FOR TESTING PURPOSES ONLY! + +It merely collates all the currently supported integrations and re-exposes their default servers through feature flags, enabling each of the examples to bring in just one dependency and then support all integrations through feature flags on this crate, which are specified by the CI testing framework. + +In other words, this is an internal convenience package used for testing. diff --git a/packages/perseus-integration/src/lib.rs b/packages/perseus-integration/src/lib.rs new file mode 100644 index 0000000000..2c2893d479 --- /dev/null +++ b/packages/perseus-integration/src/lib.rs @@ -0,0 +1,6 @@ +#[cfg(feature = "actix-web")] +pub use perseus_actix_web::dflt_server; +#[cfg(feature = "axum")] +pub use perseus_axum::dflt_server; +#[cfg(feature = "warp")] +pub use perseus_warp::dflt_server; From c4c7b562f031f7242aa04853bb55c97b7c7e8528 Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Sun, 19 Jun 2022 09:16:24 +1000 Subject: [PATCH 24/37] fix: fixed i18n translator misreference --- packages/perseus/src/i18n/translations_manager.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/perseus/src/i18n/translations_manager.rs b/packages/perseus/src/i18n/translations_manager.rs index d23e8ceda8..ef52fdeede 100644 --- a/packages/perseus/src/i18n/translations_manager.rs +++ b/packages/perseus/src/i18n/translations_manager.rs @@ -235,6 +235,6 @@ impl TranslationsManager for FsTranslationsManager { &self, _locale: String, ) -> Result { - Ok(crate::internal::i18n::DummyTranslator::new(String::new(), String::new()).unwrap()) + Ok(crate::internal::i18n::Translator::new(String::new(), String::new()).unwrap()) } } From 6665d11e5ce48d588985a0b5cea9a80af3d143a0 Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Sun, 19 Jun 2022 09:52:11 +1000 Subject: [PATCH 25/37] feat: superseded `autoserde` macro This also adds a `build_paths` macro to make that work with the new systems. --- examples/core/basic/src/templates/index.rs | 2 +- .../freezing_and_thawing/src/global_state.rs | 2 +- .../src/templates/index.rs | 2 +- .../core/global_state/src/global_state.rs | 2 +- examples/core/i18n/src/templates/post.rs | 2 +- .../core/idb_freezing/src/global_state.rs | 2 +- .../core/idb_freezing/src/templates/index.rs | 2 +- examples/core/rx_state/src/templates/index.rs | 2 +- .../core/set_headers/src/templates/index.rs | 4 +- .../src/templates/amalgamation.rs | 6 +- .../src/templates/build_paths.rs | 3 +- .../src/templates/build_state.rs | 2 +- .../src/templates/incremental_generation.rs | 3 +- .../src/templates/request_state.rs | 2 +- .../src/templates/revalidation.rs | 2 +- ...revalidation_and_incremental_generation.rs | 3 +- .../core/unreactive/src/templates/index.rs | 2 +- examples/demos/auth/src/global_state.rs | 2 +- .../demos/fetching/src/templates/index.rs | 2 +- packages/perseus-macro/src/lib.rs | 73 +++++++++++----- .../src/{autoserde.rs => state_fns.rs} | 86 +++++++++---------- packages/perseus/src/lib.rs | 5 +- 22 files changed, 122 insertions(+), 89 deletions(-) rename packages/perseus-macro/src/{autoserde.rs => state_fns.rs} (84%) diff --git a/examples/core/basic/src/templates/index.rs b/examples/core/basic/src/templates/index.rs index c019174d72..10082b7f02 100644 --- a/examples/core/basic/src/templates/index.rs +++ b/examples/core/basic/src/templates/index.rs @@ -28,7 +28,7 @@ pub fn head(cx: Scope, _props: IndexPageState) -> View { } } -#[perseus::autoserde(build_state)] +#[perseus::build_state] pub async fn get_build_state( _path: String, _locale: String, diff --git a/examples/core/freezing_and_thawing/src/global_state.rs b/examples/core/freezing_and_thawing/src/global_state.rs index 9c84a671ff..fbafee3e7d 100644 --- a/examples/core/freezing_and_thawing/src/global_state.rs +++ b/examples/core/freezing_and_thawing/src/global_state.rs @@ -9,7 +9,7 @@ pub struct AppState { pub test: String, } -#[perseus::autoserde(global_build_state)] +#[perseus::global_build_state] pub async fn get_build_state() -> RenderFnResult { Ok(AppState { test: "Hello World!".to_string(), diff --git a/examples/core/freezing_and_thawing/src/templates/index.rs b/examples/core/freezing_and_thawing/src/templates/index.rs index 612a66e65f..b45b35cba0 100644 --- a/examples/core/freezing_and_thawing/src/templates/index.rs +++ b/examples/core/freezing_and_thawing/src/templates/index.rs @@ -51,7 +51,7 @@ pub fn get_template() -> Template { .template(index_page) } -#[perseus::autoserde(build_state)] +#[perseus::build_state] pub async fn get_build_state( _path: String, _locale: String, diff --git a/examples/core/global_state/src/global_state.rs b/examples/core/global_state/src/global_state.rs index 9c84a671ff..fbafee3e7d 100644 --- a/examples/core/global_state/src/global_state.rs +++ b/examples/core/global_state/src/global_state.rs @@ -9,7 +9,7 @@ pub struct AppState { pub test: String, } -#[perseus::autoserde(global_build_state)] +#[perseus::global_build_state] pub async fn get_build_state() -> RenderFnResult { Ok(AppState { test: "Hello World!".to_string(), diff --git a/examples/core/i18n/src/templates/post.rs b/examples/core/i18n/src/templates/post.rs index 0bede897f2..1769489cf1 100644 --- a/examples/core/i18n/src/templates/post.rs +++ b/examples/core/i18n/src/templates/post.rs @@ -31,7 +31,7 @@ pub fn get_template() -> Template { .template(post_page) } -#[perseus::autoserde(build_state)] +#[perseus::build_state] pub async fn get_static_props( path: String, _locale: String, diff --git a/examples/core/idb_freezing/src/global_state.rs b/examples/core/idb_freezing/src/global_state.rs index 9c84a671ff..fbafee3e7d 100644 --- a/examples/core/idb_freezing/src/global_state.rs +++ b/examples/core/idb_freezing/src/global_state.rs @@ -9,7 +9,7 @@ pub struct AppState { pub test: String, } -#[perseus::autoserde(global_build_state)] +#[perseus::global_build_state] pub async fn get_build_state() -> RenderFnResult { Ok(AppState { test: "Hello World!".to_string(), diff --git a/examples/core/idb_freezing/src/templates/index.rs b/examples/core/idb_freezing/src/templates/index.rs index 31af6aba65..0d0bc49891 100644 --- a/examples/core/idb_freezing/src/templates/index.rs +++ b/examples/core/idb_freezing/src/templates/index.rs @@ -96,7 +96,7 @@ pub fn get_template() -> Template { .template(index_page) } -#[perseus::autoserde(build_state)] +#[perseus::build_state] pub async fn get_build_state( _path: String, _locale: String, diff --git a/examples/core/rx_state/src/templates/index.rs b/examples/core/rx_state/src/templates/index.rs index 201ec0bc24..26104d3a1c 100644 --- a/examples/core/rx_state/src/templates/index.rs +++ b/examples/core/rx_state/src/templates/index.rs @@ -31,7 +31,7 @@ pub fn get_template() -> Template { .build_state_fn(get_build_state) } -#[perseus::autoserde(build_state)] +#[perseus::build_state] pub async fn get_build_state( _path: String, _locale: String, diff --git a/examples/core/set_headers/src/templates/index.rs b/examples/core/set_headers/src/templates/index.rs index b435c90ceb..2a127a5827 100644 --- a/examples/core/set_headers/src/templates/index.rs +++ b/examples/core/set_headers/src/templates/index.rs @@ -31,7 +31,7 @@ pub fn get_template() -> Template { .set_headers_fn(set_headers) } -#[perseus::autoserde(build_state)] +#[perseus::build_state] pub async fn get_build_state(_path: String, _locale: String) -> RenderFnResultWithCause { Ok(PageState { greeting: "Hello World!".to_string(), @@ -40,7 +40,7 @@ pub async fn get_build_state(_path: String, _locale: String) -> RenderFnResultWi // For legacy reasons, this takes an `Option`, but, if you're generating state, it will always be here // In v0.4.0, this will be updated to take just your page's state (if it has any) -#[perseus::autoserde(set_headers)] +#[perseus::set_headers] pub fn set_headers(state: Option) -> HeaderMap { let mut map = HeaderMap::new(); map.insert( diff --git a/examples/core/state_generation/src/templates/amalgamation.rs b/examples/core/state_generation/src/templates/amalgamation.rs index 939394dedd..a92de09901 100644 --- a/examples/core/state_generation/src/templates/amalgamation.rs +++ b/examples/core/state_generation/src/templates/amalgamation.rs @@ -23,7 +23,7 @@ pub fn get_template() -> Template { .template(amalgamation_page) } -#[perseus::autoserde(amalgamate_states)] +#[perseus::amalgamate_states] pub fn amalgamate_states(states: States) -> RenderFnResultWithCause> { // We know they'll both be defined, and Perseus currently has to provide both as serialized strings let build_state = serde_json::from_str::(&states.build_state.unwrap())?; @@ -37,14 +37,14 @@ pub fn amalgamate_states(states: States) -> RenderFnResultWithCause RenderFnResultWithCause { Ok(PageState { message: "Hello from the build process!".to_string(), }) } -#[perseus::autoserde(request_state)] +#[perseus::request_state] pub async fn get_request_state( _path: String, _locale: String, diff --git a/examples/core/state_generation/src/templates/build_paths.rs b/examples/core/state_generation/src/templates/build_paths.rs index 137e1e4e92..5a959f7aa4 100644 --- a/examples/core/state_generation/src/templates/build_paths.rs +++ b/examples/core/state_generation/src/templates/build_paths.rs @@ -29,7 +29,7 @@ pub fn get_template() -> Template { } // We'll take in the path here, which will consist of the template name `build_paths` followed by the spcific path we're building for (as exported from `get_build_paths`) -#[perseus::autoserde(build_state)] +#[perseus::build_state] pub async fn get_build_state(path: String, _locale: String) -> RenderFnResultWithCause { let title = path.clone(); let content = format!( @@ -45,6 +45,7 @@ pub async fn get_build_state(path: String, _locale: String) -> RenderFnResultWit // Note that everything you export from here will be prefixed with `/` when it becomes a URL in your app // // Note also that there's almost no point in using build paths without build state, as every page would come out exactly the same (unless you differentiated them on the client...) +#[perseus::build_paths] pub async fn get_build_paths() -> RenderFnResult> { Ok(vec![ "".to_string(), diff --git a/examples/core/state_generation/src/templates/build_state.rs b/examples/core/state_generation/src/templates/build_state.rs index b8a6171f63..ae6b70ee68 100644 --- a/examples/core/state_generation/src/templates/build_state.rs +++ b/examples/core/state_generation/src/templates/build_state.rs @@ -21,7 +21,7 @@ pub fn get_template() -> Template { // We're told the path we're generating for (useless unless we're using build paths as well) and the locale (which will be `xx-XX` if we're not using i18n) // Note that this function is asynchronous, so we can do work like fetching from a server or the like here (see the `demo/fetching` example) -#[perseus::autoserde(build_state)] +#[perseus::build_state] pub async fn get_build_state(_path: String, _locale: String) -> RenderFnResultWithCause { Ok(PageState { greeting: "Hello World!".to_string(), diff --git a/examples/core/state_generation/src/templates/incremental_generation.rs b/examples/core/state_generation/src/templates/incremental_generation.rs index 03510d222c..7179732ced 100644 --- a/examples/core/state_generation/src/templates/incremental_generation.rs +++ b/examples/core/state_generation/src/templates/incremental_generation.rs @@ -34,7 +34,7 @@ pub fn get_template() -> Template { } // We'll take in the path here, which will consist of the template name `incremental_generation` followed by the spcific path we're building for (as exported from `get_build_paths`) -#[perseus::autoserde(build_state)] +#[perseus::build_state] pub async fn get_build_state(path: String, _locale: String) -> RenderFnResultWithCause { // This path is illegal, and can't be rendered // Because we're using incremental generation, we could gte literally anything as the `path` @@ -57,6 +57,7 @@ pub async fn get_build_state(path: String, _locale: String) -> RenderFnResultWit // Note that everything you export from here will be prefixed with `/` when it becomes a URL in your app // // Note also that there's almost no point in using build paths without build state, as every page would come out exactly the same (unless you differentiated them on the client...) +#[perseus::build_paths] pub async fn get_build_paths() -> RenderFnResult> { Ok(vec!["test".to_string(), "blah/test/blah".to_string()]) } diff --git a/examples/core/state_generation/src/templates/request_state.rs b/examples/core/state_generation/src/templates/request_state.rs index 3a5301bc8d..491fa5bdd3 100644 --- a/examples/core/state_generation/src/templates/request_state.rs +++ b/examples/core/state_generation/src/templates/request_state.rs @@ -23,7 +23,7 @@ pub fn get_template() -> Template { .template(request_state_page) } -#[perseus::autoserde(request_state)] +#[perseus::request_state] pub async fn get_request_state( _path: String, _locale: String, diff --git a/examples/core/state_generation/src/templates/revalidation.rs b/examples/core/state_generation/src/templates/revalidation.rs index c6dc42c67c..3fd550819e 100644 --- a/examples/core/state_generation/src/templates/revalidation.rs +++ b/examples/core/state_generation/src/templates/revalidation.rs @@ -27,7 +27,7 @@ pub fn get_template() -> Template { } // This will get the system time when the app was built -#[perseus::autoserde(build_state)] +#[perseus::build_state] pub async fn get_build_state(_path: String, _locale: String) -> RenderFnResultWithCause { Ok(PageState { time: format!("{:?}", std::time::SystemTime::now()), diff --git a/examples/core/state_generation/src/templates/revalidation_and_incremental_generation.rs b/examples/core/state_generation/src/templates/revalidation_and_incremental_generation.rs index 3345a241b6..f6c16a1f6c 100644 --- a/examples/core/state_generation/src/templates/revalidation_and_incremental_generation.rs +++ b/examples/core/state_generation/src/templates/revalidation_and_incremental_generation.rs @@ -35,13 +35,14 @@ pub fn get_template() -> Template { } // This will get the system time when the app was built -#[perseus::autoserde(build_state)] +#[perseus::build_state] pub async fn get_build_state(_path: String, _locale: String) -> RenderFnResultWithCause { Ok(PageState { time: format!("{:?}", std::time::SystemTime::now()), }) } +#[perseus::build_paths] pub async fn get_build_paths() -> RenderFnResult> { Ok(vec!["test".to_string(), "blah/test/blah".to_string()]) } diff --git a/examples/core/unreactive/src/templates/index.rs b/examples/core/unreactive/src/templates/index.rs index a2516d1de9..f129567d95 100644 --- a/examples/core/unreactive/src/templates/index.rs +++ b/examples/core/unreactive/src/templates/index.rs @@ -34,7 +34,7 @@ pub fn head(cx: Scope, _props: IndexPageState) -> View { } } -#[perseus::autoserde(build_state)] +#[perseus::build_state] pub async fn get_build_state( _path: String, _locale: String, diff --git a/examples/demos/auth/src/global_state.rs b/examples/demos/auth/src/global_state.rs index 441a108e96..d5bcf92a3d 100644 --- a/examples/demos/auth/src/global_state.rs +++ b/examples/demos/auth/src/global_state.rs @@ -5,7 +5,7 @@ pub fn get_global_state_creator() -> GlobalStateCreator { GlobalStateCreator::new().build_state_fn(get_build_state) } -#[perseus::autoserde(global_build_state)] +#[perseus::global_build_state] pub async fn get_build_state() -> RenderFnResult { Ok(AppState { // We explicitly tell the first page that no login state has been checked yet diff --git a/examples/demos/fetching/src/templates/index.rs b/examples/demos/fetching/src/templates/index.rs index 05a200d03c..c0da60d6f2 100644 --- a/examples/demos/fetching/src/templates/index.rs +++ b/examples/demos/fetching/src/templates/index.rs @@ -58,7 +58,7 @@ pub fn get_template() -> Template { .template(index_page) } -#[perseus::autoserde(build_state)] +#[perseus::build_state] pub async fn get_build_state( _path: String, _locale: String, diff --git a/packages/perseus-macro/src/lib.rs b/packages/perseus-macro/src/lib.rs index 1b8df56c02..14349f3892 100644 --- a/packages/perseus-macro/src/lib.rs +++ b/packages/perseus-macro/src/lib.rs @@ -11,10 +11,10 @@ This is the API documentation for the `perseus-macro` package, which manages Per documentation, and this should mostly be used as a secondary reference source. You can also find full usage examples [here](https://github.com/arctic-hen7/perseus/tree/main/examples). */ -mod autoserde; mod entrypoint; mod head; mod rx_state; +mod state_fns; mod template; mod template_rx; mod test; @@ -22,30 +22,61 @@ mod test; use darling::FromMeta; use proc_macro::TokenStream; use quote::quote; +use state_fns::StateFnType; use syn::{ItemStruct, Path}; -/// Automatically serializes/deserializes properties for a template. Perseus handles your templates' properties as `String`s under the -/// hood for both simplicity and to avoid bundle size increases from excessive monomorphization. This macro aims to prevent the need for -/// manually serializing and deserializing everything! This takes the type of function that it's working on, which must be one of the -/// following: -/// -/// - `build_state` (serializes return type) -/// - `request_state` (serializes return type) -/// - `set_headers` (deserializes parameter) -/// - `amalgamate_states` (serializes return type, you'll still need to deserializes from `States` manually) +/// Annotates functions used for generating state at build time to support automatic serialization/deserialization of app state and +/// client/server division. This supersedes the old `autoserde` macro for build state functions. #[proc_macro_attribute] -pub fn autoserde(args: TokenStream, input: TokenStream) -> TokenStream { - let parsed = syn::parse_macro_input!(input as autoserde::AutoserdeFn); - let attr_args = syn::parse_macro_input!(args as syn::AttributeArgs); - // Parse macro arguments with `darling` - let args = match autoserde::AutoserdeArgs::from_list(&attr_args) { - Ok(v) => v, - Err(e) => { - return TokenStream::from(e.write_errors()); - } - }; +pub fn build_state(_args: TokenStream, input: TokenStream) -> TokenStream { + let parsed = syn::parse_macro_input!(input as state_fns::StateFn); + + state_fns::state_fn_impl(parsed, StateFnType::BuildState).into() +} + +/// Annotates functions used for generating paths at build time to support automatic serialization/deserialization of app state and +/// client/server division. This supersedes the old `autoserde` macro for build paths functions. +#[proc_macro_attribute] +pub fn build_paths(_args: TokenStream, input: TokenStream) -> TokenStream { + let parsed = syn::parse_macro_input!(input as state_fns::StateFn); + + state_fns::state_fn_impl(parsed, StateFnType::BuildPaths).into() +} + +/// Annotates functions used for generating global state at build time to support automatic serialization/deserialization of app state and +/// client/server division. This supersedes the old `autoserde` macro for global build state functions. +#[proc_macro_attribute] +pub fn global_build_state(_args: TokenStream, input: TokenStream) -> TokenStream { + let parsed = syn::parse_macro_input!(input as state_fns::StateFn); + + state_fns::state_fn_impl(parsed, StateFnType::GlobalBuildState).into() +} + +/// Annotates functions used for generating state at request time to support automatic serialization/deserialization of app state and +/// client/server division. This supersedes the old `autoserde` macro for request state functions. +#[proc_macro_attribute] +pub fn request_state(_args: TokenStream, input: TokenStream) -> TokenStream { + let parsed = syn::parse_macro_input!(input as state_fns::StateFn); + + state_fns::state_fn_impl(parsed, StateFnType::RequestState).into() +} + +/// Annotates functions used for generating state at build time to support automatic serialization/deserialization of app state and +/// client/server division. This supersedes the old `autoserde` macro for build state functions. +#[proc_macro_attribute] +pub fn set_headers(_args: TokenStream, input: TokenStream) -> TokenStream { + let parsed = syn::parse_macro_input!(input as state_fns::StateFn); + + state_fns::state_fn_impl(parsed, StateFnType::SetHeaders).into() +} + +/// Annotates functions used for amlagamating build-time and request-time states to support automatic serialization/deserialization of app state and +/// client/server division. This supersedes the old `autoserde` macro for state amalgamation functions. +#[proc_macro_attribute] +pub fn amalgamate_states(_args: TokenStream, input: TokenStream) -> TokenStream { + let parsed = syn::parse_macro_input!(input as state_fns::StateFn); - autoserde::autoserde_impl(parsed, args).into() + state_fns::state_fn_impl(parsed, StateFnType::AmalgamateStates).into() } /// Labels a Sycamore component as a Perseus template, turning it into something that can be easily inserted into the `.template()` diff --git a/packages/perseus-macro/src/autoserde.rs b/packages/perseus-macro/src/state_fns.rs similarity index 84% rename from packages/perseus-macro/src/autoserde.rs rename to packages/perseus-macro/src/state_fns.rs index 213a057810..07e439b3a8 100644 --- a/packages/perseus-macro/src/autoserde.rs +++ b/packages/perseus-macro/src/state_fns.rs @@ -1,4 +1,5 @@ -use darling::FromMeta; +// This file contains all the macros that supersede `autoserde` + use proc_macro2::TokenStream; use quote::quote; use syn::parse::{Parse, ParseStream}; @@ -7,24 +8,8 @@ use syn::{ Result, ReturnType, Type, Visibility, }; -/// The arguments that the `autoserde` annotation takes. -// TODO prevent the user from providing more than one of these -#[derive(Debug, FromMeta, PartialEq, Eq)] -pub struct AutoserdeArgs { - #[darling(default)] - build_state: bool, - #[darling(default)] - request_state: bool, - #[darling(default)] - set_headers: bool, - #[darling(default)] - amalgamate_states: bool, - #[darling(default)] - global_build_state: bool, -} - /// A function that can be wrapped in the Perseus test sub-harness. -pub struct AutoserdeFn { +pub struct StateFn { /// The body of the function. pub block: Box, /// The arguments that the function takes. We don't need to modify these because we wrap them with a functin that does serializing/ @@ -41,7 +26,7 @@ pub struct AutoserdeFn { /// Any generics the function takes (shouldn't be any, but it's possible). pub generics: Generics, } -impl Parse for AutoserdeFn { +impl Parse for StateFn { fn parse(input: ParseStream) -> Result { let parsed: Item = input.parse()?; @@ -97,8 +82,19 @@ impl Parse for AutoserdeFn { } } -pub fn autoserde_impl(input: AutoserdeFn, fn_type: AutoserdeArgs) -> TokenStream { - let AutoserdeFn { +/// The different types of state functions. +pub enum StateFnType { + BuildState, + BuildPaths, + RequestState, + SetHeaders, + AmalgamateStates, + GlobalBuildState, +} + +// We just use a single implementation function for ease, but there's a separate macro for each type of state function +pub fn state_fn_impl(input: StateFn, fn_type: StateFnType) -> TokenStream { + let StateFn { block, args, generics, @@ -108,9 +104,8 @@ pub fn autoserde_impl(input: AutoserdeFn, fn_type: AutoserdeArgs) -> TokenStream return_type, } = input; - if fn_type.build_state { - // This will always be asynchronous - quote! { + match fn_type { + StateFnType::BuildState => quote! { // We create a normal version of the function and one to appease the handlers in Wasm (which expect functions that take no arguments, etc.) #[cfg(target_arch = "wasm32")] #vis fn #name() {} @@ -129,10 +124,20 @@ pub fn autoserde_impl(input: AutoserdeFn, fn_type: AutoserdeArgs) -> TokenStream let build_state_with_str = build_state.map(|val| ::serde_json::to_string(&val).unwrap()); build_state_with_str } - } - } else if fn_type.request_state { - // This will always be asynchronous - quote! { + }, + // This one only exists to appease the server-side/client-side division + StateFnType::BuildPaths => quote! { + // We create a normal version of the function and one to appease the handlers in Wasm (which expect functions that take no arguments, etc.) + #[cfg(target_arch = "wasm32")] + #vis fn #name() {} + // This normal version is identical to the user's (we know it won't have any arguments, and we know its return type) + // We use the user's return type to prevent unused imports warnings in their code + #[cfg(not(target_arch = "wasm32"))] + #vis async fn #name() -> #return_type { + #block + } + }, + StateFnType::RequestState => quote! { // We create a normal version of the function and one to appease the handlers in Wasm (which expect functions that take no arguments, etc.) #[cfg(target_arch = "wasm32")] #vis fn #name() {} @@ -151,10 +156,9 @@ pub fn autoserde_impl(input: AutoserdeFn, fn_type: AutoserdeArgs) -> TokenStream let req_state_with_str = req_state.map(|val| ::serde_json::to_string(&val).unwrap()); req_state_with_str } - } - } else if fn_type.set_headers { - // This will always be synchronous - quote! { + }, + // Always synchronous + StateFnType::SetHeaders => quote! { // We create a normal version of the function and one to appease the handlers in Wasm (which expect functions that take no arguments, etc.) #[cfg(target_arch = "wasm32")] #vis fn #name() {} @@ -170,10 +174,9 @@ pub fn autoserde_impl(input: AutoserdeFn, fn_type: AutoserdeArgs) -> TokenStream let props_de = props.map(|val| ::serde_json::from_str(&val).unwrap()); #name(props_de) } - } - } else if fn_type.amalgamate_states { - // This will always be synchronous - quote! { + }, + // Always synchronous + StateFnType::AmalgamateStates => quote! { // We create a normal version of the function and one to appease the handlers in Wasm (which expect functions that take no arguments, etc.) #[cfg(target_arch = "wasm32")] #vis fn #name() {} @@ -192,9 +195,8 @@ pub fn autoserde_impl(input: AutoserdeFn, fn_type: AutoserdeArgs) -> TokenStream let amalgamated_state_with_str = amalgamated_state.map(|val| val.map(|val| ::serde_json::to_string(&val).unwrap())); amalgamated_state_with_str } - } - } else if fn_type.global_build_state { - quote! { + }, + StateFnType::GlobalBuildState => quote! { // We create a normal version of the function and one to appease the handlers in Wasm (which expect functions that take no arguments, etc.) #[cfg(target_arch = "wasm32")] #vis fn #name() {} @@ -213,10 +215,6 @@ pub fn autoserde_impl(input: AutoserdeFn, fn_type: AutoserdeArgs) -> TokenStream let build_state_with_str = build_state.map(|val| ::serde_json::to_string(&val).unwrap()); build_state_with_str } - } - } else { - quote! { - compile_error!("function type not supported, must be one of: `build_state`, `request_state`, `set_headers`, `amalgamate_states`, or `global_build_state`") - } + }, } } diff --git a/packages/perseus/src/lib.rs b/packages/perseus/src/lib.rs index e2e88173a4..e284fb06a0 100644 --- a/packages/perseus/src/lib.rs +++ b/packages/perseus/src/lib.rs @@ -70,8 +70,9 @@ pub type Request = HttpRequest<()>; pub use client::{run_client, ClientReturn}; #[cfg(feature = "macros")] pub use perseus_macro::{ - autoserde, browser, browser_main, engine, engine_main, head, main, main_export, make_rx, - template, template_rx, test, + amalgamate_states, browser, browser_main, build_paths, build_state, engine, engine_main, + global_build_state, head, main, main_export, make_rx, request_state, set_headers, template, + template_rx, test, }; pub use sycamore::prelude::{DomNode, Html, HydrateNode, SsrNode}; pub use sycamore_router::{navigate, navigate_replace, Route}; // TODO Should we be exporting `Route` anymore? From a6869d9a6e4f81064f6ca4bc5e9bddc0a95ceddc Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Sun, 19 Jun 2022 10:47:31 +1000 Subject: [PATCH 26/37] test: fixed testing script --- scripts/test.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/scripts/test.rs b/scripts/test.rs index 959a432941..37bba720dc 100644 --- a/scripts/test.rs +++ b/scripts/test.rs @@ -30,14 +30,14 @@ fn real_main() -> i32 { let shell_param = "-command"; let exec_name = { - // This is intended to be run from the root of the project + // This is intended to be run from the root of the project (which this script always will be because of Bonnie's requirements) let output = Command::new(shell_exec) .args([shell_param, &format!( - "bonnie dev example {category} {example} test --no-run --integration {integration}", + "bonnie dev example {category} {example} test --no-run", category=&category, example=&example, - integration=&integration )]) + .env("EXAMPLE_INTEGRATION", &integration) .output() .expect("couldn't build tests (command execution failed)"); let exit_code = match output.status.code() { @@ -57,11 +57,13 @@ fn real_main() -> i32 { }; // Run the server from that executable in the background - // This has to be run from the correct execution context (inside the `.perseus/server/` directory for teh target example) + // This has to be run from the correct execution context (inside the root of the target example) let mut server = Command::new(exec_name) - .current_dir(&format!("examples/{}/{}/.perseus/server", &category, &example)) + .current_dir(&format!("examples/{}/{}", &category, &example)) // Tell the server we're in testing mode .env("PERSEUS_TESTING", "true") + // We're in dev mode, so we have to tell the binary what to do + .env("PERSEUS_ENGINE_OPERATION", "serve") .spawn() .expect("couldn't start test server (command execution failed)"); @@ -69,8 +71,8 @@ fn real_main() -> i32 { let exit_code = { let output = Command::new(shell_exec) .current_dir(&format!("examples/{}/{}", &category, &example)) - .args([shell_param, "cargo test -- --test-threads 1"]) // TODO Confirm that this syntax works on Windows + .args([shell_param, &format!("cargo test --features 'perseus-integration/{}' -- --test-threads 1", &integration)]) .envs(if is_headless { vec![("PERSEUS_RUN_WASM_TESTS", "true"), ("PERSEUS_RUN_WASM_TESTS_HEADLESS", "true")] } else { From 34132a28dbf4684d905ae599fc81928d8bba4b7b Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Sun, 19 Jun 2022 10:51:29 +1000 Subject: [PATCH 27/37] chore: updated `bonnie.toml` for new layout --- bonnie.toml | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/bonnie.toml b/bonnie.toml index 85cd7fe219..32447cb70e 100644 --- a/bonnie.toml +++ b/bonnie.toml @@ -3,17 +3,11 @@ env_files = [ ".env" ] [scripts] setup.cmd.generic = [ - "mkdir -p examples/core/basic/.perseus/dist", - "mkdir -p examples/core/basic/.perseus/dist/static", - "mkdir -p examples/core/basic/.perseus/dist/exported", "cargo build", "npm i --prefix ./website", "echo \"\n\nThe Perseus repository is ready for local development! Type 'bonnie help' to see the available commands you can run here. Also, please ensure that you have 'npx' available and that you've installed 'tailwindcss', `concurrently`, `serve` and 'browser-sync' ('npm i -g tailwindcss concurrently serve browser-sync') if you'll be working with the website or running `bonnie dev export-serve ...`.\"" ] setup.cmd.targets.windows = [ - "New-Item -Force -ItemType directory -Path examples\\core\\basic\\.perseus\\dist", - "New-Item -Force -ItemType directory -Path examples\\core\\basic\\.perseus\\dist\\static", - "New-Item -Force -ItemType directory -Path examples\\core\\basic\\.perseus\\dist\\exported", "cargo build", "npm i --prefix ./website", "Write-Host \"\n\nThe Perseus repository is ready for local development! Type 'bonnie help' to see the available commands you can run here. Also, please ensure that you have 'npx' available and that you've installed 'tailwindcss', `concurrently`, `serve` and 'browser-sync' ('npm i -g tailwindcss concurrently serve browser-sync') if you'll be working with the website or running `bonnie dev export-serve ...`.\"" @@ -153,16 +147,6 @@ check.cmd = [ ] check.desc = "checks code for formatting errors and the like" -ci-prep.cmd.generic = [ - "mkdir -p examples/core/basic/.perseus/dist", - "mkdir -p examples/core/basic/.perseus/dist/static", -] -ci-prep.cmd.targets.windows = [ - "New-Item -Force -ItemType directory -Path examples\\core\\basic\\.perseus\\dist", - "New-Item -Force -ItemType directory -Path examples\\core\\basic\\.perseus\\static", -] -ci-prep.desc = "creates empty directories to preserve the file structure that testing expects" - test.cmd = [ "cargo test", # This will ignore Wasm tests # Run tests for each example @@ -210,7 +194,7 @@ release.desc = "creates a new project release and pushes it to github (cargo ver # --- COMMANDS FOLLOWING THIS POINT ARE LINUX-ONLY --- -replace-versions.cmd = "find . \\( \\( -name \"*Cargo.toml\" -or -name \"*Cargo.toml.example\" -or \\( -name \"*.md\" -not -name \"*.proj.md\" \\) \\) -not -name \"CHANGELOG.md\" -not -path \"./target/*\" -not -path \"./website/*\" -not -path \"*.perseus*\" -or \\( -name \"*Cargo.toml\" -path \"./examples/core/basic/.perseus/*\" -not -path \"./examples/core/basic/.perseus/dist/*\" \\) \\) -exec sed -i -e 's/%old_version/%new_version/g' {} \\;" +replace-versions.cmd = "find . \\( \\( -name \"*Cargo.toml\" -or -name \"*Cargo.toml.example\" -or \\( -name \"*.md\" -not -name \"*.proj.md\" \\) \\) -not -name \"CHANGELOG.md\" -not -path \"./target/*\" -not -path \"./website/*\" \\) -exec sed -i -e 's/%old_version/%new_version/g' {} \\;" replace-versions.args = [ "old_version", "new_version" ] replace-versions.desc = "replaces an old version number with a new one in all relevant files (Linux only)" @@ -223,7 +207,7 @@ publish.cmd = [ "cd ../perseus", "cargo publish %%", "cd ../perseus-cli", - "cargo publish --allow-dirty %%", # Without this flag, `.perseus` will be a problem because it's not in Git + "cargo publish %%", # We delay this so that `crates.io` can have time to host the core "cd ../perseus-actix-web", "cargo publish %%", From 7dfae232a0565312fbcd788c4c1205d037d84c3d Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Sun, 19 Jun 2022 10:54:45 +1000 Subject: [PATCH 28/37] ci: removed `ci-prep` calls and added `wasm32-unknown-unknown` target for `check` op --- .github/workflows/cd.yml | 2 -- .github/workflows/ci.yml | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 08d091ddc0..84bbb1a3ac 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -24,7 +24,6 @@ jobs: steps: - uses: actions/checkout@v2 - run: cargo install bonnie - - run: bonnie ci-prep - name: Build run: cargo build --release working-directory: packages/perseus-cli @@ -45,7 +44,6 @@ jobs: - name: Install musl toolchain run: rustup target add x86_64-unknown-linux-musl - run: cargo install bonnie - - run: bonnie ci-prep - name: Build run: cargo build --release --target x86_64-unknown-linux-musl working-directory: packages/perseus-cli diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a38ba66ddd..dd5d68c9bd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/checkout@v2 - run: cargo install bonnie - - run: bonnie ci-prep + - run: rustup target add wasm32-unknown-unknown - name: Run checks run: bonnie check test: @@ -64,6 +64,5 @@ jobs: - run: sudo apt install firefox firefox-geckodriver - name: Run Firefox WebDriver run: geckodriver & - - run: bonnie ci-prep - name: Run E2E tests for example ${{ matrix.name }} in category ${{ matrix.type }} run: bonnie test example-all-integrations ${{ matrix.type }} ${{ matrix.name }} --headless From 5455b3c619200a7a377a96e3292b72cb0294862d Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Sun, 19 Jun 2022 15:19:58 +1000 Subject: [PATCH 29/37] ci: fixed remnant `ci-prep` invocation --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dd5d68c9bd..1777c63d89 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,6 @@ jobs: steps: - uses: actions/checkout@v2 - run: cargo install bonnie - - run: bonnie ci-prep - name: Run traditional tests run: cargo test --all # We now have a separate job for each example's E2E testing because they all take a while, we may as well run them in parallel From b1bc6ee3cec5c2f7ff3b57dd16bbabc5b606859e Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Sun, 19 Jun 2022 15:20:08 +1000 Subject: [PATCH 30/37] fix: fixed global build state server/client division --- packages/perseus/src/state/global_state.rs | 23 +++++++++++----------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/packages/perseus/src/state/global_state.rs b/packages/perseus/src/state/global_state.rs index 3b809b55e2..6c1ec9cb5f 100644 --- a/packages/perseus/src/state/global_state.rs +++ b/packages/perseus/src/state/global_state.rs @@ -19,16 +19,12 @@ pub type GlobalStateCreatorFn = Rc; #[derive(Default, Clone)] pub struct GlobalStateCreator { /// The function that creates state at build-time. This is roughly equivalent to the *build state* strategy for templates. + #[cfg(not(target_arch = "wasm32"))] build: Option, } impl std::fmt::Debug for GlobalStateCreator { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("GlobalStateCreator") - .field( - "build", - &self.build.as_ref().map(|_| "GlobalStateCreatorFn"), - ) - .finish() + f.debug_struct("GlobalStateCreator").finish() } } impl GlobalStateCreator { @@ -37,19 +33,22 @@ impl GlobalStateCreator { Self::default() } /// Adds a function to generate global state at build-time. - #[allow(unused_mut)] - #[allow(unused_variables)] + #[cfg(not(target_arch = "wasm32"))] pub fn build_state_fn( mut self, val: impl GlobalStateCreatorFnType + Send + Sync + 'static, ) -> Self { - #[cfg(feature = "server-side")] - { - self.build = Some(Rc::new(val)); - } + self.build = Some(Rc::new(val)); self } + /// Adds a function to generate global state at build-time. + #[cfg(target_arch = "wasm32")] + pub fn build_state_fn(self, _val: impl Fn() + 'static) -> Self { + self + } + /// Gets the global state at build-time. If no function was registered to this, we'll return `None`. + #[cfg(not(target_arch = "wasm32"))] pub async fn get_build_state(&self) -> Result, GlobalStateError> { if let Some(get_server_state) = &self.build { let res = get_server_state.call().await; From b4921af83dd8c3cc198ba367f3af78992eef46d7 Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Sun, 19 Jun 2022 15:37:42 +1000 Subject: [PATCH 31/37] fix: added missing build paths annotation --- examples/core/i18n/src/templates/post.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/core/i18n/src/templates/post.rs b/examples/core/i18n/src/templates/post.rs index 1769489cf1..df1f4e8ac7 100644 --- a/examples/core/i18n/src/templates/post.rs +++ b/examples/core/i18n/src/templates/post.rs @@ -49,6 +49,7 @@ pub async fn get_static_props( }) // This `?` declares the default, that the server is the cause of the error } +#[perseus::build_paths] pub async fn get_static_paths() -> RenderFnResult> { Ok(vec![ "".to_string(), From d8da1cb0b9a2007270d34b989cfcfb00eacc9f52 Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Sun, 19 Jun 2022 15:59:54 +1000 Subject: [PATCH 32/37] test: removed unnecessary test from plugins example This made it uncompilable because of the silly way I've set that up. I'll fix that as I rewrite the docs for v0.4.0. --- examples/core/plugins/src/plugin.rs | 6 +----- examples/core/plugins/tests/main.rs | 2 -- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/examples/core/plugins/src/plugin.rs b/examples/core/plugins/src/plugin.rs index 0c09dca076..31279dfacc 100644 --- a/examples/core/plugins/src/plugin.rs +++ b/examples/core/plugins/src/plugin.rs @@ -26,11 +26,7 @@ pub fn get_test_plugin() -> Plugin { // Note that this doesn't work with hydration, but a full template does (there's some difference there that causes a hydration ID overlap for some reason) vec![Template::new("about") .template(move |cx, _| sycamore::view! { cx, p { (about_page_greeting) } }) - .head(|cx, _| { - sycamore::view! { cx, - title { "About Page (Plugin Modified) | Perseus Example – Plugins" } - } - })] + ] } else { unreachable!() } diff --git a/examples/core/plugins/tests/main.rs b/examples/core/plugins/tests/main.rs index e2d03f3357..6ce8d02f3a 100644 --- a/examples/core/plugins/tests/main.rs +++ b/examples/core/plugins/tests/main.rs @@ -27,8 +27,6 @@ async fn main(c: &mut Client) -> Result<(), fantoccini::error::CmdError> { // Make sure the hardcoded text there exists let text = c.find(Locator::Css("p")).await?.text().await?; assert_eq!(text, "Hey from a plugin!"); - let title = c.find(Locator::Css("title")).await?.html(false).await?; - assert!(title.contains("About Page (Plugin Modified)")); // Make sure we get initial state if we refresh c.refresh().await?; wait_for_checkpoint!("initial_state_present", 0, c); From 78d142eb759cb3782c12a830a0aef67d3aa6c572 Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Sun, 19 Jun 2022 16:01:01 +1000 Subject: [PATCH 33/37] chore: updated `bn test` command with all core examples --- bonnie.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bonnie.toml b/bonnie.toml index 32447cb70e..5b9de39fc1 100644 --- a/bonnie.toml +++ b/bonnie.toml @@ -159,7 +159,10 @@ test.cmd = [ "bonnie test example-all-integrations core global_state --headless", "bonnie test example-all-integrations core idb_freezing --headless", "bonnie test example-all-integrations core router_state --headless", - "bonnie test example-all-integrations core rx_state --headless" + "bonnie test example-all-integrations core rx_state --headless", + "bonnie test example-all-integrations core index_view --headless", + "bonnie test example-all-integrations core set_headers --headless", + "bonnie test example-all-integrations core static_content --headless" ] test.desc = "runs all tests headlessly (assumes geckodriver running in background)" test.subcommands.core.cmd = "cargo test" From 4c0d42316dc6b1d0d3f2252786bb2a70ff5b1971 Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Sun, 19 Jun 2022 17:20:08 +1000 Subject: [PATCH 34/37] feat: added `should_revalidate` macro --- examples/core/plugins/src/plugin.rs | 6 +++--- .../state_generation/src/templates/revalidation.rs | 1 + .../revalidation_and_incremental_generation.rs | 10 +++++++++- packages/perseus-macro/src/lib.rs | 11 ++++++++++- packages/perseus-macro/src/state_fns.rs | 13 +++++++++++++ packages/perseus/src/lib.rs | 4 ++-- 6 files changed, 38 insertions(+), 7 deletions(-) diff --git a/examples/core/plugins/src/plugin.rs b/examples/core/plugins/src/plugin.rs index 31279dfacc..8483148d35 100644 --- a/examples/core/plugins/src/plugin.rs +++ b/examples/core/plugins/src/plugin.rs @@ -24,9 +24,9 @@ pub fn get_test_plugin() -> Plugin { if let Some(plugin_data) = plugin_data.downcast_ref::() { let about_page_greeting = plugin_data.about_page_greeting.to_string(); // Note that this doesn't work with hydration, but a full template does (there's some difference there that causes a hydration ID overlap for some reason) - vec![Template::new("about") - .template(move |cx, _| sycamore::view! { cx, p { (about_page_greeting) } }) - ] + vec![Template::new("about").template( + move |cx, _| sycamore::view! { cx, p { (about_page_greeting) } }, + )] } else { unreachable!() } diff --git a/examples/core/state_generation/src/templates/revalidation.rs b/examples/core/state_generation/src/templates/revalidation.rs index 3fd550819e..a2a6042624 100644 --- a/examples/core/state_generation/src/templates/revalidation.rs +++ b/examples/core/state_generation/src/templates/revalidation.rs @@ -36,6 +36,7 @@ pub async fn get_build_state(_path: String, _locale: String) -> RenderFnResultWi // This will run every time `.revalidate_after()` permits the page to be revalidated // This acts as a secondary check, and can perform arbitrary logic to check if we should actually revalidate a page +#[perseus::should_revalidate] pub async fn should_revalidate() -> RenderFnResultWithCause { // For simplicity's sake, this will always say we should revalidate, but you could amke this check any condition Ok(true) diff --git a/examples/core/state_generation/src/templates/revalidation_and_incremental_generation.rs b/examples/core/state_generation/src/templates/revalidation_and_incremental_generation.rs index f6c16a1f6c..b8223cd9e8 100644 --- a/examples/core/state_generation/src/templates/revalidation_and_incremental_generation.rs +++ b/examples/core/state_generation/src/templates/revalidation_and_incremental_generation.rs @@ -28,7 +28,7 @@ pub fn get_template() -> Template { // load this page. For that reason, this should NOT do long-running work, as requests will be delayed. If both this // and `revaldiate_after()` are provided, this logic will only run when `revalidate_after()` tells Perseus // that it should revalidate. - .should_revalidate_fn(|| async { Ok(true) }) + .should_revalidate_fn(should_revalidate) .build_state_fn(get_build_state) .build_paths_fn(get_build_paths) .incremental_generation() @@ -46,3 +46,11 @@ pub async fn get_build_state(_path: String, _locale: String) -> RenderFnResultWi pub async fn get_build_paths() -> RenderFnResult> { Ok(vec!["test".to_string(), "blah/test/blah".to_string()]) } + +// This will run every time `.revalidate_after()` permits the page to be revalidated +// This acts as a secondary check, and can perform arbitrary logic to check if we should actually revalidate a page +#[perseus::should_revalidate] +pub async fn should_revalidate() -> RenderFnResultWithCause { + // For simplicity's sake, this will always say we should revalidate, but you could amke this check any condition + Ok(true) +} diff --git a/packages/perseus-macro/src/lib.rs b/packages/perseus-macro/src/lib.rs index 14349f3892..10d087a933 100644 --- a/packages/perseus-macro/src/lib.rs +++ b/packages/perseus-macro/src/lib.rs @@ -70,7 +70,7 @@ pub fn set_headers(_args: TokenStream, input: TokenStream) -> TokenStream { state_fns::state_fn_impl(parsed, StateFnType::SetHeaders).into() } -/// Annotates functions used for amlagamating build-time and request-time states to support automatic serialization/deserialization of app state and +/// Annotates functions used for amalgamating build-time and request-time states to support automatic serialization/deserialization of app state and /// client/server division. This supersedes the old `autoserde` macro for state amalgamation functions. #[proc_macro_attribute] pub fn amalgamate_states(_args: TokenStream, input: TokenStream) -> TokenStream { @@ -79,6 +79,15 @@ pub fn amalgamate_states(_args: TokenStream, input: TokenStream) -> TokenStream state_fns::state_fn_impl(parsed, StateFnType::AmalgamateStates).into() } +/// Annotates functions used for checking if a template should revalidate and request-time states to support automatic serialization/deserialization +/// of app state and client/server division. This supersedes the old `autoserde` macro for revalidation determination functions. +#[proc_macro_attribute] +pub fn should_revalidate(_args: TokenStream, input: TokenStream) -> TokenStream { + let parsed = syn::parse_macro_input!(input as state_fns::StateFn); + + state_fns::state_fn_impl(parsed, StateFnType::ShouldRevalidate).into() +} + /// Labels a Sycamore component as a Perseus template, turning it into something that can be easily inserted into the `.template()` /// function, avoiding the need for you to manually serialize/deserialize things. This should be provided the name of the Sycamore component (same as given /// to Sycamore's `#[component()]`, but without the ``). diff --git a/packages/perseus-macro/src/state_fns.rs b/packages/perseus-macro/src/state_fns.rs index 07e439b3a8..4734bfbf9f 100644 --- a/packages/perseus-macro/src/state_fns.rs +++ b/packages/perseus-macro/src/state_fns.rs @@ -90,6 +90,7 @@ pub enum StateFnType { SetHeaders, AmalgamateStates, GlobalBuildState, + ShouldRevalidate, } // We just use a single implementation function for ease, but there's a separate macro for each type of state function @@ -216,5 +217,17 @@ pub fn state_fn_impl(input: StateFn, fn_type: StateFnType) -> TokenStream { build_state_with_str } }, + // This one only exists to appease the server-side/client-side division + StateFnType::ShouldRevalidate => quote! { + // We create a normal version of the function and one to appease the handlers in Wasm (which expect functions that take no arguments, etc.) + #[cfg(target_arch = "wasm32")] + #vis fn #name() {} + // This normal version is identical to the user's (we know it won't have any arguments, and we know its return type) + // We use the user's return type to prevent unused imports warnings in their code + #[cfg(not(target_arch = "wasm32"))] + #vis async fn #name() -> #return_type { + #block + } + }, } } diff --git a/packages/perseus/src/lib.rs b/packages/perseus/src/lib.rs index e284fb06a0..9d19a76494 100644 --- a/packages/perseus/src/lib.rs +++ b/packages/perseus/src/lib.rs @@ -71,8 +71,8 @@ pub use client::{run_client, ClientReturn}; #[cfg(feature = "macros")] pub use perseus_macro::{ amalgamate_states, browser, browser_main, build_paths, build_state, engine, engine_main, - global_build_state, head, main, main_export, make_rx, request_state, set_headers, template, - template_rx, test, + global_build_state, head, main, main_export, make_rx, request_state, set_headers, + should_revalidate, template, template_rx, test, }; pub use sycamore::prelude::{DomNode, Html, HydrateNode, SsrNode}; pub use sycamore_router::{navigate, navigate_replace, Route}; // TODO Should we be exporting `Route` anymore? From 53da801c148b808bc667fd07391f2277d6d81702 Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Mon, 20 Jun 2022 19:44:14 +1000 Subject: [PATCH 35/37] fix: fixed imports in headers example --- examples/core/set_headers/src/templates/index.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/core/set_headers/src/templates/index.rs b/examples/core/set_headers/src/templates/index.rs index 2a127a5827..db75748b8a 100644 --- a/examples/core/set_headers/src/templates/index.rs +++ b/examples/core/set_headers/src/templates/index.rs @@ -1,7 +1,4 @@ -use perseus::{ - http::header::{HeaderMap, HeaderName}, - Html, RenderFnResultWithCause, Template, -}; +use perseus::{Html, RenderFnResultWithCause, Template}; use sycamore::prelude::{view, Scope, SsrNode, View}; #[perseus::make_rx(PageStateRx)] @@ -40,8 +37,12 @@ pub async fn get_build_state(_path: String, _locale: String) -> RenderFnResultWi // For legacy reasons, this takes an `Option`, but, if you're generating state, it will always be here // In v0.4.0, this will be updated to take just your page's state (if it has any) +// Unfortunately, this return type does have to be fully qualified, or you have to import it with a server-only target-gate #[perseus::set_headers] -pub fn set_headers(state: Option) -> HeaderMap { +pub fn set_headers(state: Option) -> perseus::http::header::HeaderMap { + // These imports are only available on the server-side, which this function is automatically gated to + use perseus::http::header::{HeaderMap, HeaderName}; + let mut map = HeaderMap::new(); map.insert( HeaderName::from_lowercase(b"x-greeting").unwrap(), From b2bb8f37b5e653abb5631b89d218c3692fa5fadf Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Tue, 21 Jun 2022 19:47:12 +1000 Subject: [PATCH 36/37] fix: fixed static content paths in new layout --- packages/perseus/src/init.rs | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/packages/perseus/src/init.rs b/packages/perseus/src/init.rs index 8bd69757a1..1ae84db0a4 100644 --- a/packages/perseus/src/init.rs +++ b/packages/perseus/src/init.rs @@ -701,22 +701,8 @@ impl PerseusAppBase { } else if path.starts_with("../") { // Anything outside this directory is a security risk as well panic!("it's a security risk to include paths outside the current directory in `static_aliases` ('{}')", path); - } else if path.starts_with("./") { - // `./` -> `../` (moving to execution from `.perseus/`) - // But if we're operating standalone, it stays the same - if cfg!(feature = "standalone") { - path.to_string() - } else { - format!(".{}", path) - } } else { - // Anything else gets a `../` prepended - // But if we're operating standalone, it stays the same - if cfg!(feature = "standalone") { - path.to_string() - } else { - format!("../{}", path) - } + path.to_string() }; scoped_static_aliases.insert(url, new_path); From 143c1419100039e42ed73f8eb69870d8ace39488 Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Wed, 22 Jun 2022 19:47:43 +1000 Subject: [PATCH 37/37] fix: fixed imports in state generation example --- examples/comprehensive/tiny/src/lib.rs | 2 +- examples/core/state_generation/src/templates/amalgamation.rs | 4 +++- examples/core/state_generation/src/templates/request_state.rs | 4 +++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/examples/comprehensive/tiny/src/lib.rs b/examples/comprehensive/tiny/src/lib.rs index d514a7ee8c..f242810145 100644 --- a/examples/comprehensive/tiny/src/lib.rs +++ b/examples/comprehensive/tiny/src/lib.rs @@ -1,4 +1,4 @@ -use perseus::{Html, PerseusApp, Template, ErrorPages}; +use perseus::{ErrorPages, Html, PerseusApp, Template}; use sycamore::view; #[perseus::main(perseus_integration::dflt_server)] diff --git a/examples/core/state_generation/src/templates/amalgamation.rs b/examples/core/state_generation/src/templates/amalgamation.rs index a92de09901..b8f23b0c83 100644 --- a/examples/core/state_generation/src/templates/amalgamation.rs +++ b/examples/core/state_generation/src/templates/amalgamation.rs @@ -1,4 +1,6 @@ -use perseus::{RenderFnResultWithCause, Request, States, Template}; +use perseus::{RenderFnResultWithCause, Template}; +#[cfg(not(target_arch = "wasm32"))] +use perseus::{Request, States}; use sycamore::prelude::{view, Html, Scope, View}; #[perseus::make_rx(PageStateRx)] diff --git a/examples/core/state_generation/src/templates/request_state.rs b/examples/core/state_generation/src/templates/request_state.rs index 491fa5bdd3..2bbd6bc35f 100644 --- a/examples/core/state_generation/src/templates/request_state.rs +++ b/examples/core/state_generation/src/templates/request_state.rs @@ -1,4 +1,6 @@ -use perseus::{RenderFnResultWithCause, Request, Template}; +#[cfg(not(target_arch = "wasm32"))] +use perseus::Request; +use perseus::{RenderFnResultWithCause, Template}; use sycamore::prelude::{view, Html, Scope, View}; #[perseus::make_rx(PageStateRx)]