diff --git a/Cargo.lock b/Cargo.lock index 915066e24..42014375f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4104,6 +4104,13 @@ dependencies = [ "once_cell", ] +[[package]] +name = "dioxus-playwright-default-features-disabled-test" +version = "0.1.0" +dependencies = [ + "dioxus", +] + [[package]] name = "dioxus-playwright-fullstack-mounted-test" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 296bd99ec..aa0a8e247 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -103,6 +103,7 @@ members = [ "packages/playwright-tests/nested-suspense", "packages/playwright-tests/cli-optimization", "packages/playwright-tests/wasm-split-harness", + "packages/playwright-tests/default-features-disabled" ] [workspace.package] diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index 618223f10..77d9464db 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -92,7 +92,7 @@ impl BuildRequest { pub(crate) async fn build_server(&self) -> Result> { tracing::debug!("Building server..."); - if !self.build.fullstack { + if !self.build.fullstack() { return Ok(None); } diff --git a/packages/cli/src/cli/build.rs b/packages/cli/src/cli/build.rs index a33ca9a26..31bbe2d56 100644 --- a/packages/cli/src/cli/build.rs +++ b/packages/cli/src/cli/build.rs @@ -31,8 +31,8 @@ pub(crate) struct BuildArgs { /// Build the fullstack variant of this app, using that as the fileserver and backend /// /// This defaults to `false` but will be overridden to true if the `fullstack` feature is enabled. - #[clap(long)] - pub(crate) fullstack: bool, + #[arg(long, default_missing_value="true", num_args=0..=1)] + pub(crate) fullstack: Option, /// Run the ssg config of the app and generate the files #[clap(long)] @@ -91,12 +91,25 @@ impl BuildArgs { /// IE if they've specified "fullstack" as a feature on `dioxus`, then we want to build the /// fullstack variant even if they omitted the `--fullstack` flag. pub(crate) async fn resolve(&mut self, krate: &DioxusCrate) -> Result<()> { - let default_platform = krate.default_platform(); + let default_platforms = krate.default_platforms(); + let default_platform = default_platforms.iter().find(|p| **p != Platform::Server); + let default_server = default_platforms.iter().any(|p| *p == Platform::Server); let auto_platform = krate.autodetect_platform(); - // The user passed --platform XYZ but already has `default = ["ABC"]` in their Cargo.toml - // We want to strip out the default platform and use the one they passed, setting no-default-features - if self.platform.is_some() && default_platform.is_some() { + // Make sure we set the fullstack platform so we actually build the fullstack variant + // Users need to enable "fullstack" in their default feature set or explicitly pass the flag + self.fullstack = Some( + self.fullstack() + || self.fullstack.is_none() + && (default_server || krate.has_dioxus_feature("fullstack")), + ); + + // If the current build is a fullstack build which includes either the client or the server in the default features, + // remove that default feature and just add it back into the client or server args. If they passed in an explicit platform + // but they also have a default feature platform, strip out the default features and add back in the platform they passed in. + if self.fullstack() && (default_server || default_platform.is_some()) + || self.platform.is_some() && default_platform.is_some() + { self.target_args.no_default_features = true; self.target_args .features @@ -126,16 +139,11 @@ impl BuildArgs { .server_features .push(krate.feature_for_platform(Platform::Server)); - // Make sure we set the fullstack platform so we actually build the fullstack variant - // Users need to enable "fullstack" in their default feature set. - // todo(jon): fullstack *could* be a feature of the app, but right now we're assuming it's always enabled - self.fullstack = self.fullstack || krate.has_dioxus_feature("fullstack"); - // Make sure we have a server feature if we're building a fullstack app // // todo(jon): eventually we want to let users pass a `--server ` flag to specify a package to use as the server // however, it'll take some time to support that and we don't have a great RPC binding layer between the two yet - if self.fullstack && self.target_args.server_features.is_empty() { + if self.fullstack() && self.target_args.server_features.is_empty() { return Err(anyhow::anyhow!("Fullstack builds require a server feature on the target crate. Add a `server` feature to the crate and try again.").into()); } @@ -191,4 +199,9 @@ impl BuildArgs { pub(crate) fn platform(&self) -> Platform { self.platform.expect("Platform was not set") } + + /// Check if this is a fullstack build + pub(crate) fn fullstack(&self) -> bool { + self.fullstack.unwrap_or(false) + } } diff --git a/packages/cli/src/cli/bundle.rs b/packages/cli/src/cli/bundle.rs index b1317683f..e3939bc30 100644 --- a/packages/cli/src/cli/bundle.rs +++ b/packages/cli/src/cli/bundle.rs @@ -54,7 +54,7 @@ impl Bundle { let mut bundles = vec![]; // Copy the server over if it exists - if bundle.build.build.fullstack { + if bundle.build.build.fullstack() { bundles.push(bundle.server_exe().unwrap()); } diff --git a/packages/cli/src/cli/run.rs b/packages/cli/src/cli/run.rs index 55e254f1a..79f303279 100644 --- a/packages/cli/src/cli/run.rs +++ b/packages/cli/src/cli/run.rs @@ -27,7 +27,7 @@ impl RunArgs { let fullstack_ip = "127.0.0.1:8080".parse().unwrap(); let mut open_address = None; - if self.build_args.platform() == Platform::Web || self.build_args.fullstack { + if self.build_args.platform() == Platform::Web || self.build_args.fullstack() { open_address = Some(fullstack_ip); tracing::info!("Serving at: {}", fullstack_ip); } diff --git a/packages/cli/src/cli/serve.rs b/packages/cli/src/cli/serve.rs index 1ab9e22c0..5cb756de9 100644 --- a/packages/cli/src/cli/serve.rs +++ b/packages/cli/src/cli/serve.rs @@ -104,7 +104,7 @@ impl ServeArgs { match self.build_arguments.platform() { Platform::Server => true, // During SSG, just serve the static files instead of running the server - _ => self.build_arguments.fullstack && !self.build_arguments.ssg, + _ => self.build_arguments.fullstack() && !self.build_arguments.ssg, } } } diff --git a/packages/cli/src/dioxus_crate.rs b/packages/cli/src/dioxus_crate.rs index ad62f250d..42fea7e62 100644 --- a/packages/cli/src/dioxus_crate.rs +++ b/packages/cli/src/dioxus_crate.rs @@ -252,7 +252,9 @@ impl DioxusCrate { .iter() .flat_map(|feature| { tracing::trace!("Autodetecting platform from feature {feature}"); - Platform::autodetect_from_cargo_feature(feature).map(|f| (f, feature.to_string())) + Platform::autodetect_from_cargo_feature(feature) + .filter(|platform| *platform != Platform::Server) + .map(|f| (f, feature.to_string())) }) .collect::>(); @@ -459,8 +461,11 @@ impl DioxusCrate { .map(|krate| krate.krate.version.to_string()) } - pub(crate) fn default_platform(&self) -> Option { - let default = self.package().features.get("default")?; + pub(crate) fn default_platforms(&self) -> Vec { + let Some(default) = self.package().features.get("default") else { + return Vec::new(); + }; + let mut platforms = vec![]; // we only trace features 1 level deep.. for feature in default.iter() { @@ -468,8 +473,8 @@ impl DioxusCrate { if feature.starts_with("dioxus/") { let dx_feature = feature.trim_start_matches("dioxus/"); let auto = Platform::autodetect_from_cargo_feature(dx_feature); - if auto.is_some() { - return auto; + if let Some(auto) = auto { + platforms.push(auto); } } @@ -480,15 +485,17 @@ impl DioxusCrate { if feature.starts_with("dioxus/") { let dx_feature = feature.trim_start_matches("dioxus/"); let auto = Platform::autodetect_from_cargo_feature(dx_feature); - if auto.is_some() { - return auto; + if let Some(auto) = auto { + platforms.push(auto); } } } } } - None + platforms.sort(); + platforms.dedup(); + platforms } /// Gather the features that are enabled for the package diff --git a/packages/cli/src/serve/output.rs b/packages/cli/src/serve/output.rs index 8289b5b1d..11c375319 100644 --- a/packages/cli/src/serve/output.rs +++ b/packages/cli/src/serve/output.rs @@ -497,7 +497,7 @@ impl Output { state.build_engine.compile_duration(), ); - if state.build_engine.request.build.fullstack { + if state.build_engine.request.build.fullstack() { self.render_single_gauge( frame, second_progress, @@ -651,7 +651,7 @@ impl Output { Paragraph::new(Line::from(vec![ "Platform: ".gray(), self.platform.expected_name().yellow(), - if state.opts.build_arguments.fullstack { + if state.opts.build_arguments.fullstack() { " + fullstack".yellow() } else { " ".dark_gray() diff --git a/packages/playwright-tests/default-features-disabled.spec.js b/packages/playwright-tests/default-features-disabled.spec.js new file mode 100644 index 000000000..88509034f --- /dev/null +++ b/packages/playwright-tests/default-features-disabled.spec.js @@ -0,0 +1,11 @@ +// @ts-check +const { test, expect } = require("@playwright/test"); + +test("loads with correct features", async ({ page }) => { + await page.goto("http://localhost:8002"); + + // Expect the page to contain the pending text. + const main = page.locator("#main"); + await expect(main).toContainText('server features: ["server"]'); + await expect(main).toContainText('client features: ["web"]'); +}); diff --git a/packages/playwright-tests/default-features-disabled/.gitignore b/packages/playwright-tests/default-features-disabled/.gitignore new file mode 100644 index 000000000..4a7bcb809 --- /dev/null +++ b/packages/playwright-tests/default-features-disabled/.gitignore @@ -0,0 +1,3 @@ +.dioxus +dist +target \ No newline at end of file diff --git a/packages/playwright-tests/default-features-disabled/Cargo.toml b/packages/playwright-tests/default-features-disabled/Cargo.toml new file mode 100644 index 000000000..7dcb07f8a --- /dev/null +++ b/packages/playwright-tests/default-features-disabled/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "dioxus-playwright-default-features-disabled-test" +version = "0.1.0" +edition = "2021" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +dioxus = { workspace = true, features = ["fullstack"] } + +[features] +# Web is a default feature, but it shouldn't actually be enabled in server builds +default = ["web"] +server = ["dioxus/server"] +web = ["dioxus/web"] diff --git a/packages/playwright-tests/default-features-disabled/src/main.rs b/packages/playwright-tests/default-features-disabled/src/main.rs new file mode 100644 index 000000000..2f0f15a00 --- /dev/null +++ b/packages/playwright-tests/default-features-disabled/src/main.rs @@ -0,0 +1,42 @@ +// This test asserts that the client feature is disable on the server build by the cli +// even if it is set as a default feature + +#![allow(non_snake_case)] +use dioxus::prelude::*; + +fn main() { + launch(app); +} + +fn app() -> Element { + let server_features = use_server_future(get_server_features)?.unwrap().unwrap(); + let mut client_features = use_signal(Vec::new); + use_effect(move || { + client_features.set(current_platform_features()); + }); + + rsx! { + div { + "server features: {server_features:?}" + } + div { + "client features: {client_features:?}" + } + } +} + +fn current_platform_features() -> Vec { + let mut features = Vec::new(); + if cfg!(feature = "web") { + features.push("web".to_string()); + } + if cfg!(feature = "server") { + features.push("server".to_string()); + } + features +} + +#[server] +async fn get_server_features() -> Result, ServerFnError> { + Ok(current_platform_features()) +} diff --git a/packages/playwright-tests/playwright.config.js b/packages/playwright-tests/playwright.config.js index 44f84ecc6..0432af973 100644 --- a/packages/playwright-tests/playwright.config.js +++ b/packages/playwright-tests/playwright.config.js @@ -166,5 +166,14 @@ module.exports = defineConfig({ reuseExistingServer: !process.env.CI, stdout: "pipe", }, + { + cwd: path.join(process.cwd(), "default-features-disabled"), + command: + 'cargo run --package dioxus-cli --release -- serve --force-sequential --addr "127.0.0.1" --port 8002', + port: 8002, + timeout: 50 * 60 * 1000, + reuseExistingServer: !process.env.CI, + stdout: "pipe", + }, ], });