Merge branch 'master' into cli-config-library

This commit is contained in:
Evan Almloff committed 2024-01-08 13:24:16 -06:00
commit 23509ada9f
285 files changed
+13088 -4835

No files matched your search

+6 -3
View File
@@ -31,7 +31,7 @@ cargo_metadata = "0.15.0"
tokio = { version = "1.16.1", features = ["fs", "sync", "rt", "macros"] }
atty = "0.2.14"
chrono = "0.4.19"
anyhow = "1.0.53"
anyhow = "1"
hyper = "0.14.17"
hyper-rustls = "0.23.2"
indicatif = "0.17.5"
@@ -73,8 +73,10 @@ cargo-generate = "0.18"
toml_edit = "0.19.11"
# bundling
tauri-bundler = { version = "=1.3.0", features = ["native-tls-vendored"] }
tauri-utils = "=1.4.*"
tauri-bundler = { version = "=1.4.*", features = ["native-tls-vendored"] }
tauri-utils = "=1.5.*"
manganis-cli-support= { git = "https://github.com/DioxusLabs/collect-assets", features = ["webp", "html"] }
dioxus-autofmt = { workspace = true }
dioxus-check = { workspace = true }
@@ -84,6 +86,7 @@ dioxus-html = { workspace = true, features = ["hot-reload-context"] }
dioxus-core = { workspace = true, features = ["serialize"] }
dioxus-hot-reload = { workspace = true }
interprocess-docfix = { version = "1.2.2" }
gitignore = "1.0.8"
[features]
default = []
+4 -4
View File
@@ -10,7 +10,7 @@ It handles building, bundling, development and publishing to simplify developmen
### Install the stable version (recommended)
```sh
```shell
cargo install dioxus-cli
```
@@ -20,7 +20,7 @@ To get the latest bug fixes and features, you can install the development versio
However, this is not fully tested.
That means you're probably going to have more bugs despite having the latest bug fixes.
```sh
```shell
cargo install --git https://github.com/DioxusLabs/dioxus dioxus-cli
```
@@ -29,7 +29,7 @@ and install it in Cargo's global binary directory (`~/.cargo/bin/` by default).
### Install from local folder
```sh
```shell
cargo install --path . --debug
```
@@ -40,7 +40,7 @@ It will be cloned from the [dioxus-template](https://github.com/DioxusLabs/dioxu
Alternatively, you can specify the template path:
```sh
```shell
dx create hello --template gh:dioxuslabs/dioxus-template
```
+2 -2
View File
@@ -1,5 +1,5 @@
// Dioxus-CLI
// https://github.com/DioxusLabs/cli
// https://github.com/DioxusLabs/dioxus/tree/master/packages/cli
(function () {
var protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
@@ -22,4 +22,4 @@
}
};
ws.onclose = reload_upon_connect;
})()
})()
+122 -24
View File
@@ -8,27 +8,32 @@ use dioxus_cli_config::CrateConfig;
use dioxus_cli_config::DioxusConfig;
use dioxus_cli_config::ExecutableType;
use indicatif::{ProgressBar, ProgressStyle};
use lazy_static::lazy_static;
use manganis_cli_support::AssetManifestExt;
use serde::Serialize;
use std::{
fs::{copy, create_dir_all, File},
io::Read,
io::{Read, Write},
panic,
path::PathBuf,
time::Duration,
};
use wasm_bindgen_cli_support::Bindgen;
lazy_static! {
static ref PROGRESS_BARS: indicatif::MultiProgress = indicatif::MultiProgress::new();
}
#[derive(Serialize, Debug, Clone)]
pub struct BuildResult {
pub warnings: Vec<Diagnostic>,
pub elapsed_time: u128,
}
#[allow(unused)]
pub fn build(config: &CrateConfig, quiet: bool) -> Result<BuildResult> {
pub fn build(config: &CrateConfig, _: bool, skip_assets: bool) -> Result<BuildResult> {
// [1] Build the project with cargo, generating a wasm32-unknown-unknown target (is there a more specific, better target to leverage?)
// [2] Generate the appropriate build folders
// [3] Wasm-bindgen the .wasm fiile, and move it into the {builddir}/modules/xxxx/xxxx_bg.wasm
// [3] Wasm-bindgen the .wasm file, and move it into the {builddir}/modules/xxxx/xxxx_bg.wasm
// [4] Wasm-opt the .wasm file with whatever optimizations need to be done
// [5][OPTIONAL] Builds the Tailwind CSS file using the Tailwind standalone binary
// [6] Link up the html page to the wasm module
@@ -43,6 +48,8 @@ pub fn build(config: &CrateConfig, quiet: bool) -> Result<BuildResult> {
..
} = config;
let _gaurd = WebAssetConfigDropGuard::new();
// start to build the assets
let ignore_files = build_assets(config)?;
@@ -51,8 +58,20 @@ pub fn build(config: &CrateConfig, quiet: bool) -> Result<BuildResult> {
// [1] Build the .wasm module
log::info!("🚅 Running build command...");
let cmd = subprocess::Exec::cmd("cargo");
let cmd = cmd
let wasm_check_command = std::process::Command::new("rustup")
.args(["show"])
.output()?;
let wasm_check_output = String::from_utf8(wasm_check_command.stdout).unwrap();
if !wasm_check_output.contains("wasm32-unknown-unknown") {
log::info!("wasm32-unknown-unknown target not detected, installing..");
let _ = std::process::Command::new("rustup")
.args(["target", "add", "wasm32-unknown-unknown"])
.output()?;
}
let cmd = subprocess::Exec::cmd("cargo")
.env("CARGO_TARGET_DIR", target_dir)
.cwd(crate_dir)
.arg("build")
.arg("--target")
@@ -84,6 +103,8 @@ pub fn build(config: &CrateConfig, quiet: bool) -> Result<BuildResult> {
cmd
};
let cmd = cmd.args(&config.cargo_args);
let cmd = match executable {
ExecutableType::Binary(name) => cmd.arg("--bin").arg(name),
ExecutableType::Lib(name) => cmd.arg("--lib").arg(name),
@@ -241,13 +262,21 @@ pub fn build(config: &CrateConfig, quiet: bool) -> Result<BuildResult> {
}
}
if !skip_assets {
process_assets(config)?;
}
Ok(BuildResult {
warnings: warning_messages,
elapsed_time: t_start.elapsed().as_millis(),
})
}
pub fn build_desktop(config: &CrateConfig, _is_serve: bool) -> Result<BuildResult> {
pub fn build_desktop(
config: &CrateConfig,
_is_serve: bool,
skip_assets: bool,
) -> Result<BuildResult> {
log::info!("🚅 Running build [Desktop] command...");
let t_start = std::time::Instant::now();
@@ -255,9 +284,9 @@ pub fn build_desktop(config: &CrateConfig, _is_serve: bool) -> Result<BuildResul
let _guard = dioxus_cli_config::__private::save_config(config);
let mut cmd = subprocess::Exec::cmd("cargo")
.env("CARGO_TARGET_DIR", &config.target_dir)
.cwd(&config.crate_dir)
.arg("build")
.arg("--quiet")
.arg("--message-format=json");
if config.release {
@@ -265,6 +294,8 @@ pub fn build_desktop(config: &CrateConfig, _is_serve: bool) -> Result<BuildResul
}
if config.verbose {
cmd = cmd.arg("--verbose");
} else {
cmd = cmd.arg("--quiet");
}
if config.custom_profile.is_some() {
@@ -277,6 +308,14 @@ pub fn build_desktop(config: &CrateConfig, _is_serve: bool) -> Result<BuildResul
cmd = cmd.arg("--features").arg(features_str);
}
if let Some(target) = &config.target {
cmd = cmd.arg("--target").arg(target);
}
let target_platform = config.target.as_deref().unwrap_or("");
cmd = cmd.args(&config.cargo_args);
let cmd = match &config.executable {
ExecutableType::Binary(name) => cmd.arg("--bin").arg(name),
ExecutableType::Lib(name) => cmd.arg("--lib").arg(name),
@@ -294,12 +333,17 @@ pub fn build_desktop(config: &CrateConfig, _is_serve: bool) -> Result<BuildResul
let mut res_path = match &config.executable {
ExecutableType::Binary(name) | ExecutableType::Lib(name) => {
file_name = name.clone();
config.target_dir.join(release_type).join(name)
config
.target_dir
.join(target_platform)
.join(release_type)
.join(name)
}
ExecutableType::Example(name) => {
file_name = name.clone();
config
.target_dir
.join(target_platform)
.join(release_type)
.join("examples")
.join(name)
@@ -351,6 +395,13 @@ pub fn build_desktop(config: &CrateConfig, _is_serve: bool) -> Result<BuildResul
}
}
if !skip_assets {
// Collect assets
process_assets(config)?;
// Create the __assets_head.html file for bundling
create_assets_head(config)?;
}
log::info!(
"🚩 Build completed: [./{}]",
config.dioxus_config.application.out_dir.clone().display()
@@ -364,11 +415,19 @@ pub fn build_desktop(config: &CrateConfig, _is_serve: bool) -> Result<BuildResul
})
}
fn create_assets_head(config: &CrateConfig) -> Result<()> {
let manifest = config.asset_manifest();
let mut file = File::create(config.out_dir.join("__assets_head.html"))?;
file.write_all(manifest.head().as_bytes())?;
Ok(())
}
fn prettier_build(cmd: subprocess::Exec) -> anyhow::Result<Vec<Diagnostic>> {
let mut warning_messages: Vec<Diagnostic> = vec![];
let pb = ProgressBar::new_spinner();
let mut pb = ProgressBar::new_spinner();
pb.enable_steady_tick(Duration::from_millis(200));
pb = PROGRESS_BARS.add(pb);
pb.set_style(
ProgressStyle::with_template("{spinner:.dim.bold} {wide_msg}")
.unwrap()
@@ -376,14 +435,6 @@ fn prettier_build(cmd: subprocess::Exec) -> anyhow::Result<Vec<Diagnostic>> {
);
pb.set_message("💼 Waiting to start build the project...");
struct StopSpinOnDrop(ProgressBar);
impl Drop for StopSpinOnDrop {
fn drop(&mut self) {
self.0.finish_and_clear();
}
}
let stdout = cmd.detached().stream_stdout()?;
let reader = std::io::BufReader::new(stdout);
@@ -419,14 +470,18 @@ fn prettier_build(cmd: subprocess::Exec) -> anyhow::Result<Vec<Diagnostic>> {
std::process::exit(1);
}
}
_ => (), // Unknown message
_ => {
// Unknown message
}
}
}
Ok(warning_messages)
}
pub fn gen_page(config: &DioxusConfig, serve: bool) -> String {
let crate_root = crate_root().unwrap();
pub fn gen_page(config: &DioxusConfig, serve: bool, skip_assets: bool) -> String {
let _gaurd = WebAssetConfigDropGuard::new();
let crate_root = crate::cargo::crate_root().unwrap();
let custom_html_file = crate_root.join("index.html");
let mut html = if custom_html_file.is_file() {
let mut buf = String::new();
@@ -440,7 +495,7 @@ pub fn gen_page(config: &DioxusConfig, serve: bool) -> String {
String::from(include_str!("./assets/index.html"))
};
let resources = config.web.resource.clone();
let resources = config.dioxus_config.web.resource.clone();
let mut style_list = resources.style.unwrap_or_default();
let mut script_list = resources.script.unwrap_or_default();
@@ -462,6 +517,10 @@ pub fn gen_page(config: &DioxusConfig, serve: bool) -> String {
if config.application.tools.clone().contains_key("tailwindcss") {
style_str.push_str("<link rel=\"stylesheet\" href=\"/{base_path}/tailwind.css\">\n");
}
if !skip_assets {
let manifest = config.asset_manifest();
style_str.push_str(&manifest.head());
}
replace_or_insert_before("{style_include}", &style_str, "</head", &mut html);
@@ -482,11 +541,11 @@ pub fn gen_page(config: &DioxusConfig, serve: bool) -> String {
);
}
let base_path = match &config.web.app.base_path {
let base_path = match &config.dioxus_config.web.app.base_path {
Some(path) => path,
None => ".",
};
let app_name = &config.application.name;
let app_name = &config.dioxus_config.application.name;
// Check if a script already exists
if html.contains("{app_name}") && html.contains("{base_path}") {
html = html.replace("{app_name}", app_name);
@@ -675,3 +734,42 @@ fn build_assets(config: &CrateConfig) -> Result<Vec<PathBuf>> {
Ok(result)
}
/// Process any assets collected from the binary
fn process_assets(config: &CrateConfig) -> anyhow::Result<()> {
let manifest = config.asset_manifest();
let static_asset_output_dir = PathBuf::from(
config
.dioxus_config
.web
.app
.base_path
.clone()
.unwrap_or_default(),
);
let static_asset_output_dir = config.out_dir.join(static_asset_output_dir);
manifest.copy_static_assets_to(static_asset_output_dir)?;
Ok(())
}
pub(crate) struct WebAssetConfigDropGuard;
impl WebAssetConfigDropGuard {
pub fn new() -> Self {
// Set up the collect asset config
manganis_cli_support::Config::default()
.with_assets_serve_location("/")
.save();
Self {}
}
}
impl Drop for WebAssetConfigDropGuard {
fn drop(&mut self) {
// Reset the config
manganis_cli_support::Config::default().save();
}
}
+147 -76
View File
@@ -1,3 +1,4 @@
use dioxus_autofmt::{IndentOptions, IndentType};
use futures::{stream::FuturesUnordered, StreamExt};
use std::{fs, path::Path, process::exit};
@@ -26,16 +27,19 @@ pub struct Autoformat {
impl Autoformat {
// Todo: autoformat the entire crate
pub async fn autoformat(self) -> Result<()> {
let Autoformat { check, raw, file } = self;
// Default to formatting the project
if self.raw.is_none() && self.file.is_none() {
if let Err(e) = autoformat_project(self.check).await {
if raw.is_none() && file.is_none() {
if let Err(e) = autoformat_project(check).await {
eprintln!("error formatting project: {}", e);
exit(1);
}
}
if let Some(raw) = self.raw {
if let Some(inner) = dioxus_autofmt::fmt_block(&raw, 0) {
if let Some(raw) = raw {
let indent = indentation_for(".")?;
if let Some(inner) = dioxus_autofmt::fmt_block(&raw, 0, indent) {
println!("{}", inner);
} else {
// exit process with error
@@ -45,43 +49,90 @@ impl Autoformat {
}
// Format single file
if let Some(file) = self.file {
let file_content = if file == "-" {
let mut contents = String::new();
std::io::stdin().read_to_string(&mut contents)?;
Ok(contents)
} else {
fs::read_to_string(&file)
};
match file_content {
Ok(s) => {
let edits = dioxus_autofmt::fmt_file(&s);
let out = dioxus_autofmt::apply_formats(&s, edits);
if file == "-" {
print!("{}", out);
} else {
match fs::write(&file, out) {
Ok(_) => {
println!("formatted {}", file);
}
Err(e) => {
eprintln!("failed to write formatted content to file: {}", e);
}
}
}
}
Err(e) => {
eprintln!("failed to open file: {}", e);
exit(1);
}
}
if let Some(file) = file {
refactor_file(file)?;
}
Ok(())
}
}
fn refactor_file(file: String) -> Result<(), Error> {
let indent = indentation_for(".")?;
let file_content = if file == "-" {
let mut contents = String::new();
std::io::stdin().read_to_string(&mut contents)?;
Ok(contents)
} else {
fs::read_to_string(&file)
};
let Ok(s) = file_content else {
eprintln!("failed to open file: {}", file_content.unwrap_err());
exit(1);
};
let edits = dioxus_autofmt::fmt_file(&s, indent);
let out = dioxus_autofmt::apply_formats(&s, edits);
if file == "-" {
print!("{}", out);
} else if let Err(e) = fs::write(&file, out) {
eprintln!("failed to write formatted content to file: {e}",);
} else {
println!("formatted {}", file);
}
Ok(())
}
fn get_project_files(config: &CrateConfig) -> Vec<PathBuf> {
let mut files = vec![];
let gitignore_path = config.crate_dir.join(".gitignore");
if gitignore_path.is_file() {
let gitigno = gitignore::File::new(gitignore_path.as_path()).unwrap();
if let Ok(git_files) = gitigno.included_files() {
let git_files = git_files
.into_iter()
.filter(|f| f.ends_with(".rs") && !is_target_dir(f));
files.extend(git_files)
};
} else {
collect_rs_files(&config.crate_dir, &mut files);
}
files
}
fn is_target_dir(file: &Path) -> bool {
let stripped = if let Ok(cwd) = std::env::current_dir() {
file.strip_prefix(cwd).unwrap_or(file)
} else {
file
};
if let Some(first) = stripped.components().next() {
first.as_os_str() == "target"
} else {
false
}
}
async fn format_file(
path: impl AsRef<Path>,
indent: IndentOptions,
) -> Result<usize, tokio::io::Error> {
let contents = tokio::fs::read_to_string(&path).await?;
let edits = dioxus_autofmt::fmt_file(&contents, indent);
let len = edits.len();
if !edits.is_empty() {
let out = dioxus_autofmt::apply_formats(&contents, edits);
tokio::fs::write(path, out).await?;
}
Ok(len)
}
/// Read every .rs file accessible when considering the .gitignore and try to format it
///
/// Runs using Tokio for multithreading, so it should be really really fast
@@ -90,42 +141,27 @@ impl Autoformat {
async fn autoformat_project(check: bool) -> Result<()> {
let crate_config = dioxus_cli_config::CrateConfig::new(None)?;
let mut files_to_format = vec![];
collect_rs_files(&crate_config.crate_dir, &mut files_to_format);
let files_to_format = get_project_files(&crate_config);
if files_to_format.is_empty() {
return Ok(());
}
let indent = indentation_for(&files_to_format[0])?;
let counts = files_to_format
.into_iter()
.filter(|file| {
if file.components().any(|f| f.as_os_str() == "target") {
return false;
}
true
})
.map(|path| async {
let _path = path.clone();
let res = tokio::spawn(async move {
let contents = tokio::fs::read_to_string(&path).await?;
let edits = dioxus_autofmt::fmt_file(&contents);
let len = edits.len();
if !edits.is_empty() {
let out = dioxus_autofmt::apply_formats(&contents, edits);
tokio::fs::write(&path, out).await?;
}
Ok(len) as Result<usize, tokio::io::Error>
})
.await;
let path_clone = path.clone();
let res = tokio::spawn(format_file(path, indent.clone())).await;
match res {
Err(err) => {
eprintln!("error formatting file: {}\n{err}", _path.display());
eprintln!("error formatting file: {}\n{err}", path_clone.display());
None
}
Ok(Err(err)) => {
eprintln!("error formatting file: {}\n{err}", _path.display());
eprintln!("error formatting file: {}\n{err}", path_clone.display());
None
}
Ok(Ok(res)) => Some(res),
@@ -135,13 +171,7 @@ async fn autoformat_project(check: bool) -> Result<()> {
.collect::<Vec<_>>()
.await;
let files_formatted: usize = counts
.into_iter()
.map(|f| match f {
Some(res) => res,
_ => 0,
})
.sum();
let files_formatted: usize = counts.into_iter().flatten().sum();
if files_formatted > 0 && check {
eprintln!("{} files needed formatting", files_formatted);
@@ -151,26 +181,67 @@ async fn autoformat_project(check: bool) -> Result<()> {
Ok(())
}
fn collect_rs_files(folder: &Path, files: &mut Vec<PathBuf>) {
let Ok(folder) = folder.read_dir() else {
fn indentation_for(file_or_dir: impl AsRef<Path>) -> Result<IndentOptions> {
let out = std::process::Command::new("cargo")
.args(["fmt", "--", "--print-config", "current"])
.arg(file_or_dir.as_ref())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::inherit())
.output()?;
if !out.status.success() {
return Err(Error::CargoError("cargo fmt failed".into()));
}
let config = String::from_utf8_lossy(&out.stdout);
let hard_tabs = config
.lines()
.find(|line| line.starts_with("hard_tabs "))
.and_then(|line| line.split_once('='))
.map(|(_, value)| value.trim() == "true")
.ok_or_else(|| {
Error::RuntimeError("Could not find hard_tabs option in rustfmt config".into())
})?;
let tab_spaces = config
.lines()
.find(|line| line.starts_with("tab_spaces "))
.and_then(|line| line.split_once('='))
.map(|(_, value)| value.trim().parse::<usize>())
.ok_or_else(|| {
Error::RuntimeError("Could not find tab_spaces option in rustfmt config".into())
})?
.map_err(|_| {
Error::RuntimeError("Could not parse tab_spaces option in rustfmt config".into())
})?;
Ok(IndentOptions::new(
if hard_tabs {
IndentType::Tabs
} else {
IndentType::Spaces
},
tab_spaces,
))
}
fn collect_rs_files(folder: &impl AsRef<Path>, files: &mut Vec<PathBuf>) {
if is_target_dir(folder.as_ref()) {
return;
}
let Ok(folder) = folder.as_ref().read_dir() else {
return;
};
// load the gitignore
for entry in folder {
let Ok(entry) = entry else {
continue;
};
let path = entry.path();
if path.is_dir() {
collect_rs_files(&path, files);
}
if let Some(ext) = path.extension() {
if ext == "rs" {
if ext == "rs" && !is_target_dir(&path) {
files.push(path);
}
}
+54 -11
View File
@@ -1,5 +1,8 @@
#[cfg(feature = "plugin")]
use crate::plugin::PluginManager;
use crate::server::fullstack::FullstackServerEnvGuard;
use crate::server::fullstack::FullstackWebEnvGuard;
use crate::WebAssetConfigDropGuard;
use dioxus_cli_config::Platform;
use super::*;
@@ -13,23 +16,26 @@ pub struct Build {
}
impl Build {
pub fn build(self, bin: Option<PathBuf>) -> Result<()> {
pub fn build(self, bin: Option<PathBuf>, target_dir: Option<&std::path::Path>) -> Result<()> {
let mut crate_config = dioxus_cli_config::CrateConfig::new(bin)?;
if let Some(target_dir) = target_dir {
crate_config.target_dir = target_dir.to_path_buf();
}
// change the release state.
crate_config.with_release(self.build.release);
crate_config.with_verbose(self.build.verbose);
if self.build.example.is_some() {
crate_config.as_example(self.build.example.unwrap());
crate_config.as_example(self.build.example.clone().unwrap());
}
if self.build.profile.is_some() {
crate_config.set_profile(self.build.profile.unwrap());
crate_config.set_profile(self.build.profile.clone().unwrap());
}
if self.build.features.is_some() {
crate_config.set_features(self.build.features.unwrap());
crate_config.set_features(self.build.features.clone().unwrap());
}
let platform = self
@@ -37,19 +43,56 @@ impl Build {
.platform
.unwrap_or(crate_config.dioxus_config.application.default_platform);
#[cfg(feature = "plugin")]
let _ = PluginManager::on_build_start(&crate_config, &platform);
if let Some(target) = self.build.target.clone() {
crate_config.set_target(target);
}
crate_config.set_cargo_args(self.build.cargo_args.clone());
// #[cfg(feature = "plugin")]
// let _ = PluginManager::on_build_start(&crate_config, &platform);
match platform {
Platform::Web => {
crate::builder::build(&crate_config, true)?;
crate::builder::build(&crate_config, false, self.build.skip_assets)?;
}
Platform::Desktop => {
crate::builder::build_desktop(&crate_config, false)?;
crate::builder::build_desktop(&crate_config, false, self.build.skip_assets)?;
}
Platform::Fullstack => {
// Fullstack mode must be built with web configs on the desktop (server) binary as well as the web binary
let _config = WebAssetConfigDropGuard::new();
{
let mut web_config = crate_config.clone();
let _gaurd = FullstackWebEnvGuard::new(&self.build);
let web_feature = self.build.client_feature;
let features = &mut web_config.features;
match features {
Some(features) => {
features.push(web_feature);
}
None => web_config.features = Some(vec![web_feature]),
};
crate::builder::build(&crate_config, false, self.build.skip_assets)?;
}
{
let mut desktop_config = crate_config.clone();
let desktop_feature = self.build.server_feature;
let features = &mut desktop_config.features;
match features {
Some(features) => {
features.push(desktop_feature);
}
None => desktop_config.features = Some(vec![desktop_feature]),
};
let _gaurd =
FullstackServerEnvGuard::new(self.build.force_debug, self.build.release);
crate::builder::build_desktop(&desktop_config, false, self.build.skip_assets)?;
}
}
}
let temp = gen_page(&crate_config.dioxus_config, false);
let temp = gen_page(&crate_config, false, self.build.skip_assets);
let mut file = std::fs::File::create(
crate_config
@@ -59,8 +102,8 @@ impl Build {
)?;
file.write_all(temp.as_bytes())?;
#[cfg(feature = "plugin")]
let _ = PluginManager::on_build_finish(&crate_config, &platform);
// #[cfg(feature = "plugin")]
// let _ = PluginManager::on_build_finish(&crate_config, &platform);
Ok(())
}
+27 -3
View File
@@ -77,8 +77,14 @@ impl Bundle {
crate_config.set_profile(self.build.profile.unwrap());
}
if let Some(target) = &self.build.target {
crate_config.set_target(target.to_string());
}
crate_config.set_cargo_args(self.build.cargo_args);
// build the desktop app
build_desktop(&crate_config, false)?;
build_desktop(&crate_config, false, false)?;
// copy the binary to the out dir
let package = crate_config.manifest.package.unwrap();
@@ -129,6 +135,19 @@ impl Bundle {
}
}
// Add all assets from collect assets to the bundle
{
let config = manganis_cli_support::Config::current();
let location = config.assets_serve_location().to_string();
let location = format!("./{}", location);
println!("Adding assets from {} to bundle", location);
if let Some(resources) = &mut bundle_settings.resources {
resources.push(location);
} else {
bundle_settings.resources = Some(vec![location]);
}
}
let mut settings = SettingsBuilder::new()
.project_out_directory(crate_config.out_dir)
.package_settings(PackageSettings {
@@ -149,6 +168,11 @@ impl Bundle {
.collect(),
);
}
if let Some(target) = &self.build.target {
settings = settings.target(target.to_string());
}
let settings = settings.build();
// on macos we need to set CI=true (https://github.com/tauri-apps/tauri/issues/2567)
@@ -157,9 +181,9 @@ impl Bundle {
tauri_bundler::bundle::bundle_project(settings.unwrap()).unwrap_or_else(|err|{
#[cfg(target_os = "macos")]
panic!("Failed to bundle project: {}\nMake sure you have automation enabled in your terminal (https://github.com/tauri-apps/tauri/issues/3055#issuecomment-1624389208) and full disk access enabled for your terminal (https://github.com/tauri-apps/tauri/issues/3055#issuecomment-1624389208)", err);
panic!("Failed to bundle project: {:#?}\nMake sure you have automation enabled in your terminal (https://github.com/tauri-apps/tauri/issues/3055#issuecomment-1624389208) and full disk access enabled for your terminal (https://github.com/tauri-apps/tauri/issues/3055#issuecomment-1624389208)", err);
#[cfg(not(target_os = "macos"))]
panic!("Failed to bundle project: {}", err);
panic!("Failed to bundle project: {:#?}", err);
});
Ok(())
+80 -9
View File
@@ -5,15 +5,16 @@ use super::*;
/// Config options for the build system.
#[derive(Clone, Debug, Default, Deserialize, Parser)]
pub struct ConfigOptsBuild {
/// The index HTML file to drive the bundling process [default: index.html]
#[arg(long)]
pub target: Option<PathBuf>,
/// Build in release mode [default: false]
#[clap(long)]
#[serde(default)]
pub release: bool,
/// This flag only applies to fullstack builds. By default fullstack builds will run with something in between debug and release mode. This flag will force the build to run in debug mode. [default: false]
#[clap(long)]
#[serde(default)]
pub force_debug: bool,
// Use verbose output [default: false]
#[clap(long)]
#[serde(default)]
@@ -31,17 +32,53 @@ pub struct ConfigOptsBuild {
#[clap(long, value_enum)]
pub platform: Option<Platform>,
/// Skip collecting assets from dependencies [default: false]
#[clap(long)]
#[serde(default)]
pub skip_assets: bool,
/// Space separated list of features to activate
#[clap(long)]
pub features: Option<Vec<String>>,
/// The feature to use for the client in a fullstack app [default: "web"]
#[clap(long, default_value_t = { "web".to_string() })]
pub client_feature: String,
/// The feature to use for the server in a fullstack app [default: "ssr"]
#[clap(long, default_value_t = { "ssr".to_string() })]
pub server_feature: String,
/// Rustc platform triple
#[clap(long)]
pub target: Option<String>,
/// Extra arguments passed to cargo build
#[clap(last = true)]
pub cargo_args: Vec<String>,
}
impl From<ConfigOptsServe> for ConfigOptsBuild {
fn from(serve: ConfigOptsServe) -> Self {
Self {
target: serve.target,
release: serve.release,
verbose: serve.verbose,
example: serve.example,
profile: serve.profile,
platform: serve.platform,
features: serve.features,
client_feature: serve.client_feature,
server_feature: serve.server_feature,
skip_assets: serve.skip_assets,
force_debug: serve.force_debug,
cargo_args: serve.cargo_args,
}
}
}
#[derive(Clone, Debug, Default, Deserialize, Parser)]
pub struct ConfigOptsServe {
/// The index HTML file to drive the bundling process [default: index.html]
#[arg(short, long)]
pub target: Option<PathBuf>,
/// Port of dev server
#[clap(long)]
#[clap(default_value_t = 8080)]
@@ -61,6 +98,11 @@ pub struct ConfigOptsServe {
#[serde(default)]
pub release: bool,
/// This flag only applies to fullstack builds. By default fullstack builds will run with something in between debug and release mode. This flag will force the build to run in debug mode. [default: false]
#[clap(long)]
#[serde(default)]
pub force_debug: bool,
// Use verbose output [default: false]
#[clap(long)]
#[serde(default)]
@@ -70,7 +112,7 @@ pub struct ConfigOptsServe {
#[clap(long)]
pub profile: Option<String>,
/// Build platform: support Web & Desktop [default: "default_platform"]
/// Build platform: support Web, Desktop, and Fullstack [default: "default_platform"]
#[clap(long, value_enum)]
pub platform: Option<Platform>,
@@ -88,6 +130,27 @@ pub struct ConfigOptsServe {
/// Space separated list of features to activate
#[clap(long)]
pub features: Option<Vec<String>>,
/// Skip collecting assets from dependencies [default: false]
#[clap(long)]
#[serde(default)]
pub skip_assets: bool,
/// The feature to use for the client in a fullstack app [default: "web"]
#[clap(long, default_value_t = { "web".to_string() })]
pub client_feature: String,
/// The feature to use for the server in a fullstack app [default: "ssr"]
#[clap(long, default_value_t = { "ssr".to_string() })]
pub server_feature: String,
/// Rustc platform triple
#[clap(long)]
pub target: Option<String>,
/// Extra arguments passed to cargo build
#[clap(last = true)]
pub cargo_args: Vec<String>,
}
/// Config options for the bundling system.
@@ -118,4 +181,12 @@ pub struct ConfigOptsBundle {
/// Space separated list of features to activate
#[clap(long)]
pub features: Option<Vec<String>>,
/// Rustc platform triple
#[clap(long)]
pub target: Option<String>,
/// Extra arguments passed to cargo build
#[clap(last = true)]
pub cargo_args: Vec<String>,
}
+6
View File
@@ -24,6 +24,12 @@ impl Clean {
remove_dir_all(crate_config.crate_dir.join(&out_dir))?;
}
let fullstack_out_dir = crate_config.crate_dir.join(".dioxus");
if fullstack_out_dir.is_dir() {
remove_dir_all(fullstack_out_dir)?;
}
Ok(())
}
}
+21 -6
View File
@@ -14,6 +14,7 @@ pub struct Serve {
impl Serve {
pub async fn serve(self, bin: Option<PathBuf>) -> Result<()> {
let mut crate_config = dioxus_cli_config::CrateConfig::new(bin)?;
let serve_cfg = self.serve.clone();
// change the relase state.
crate_config.with_hot_reload(self.serve.hot_reload);
@@ -33,6 +34,12 @@ impl Serve {
crate_config.set_features(self.serve.features.unwrap());
}
if let Some(target) = self.serve.target {
crate_config.set_target(target);
}
crate_config.set_cargo_args(self.serve.cargo_args);
let platform = self
.serve
.platform
@@ -41,21 +48,29 @@ impl Serve {
match platform {
Platform::Web => {
// generate dev-index page
Serve::regen_dev_page(&crate_config)?;
Serve::regen_dev_page(&crate_config, self.serve.skip_assets)?;
// start the develop server
server::web::startup(self.serve.port, crate_config.clone(), self.serve.open)
.await?;
server::web::startup(
self.serve.port,
crate_config.clone(),
self.serve.open,
self.serve.skip_assets,
)
.await?;
}
Platform::Desktop => {
server::desktop::startup(crate_config.clone()).await?;
server::desktop::startup(crate_config.clone(), &serve_cfg).await?;
}
Platform::Fullstack => {
server::fullstack::startup(crate_config.clone(), &serve_cfg).await?;
}
}
Ok(())
}
pub fn regen_dev_page(crate_config: &CrateConfig) -> Result<()> {
let serve_html = gen_page(&crate_config.dioxus_config, true);
pub fn regen_dev_page(crate_config: &CrateConfig, skip_assets: bool) -> Result<()> {
let serve_html = gen_page(crate_config, true, skip_assets);
let dist_path = crate_config
.crate_dir
+13 -1
View File
@@ -28,7 +28,19 @@ pub fn set_up_logging() {
message = message,
));
})
.level(log::LevelFilter::Info)
.level(match std::env::var("DIOXUS_LOG") {
Ok(level) => match level.to_lowercase().as_str() {
"error" => log::LevelFilter::Error,
"warn" => log::LevelFilter::Warn,
"info" => log::LevelFilter::Info,
"debug" => log::LevelFilter::Debug,
"trace" => log::LevelFilter::Trace,
_ => {
panic!("Invalid log level: {}", level)
}
},
Err(_) => log::LevelFilter::Info,
})
.chain(std::io::stdout())
.apply()
.unwrap();
+16 -13
View File
@@ -43,34 +43,36 @@ async fn main() -> anyhow::Result<()> {
set_up_logging();
let bin = get_bin(args.bin)?;
let bin = get_bin(args.bin);
let _dioxus_config = DioxusConfig::load(Some(bin.clone()))
if let Ok(bin) = &bin {
let _dioxus_config = DioxusConfig::load(Some(bin.clone()))
.map_err(|e| anyhow!("Failed to load Dioxus config because: {e}"))?
.unwrap_or_else(|| {
log::warn!("You appear to be creating a Dioxus project from scratch; we will use the default config");
DioxusConfig::default()
});
#[cfg(feature = "plugin")]
PluginManager::init(_dioxus_config.plugin)
.map_err(|e| anyhow!("🚫 Plugin system initialization failed: {e}"))?;
#[cfg(feature = "plugin")]
PluginManager::init(_dioxus_config.plugin)
.map_err(|e| anyhow!("🚫 Plugin system initialization failed: {e}"))?;
}
match args.action {
Translate(opts) => opts
.translate()
.map_err(|e| anyhow!("🚫 Translation of HTML into RSX failed: {}", e)),
Build(opts) => opts
.build(Some(bin.clone()))
Build(opts) if bin.is_ok() => opts
.build(Some(bin.unwrap().clone()), None)
.map_err(|e| anyhow!("🚫 Building project failed: {}", e)),
Clean(opts) => opts
.clean(Some(bin.clone()))
Clean(opts) if bin.is_ok() => opts
.clean(Some(bin.unwrap().clone()))
.map_err(|e| anyhow!("🚫 Cleaning project failed: {}", e)),
Serve(opts) => opts
.serve(Some(bin.clone()))
Serve(opts) if bin.is_ok() => opts
.serve(Some(bin.unwrap().clone()))
.await
.map_err(|e| anyhow!("🚫 Serving project failed: {}", e)),
@@ -82,8 +84,8 @@ async fn main() -> anyhow::Result<()> {
.config()
.map_err(|e| anyhow!("🚫 Configuring new project failed: {}", e)),
Bundle(opts) => opts
.bundle(Some(bin.clone()))
Bundle(opts) if bin.is_ok() => opts
.bundle(Some(bin.unwrap().clone()))
.map_err(|e| anyhow!("🚫 Bundling project failed: {}", e)),
#[cfg(feature = "plugin")]
@@ -108,5 +110,6 @@ async fn main() -> anyhow::Result<()> {
Ok(())
}
_ => Err(anyhow::anyhow!(bin.unwrap_err())),
}
}
+89 -39
View File
@@ -1,4 +1,6 @@
use crate::server::Platform;
use crate::{
cfg::ConfigOptsServe,
server::{
output::{print_console_info, PrettierOptions},
setup_file_watcher,
@@ -23,7 +25,14 @@ use plugin::PluginManager;
use super::HotReloadState;
pub async fn startup(config: CrateConfig) -> Result<()> {
pub async fn startup(config: CrateConfig, serve: &ConfigOptsServe) -> Result<()> {
startup_with_platform::<DesktopPlatform>(config, serve).await
}
pub(crate) async fn startup_with_platform<P: Platform + Send + 'static>(
config: CrateConfig,
serve_cfg: &ConfigOptsServe,
) -> Result<()> {
// ctrl-c shutdown checker
let _crate_config = config.clone();
let _ = ctrlc::set_handler(move || {
@@ -45,8 +54,6 @@ pub async fn startup(config: CrateConfig) -> Result<()> {
let hot_reload_tx = broadcast::channel(100).0;
clear_paths();
Some(HotReloadState {
messages: hot_reload_tx.clone(),
file_map: file_map.clone(),
@@ -55,15 +62,18 @@ pub async fn startup(config: CrateConfig) -> Result<()> {
false => None,
};
serve(config, hot_reload_state).await?;
serve::<P>(config, serve_cfg, hot_reload_state).await?;
Ok(())
}
/// Start the server without hot reload
pub async fn serve(config: CrateConfig, hot_reload_state: Option<HotReloadState>) -> Result<()> {
let (child, first_build_result) = start_desktop(&config)?;
let currently_running_child: RwLock<Child> = RwLock::new(child);
async fn serve<P: Platform + Send + 'static>(
config: CrateConfig,
serve: &ConfigOptsServe,
hot_reload_state: Option<HotReloadState>,
) -> Result<()> {
let platform = RwLock::new(P::start(&config, serve)?);
log::info!("🚀 Starting development server...");
@@ -72,14 +82,7 @@ pub async fn serve(config: CrateConfig, hot_reload_state: Option<HotReloadState>
let _watcher = setup_file_watcher(
{
let config = config.clone();
move || {
let mut current_child = currently_running_child.write().unwrap();
current_child.kill()?;
let (child, result) = start_desktop(&config)?;
*current_child = child;
Ok(result)
}
move || platform.write().unwrap().rebuild(&config)
},
&config,
None,
@@ -87,19 +90,9 @@ pub async fn serve(config: CrateConfig, hot_reload_state: Option<HotReloadState>
)
.await?;
// Print serve info
print_console_info(
&config,
PrettierOptions {
changed: vec![],
warnings: first_build_result.warnings,
elapsed_time: first_build_result.elapsed_time,
},
None,
);
match hot_reload_state {
Some(hot_reload_state) => {
// The open interprocess sockets
start_desktop_hot_reload(hot_reload_state).await?;
}
None => {
@@ -111,7 +104,14 @@ pub async fn serve(config: CrateConfig, hot_reload_state: Option<HotReloadState>
}
async fn start_desktop_hot_reload(hot_reload_state: HotReloadState) -> Result<()> {
match LocalSocketListener::bind("@dioxusin") {
let metadata = cargo_metadata::MetadataCommand::new()
.no_deps()
.exec()
.unwrap();
let target_dir = metadata.target_directory.as_std_path();
let path = target_dir.join("dioxusin");
clear_paths(&path);
match LocalSocketListener::bind(path) {
Ok(local_socket_stream) => {
let aborted = Arc::new(Mutex::new(false));
// States
@@ -150,7 +150,11 @@ async fn start_desktop_hot_reload(hot_reload_state: HotReloadState) -> Result<()
println!("Connected to hot reloading 🚀");
}
Err(err) => {
if err.kind() != std::io::ErrorKind::WouldBlock {
let error_string = err.to_string();
// Filter out any error messages about a operation that may block and an error message that triggers on some operating systems that says "Waiting for a process to open the other end of the pipe" without WouldBlock being set
let display_error = err.kind() != std::io::ErrorKind::WouldBlock
&& !error_string.contains("Waiting for a process");
if display_error {
println!("Error connecting to hot reloading: {} (Hot reloading is a feature of the dioxus-cli. If you are not using the CLI, this error can be ignored)", err);
}
}
@@ -183,17 +187,14 @@ async fn start_desktop_hot_reload(hot_reload_state: HotReloadState) -> Result<()
Ok(())
}
fn clear_paths() {
if cfg!(target_os = "macos") {
fn clear_paths(file_socket_path: &std::path::Path) {
if cfg!(unix) {
// On unix, if you force quit the application, it can leave the file socket open
// This will cause the local socket listener to fail to open
// We check if the file socket is already open from an old session and then delete it
let paths = ["./dioxusin", "./@dioxusin"];
for path in paths {
let path = std::path::PathBuf::from(path);
if path.exists() {
let _ = std::fs::remove_file(path);
}
if file_socket_path.exists() {
let _ = std::fs::remove_file(file_socket_path);
}
}
}
@@ -212,9 +213,9 @@ fn send_msg(msg: HotReloadMsg, channel: &mut impl std::io::Write) -> bool {
}
}
pub fn start_desktop(config: &CrateConfig) -> Result<(Child, BuildResult)> {
fn start_desktop(config: &CrateConfig, skip_assets: bool) -> Result<(RAIIChild, BuildResult)> {
// Run the desktop application
let result = crate::builder::build_desktop(config, true)?;
let result = crate::builder::build_desktop(config, true, skip_assets)?;
match &config.executable {
ExecutableType::Binary(name)
@@ -224,9 +225,58 @@ pub fn start_desktop(config: &CrateConfig) -> Result<(Child, BuildResult)> {
if cfg!(windows) {
file.set_extension("exe");
}
let child = Command::new(file.to_str().unwrap()).spawn()?;
let active = "DIOXUS_ACTIVE";
let child = RAIIChild(
Command::new(file.to_str().unwrap())
.env(active, "true")
.spawn()?,
);
Ok((child, result))
}
}
}
pub(crate) struct DesktopPlatform {
currently_running_child: RAIIChild,
skip_assets: bool,
}
impl Platform for DesktopPlatform {
fn start(config: &CrateConfig, serve: &ConfigOptsServe) -> Result<Self> {
let (child, first_build_result) = start_desktop(config, serve.skip_assets)?;
log::info!("🚀 Starting development server...");
// Print serve info
print_console_info(
config,
PrettierOptions {
changed: vec![],
warnings: first_build_result.warnings,
elapsed_time: first_build_result.elapsed_time,
},
None,
);
Ok(Self {
currently_running_child: child,
skip_assets: serve.skip_assets,
})
}
fn rebuild(&mut self, config: &CrateConfig) -> Result<BuildResult> {
self.currently_running_child.0.kill()?;
let (child, result) = start_desktop(config, self.skip_assets)?;
self.currently_running_child = child;
Ok(result)
}
}
struct RAIIChild(Child);
impl Drop for RAIIChild {
fn drop(&mut self) {
let _ = self.0.kill();
}
}
+161
View File
@@ -0,0 +1,161 @@
use crate::{
cfg::{ConfigOptsBuild, ConfigOptsServe},
CrateConfig, Result, WebAssetConfigDropGuard,
};
use super::{desktop, Platform};
pub async fn startup(config: CrateConfig, serve: &ConfigOptsServe) -> Result<()> {
desktop::startup_with_platform::<FullstackPlatform>(config, serve).await
}
fn start_web_build_thread(
config: &CrateConfig,
serve: &ConfigOptsServe,
) -> std::thread::JoinHandle<Result<()>> {
let serve = serve.clone();
let target_directory = config.crate_dir.join(".dioxus").join("web");
std::fs::create_dir_all(&target_directory).unwrap();
std::thread::spawn(move || build_web(serve, &target_directory))
}
struct FullstackPlatform {
serve: ConfigOptsServe,
desktop: desktop::DesktopPlatform,
_config: WebAssetConfigDropGuard,
}
impl Platform for FullstackPlatform {
fn start(config: &CrateConfig, serve: &ConfigOptsServe) -> Result<Self>
where
Self: Sized,
{
let thread_handle = start_web_build_thread(config, serve);
let mut desktop_config = config.clone();
let desktop_feature = serve.server_feature.clone();
let features = &mut desktop_config.features;
match features {
Some(features) => {
features.push(desktop_feature);
}
None => desktop_config.features = Some(vec![desktop_feature]),
};
let config = WebAssetConfigDropGuard::new();
let desktop = desktop::DesktopPlatform::start(&desktop_config, serve)?;
thread_handle
.join()
.map_err(|_| anyhow::anyhow!("Failed to join thread"))??;
Ok(Self {
desktop,
serve: serve.clone(),
_config: config,
})
}
fn rebuild(&mut self, crate_config: &CrateConfig) -> Result<crate::BuildResult> {
let thread_handle = start_web_build_thread(crate_config, &self.serve);
let result = {
let mut desktop_config = crate_config.clone();
let desktop_feature = self.serve.server_feature.clone();
let features = &mut desktop_config.features;
match features {
Some(features) => {
features.push(desktop_feature);
}
None => desktop_config.features = Some(vec![desktop_feature]),
};
let _gaurd = FullstackServerEnvGuard::new(self.serve.force_debug, self.serve.release);
self.desktop.rebuild(&desktop_config)
};
thread_handle
.join()
.map_err(|_| anyhow::anyhow!("Failed to join thread"))??;
result
}
}
fn build_web(serve: ConfigOptsServe, target_directory: &std::path::Path) -> Result<()> {
let mut web_config: ConfigOptsBuild = serve.into();
let web_feature = web_config.client_feature.clone();
let features = &mut web_config.features;
match features {
Some(features) => {
features.push(web_feature);
}
None => web_config.features = Some(vec![web_feature]),
};
web_config.platform = Some(crate::cfg::Platform::Web);
let _gaurd = FullstackWebEnvGuard::new(&web_config);
crate::cli::build::Build { build: web_config }.build(None, Some(target_directory))
}
// Debug mode web builds have a very large size by default. If debug mode is not enabled, we strip some of the debug info by default
// This reduces a hello world from ~40MB to ~2MB
pub(crate) struct FullstackWebEnvGuard {
old_rustflags: Option<String>,
}
impl FullstackWebEnvGuard {
pub fn new(serve: &ConfigOptsBuild) -> Self {
Self {
old_rustflags: (!serve.force_debug).then(|| {
let old_rustflags = std::env::var("RUSTFLAGS").unwrap_or_default();
let debug_assertions = if serve.release {
""
} else {
" -C debug-assertions"
};
std::env::set_var(
"RUSTFLAGS",
format!(
"{old_rustflags} -C debuginfo=none -C strip=debuginfo{debug_assertions}"
),
);
old_rustflags
}),
}
}
}
impl Drop for FullstackWebEnvGuard {
fn drop(&mut self) {
if let Some(old_rustflags) = self.old_rustflags.take() {
std::env::set_var("RUSTFLAGS", old_rustflags);
}
}
}
// Debug mode web builds have a very large size by default. If debug mode is not enabled, we strip some of the debug info by default
// This reduces a hello world from ~40MB to ~2MB
pub(crate) struct FullstackServerEnvGuard {
old_rustflags: Option<String>,
}
impl FullstackServerEnvGuard {
pub fn new(debug: bool, release: bool) -> Self {
Self {
old_rustflags: (!debug).then(|| {
let old_rustflags = std::env::var("RUSTFLAGS").unwrap_or_default();
let debug_assertions = if release { "" } else { " -C debug-assertions" };
std::env::set_var(
"RUSTFLAGS",
format!("{old_rustflags} -C opt-level=2 {debug_assertions}"),
);
old_rustflags
}),
}
}
}
impl Drop for FullstackServerEnvGuard {
fn drop(&mut self) {
if let Some(old_rustflags) = self.old_rustflags.take() {
std::env::set_var("RUSTFLAGS", old_rustflags);
}
}
}
+9
View File
@@ -1,3 +1,4 @@
use crate::{cfg::ConfigOptsServe, BuildResult, Result};
use crate::{BuildResult, Result};
use dioxus_cli_config::CrateConfig;
@@ -12,6 +13,7 @@ use tokio::sync::broadcast::{self};
mod output;
use output::*;
pub mod desktop;
pub mod fullstack;
pub mod web;
/// Sets up a file watcher
@@ -133,6 +135,13 @@ async fn setup_file_watcher<F: Fn() -> Result<BuildResult> + Send + 'static>(
Ok(watcher)
}
pub(crate) trait Platform {
fn start(config: &CrateConfig, serve: &ConfigOptsServe) -> Result<Self>
where
Self: Sized;
fn rebuild(&mut self, config: &CrateConfig) -> Result<BuildResult>;
}
#[derive(Clone)]
pub struct HotReloadState {
pub messages: broadcast::Sender<Template<'static>>,
+14 -11
View File
@@ -23,17 +23,20 @@ pub fn print_console_info(
options: PrettierOptions,
web_info: Option<WebServerInfo>,
) {
if let Ok(native_clearseq) = Command::new(if cfg!(target_os = "windows") {
"cls"
} else {
"clear"
})
.output()
{
print!("{}", String::from_utf8_lossy(&native_clearseq.stdout));
} else {
// Try ANSI-Escape characters
print!("\x1b[2J\x1b[H");
// Don't clear the screen if the user has set the DIOXUS_LOG environment variable to "trace" so that we can see the logs
if Some("trace") != std::env::var("DIOXUS_LOG").ok().as_deref() {
if let Ok(native_clearseq) = Command::new(if cfg!(target_os = "windows") {
"cls"
} else {
"clear"
})
.output()
{
print!("{}", String::from_utf8_lossy(&native_clearseq.stdout));
} else {
// Try ANSI-Escape characters
print!("\x1b[2J\x1b[H");
}
}
let mut profile = if config.release { "Release" } else { "Debug" }.to_string();
+30 -8
View File
@@ -11,6 +11,7 @@ use axum::{
body::{Full, HttpBody},
extract::{ws::Message, Extension, TypedHeader, WebSocketUpgrade},
http::{
self,
header::{HeaderName, HeaderValue},
Method, Response, StatusCode,
},
@@ -49,7 +50,12 @@ struct WsReloadState {
update: broadcast::Sender<()>,
}
pub async fn startup(port: u16, config: CrateConfig, start_browser: bool) -> Result<()> {
pub async fn startup(
port: u16,
config: CrateConfig,
start_browser: bool,
skip_assets: bool,
) -> Result<()> {
// ctrl-c shutdown checker
let _crate_config = config.clone();
let _ = ctrlc::set_handler(move || {
@@ -81,7 +87,15 @@ pub async fn startup(port: u16, config: CrateConfig, start_browser: bool) -> Res
false => None,
};
serve(ip, port, config, start_browser, hot_reload_state).await?;
serve(
ip,
port,
config,
start_browser,
skip_assets,
hot_reload_state,
)
.await?;
Ok(())
}
@@ -92,9 +106,10 @@ pub async fn serve(
port: u16,
config: CrateConfig,
start_browser: bool,
skip_assets: bool,
hot_reload_state: Option<HotReloadState>,
) -> Result<()> {
let first_build_result = crate::builder::build(&config, true)?;
let first_build_result = crate::builder::build(&config, false, skip_assets)?;
log::info!("🚀 Starting development server...");
@@ -107,7 +122,7 @@ pub async fn serve(
{
let config = config.clone();
let reload_tx = reload_tx.clone();
move || build(&config, &reload_tx)
move || build(&config, &reload_tx, skip_assets)
},
&config,
Some(WebServerInfo {
@@ -264,7 +279,7 @@ async fn setup_router(
.override_response_header(HeaderName::from_static("cross-origin-opener-policy"), coop)
.and_then(
move |response: Response<ServeFileSystemResponseBody>| async move {
let response = if file_service_config.dioxus_config.web.watcher.index_on_404
let mut response = if file_service_config.dioxus_config.web.watcher.index_on_404
&& response.status() == StatusCode::NOT_FOUND
{
let body = Full::from(
@@ -287,6 +302,13 @@ async fn setup_router(
} else {
response.map(|body| body.boxed())
};
let headers = response.headers_mut();
headers.insert(
http::header::CACHE_CONTROL,
HeaderValue::from_static("no-cache"),
);
headers.insert(http::header::PRAGMA, HeaderValue::from_static("no-cache"));
headers.insert(http::header::EXPIRES, HeaderValue::from_static("0"));
Ok(response)
},
)
@@ -421,12 +443,12 @@ async fn ws_handler(
})
}
fn build(config: &CrateConfig, reload_tx: &Sender<()>) -> Result<BuildResult> {
let result = builder::build(config, true)?;
fn build(config: &CrateConfig, reload_tx: &Sender<()>, skip_assets: bool) -> Result<BuildResult> {
let result = builder::build(config, true, skip_assets)?;
// change the websocket reload state to true;
// the page will auto-reload.
if config.dioxus_config.web.watcher.reload_html {
let _ = Serve::regen_dev_page(config);
let _ = Serve::regen_dev_page(config, skip_assets);
}
let _ = reload_tx.send(());
Ok(result)