Merge pull request #7 from mrxiaozhuox/master
Complete basic version development
This commit is contained in:
32 files changed
+876
-530
No files matched your search
+11
-5
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "dioxus-cli"
|
name = "dioxus-cli"
|
||||||
version = "0.1.1"
|
version = "0.1.2"
|
||||||
authors = ["Jonathan Kelley"]
|
authors = ["Jonathan Kelley"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "CLI tool for developing, testing, and publishing Dioxus apps"
|
description = "CLI tool for developing, testing, and publishing Dioxus apps"
|
||||||
@@ -12,23 +12,29 @@ license = "MIT/Apache-2.0"
|
|||||||
thiserror = "1.0.23"
|
thiserror = "1.0.23"
|
||||||
log = "0.4.13"
|
log = "0.4.13"
|
||||||
fern = { version = "0.6.0", features = ["colored"] }
|
fern = { version = "0.6.0", features = ["colored"] }
|
||||||
wasm-bindgen-cli-support = "0.2.78"
|
wasm-bindgen-cli-support = "0.2.79"
|
||||||
anyhow = "1.0.38"
|
anyhow = "1.0.38"
|
||||||
serde = { version = "1.0.133", features = ["derive"] }
|
serde = { version = "1.0.133", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
toml = "0.5.8"
|
||||||
fs_extra = "1.2.0"
|
fs_extra = "1.2.0"
|
||||||
cargo_toml = "0.10.0"
|
cargo_toml = "0.10.0"
|
||||||
futures = "0.3.12"
|
futures = "0.3.12"
|
||||||
notify = "4.0.17"
|
notify = { version = "5.0.0-pre.13", features = ["serde"] }
|
||||||
html_parser = "0.6.2"
|
html_parser = "0.6.2"
|
||||||
tui = { version = "0.16.0", default-features = false, features = ["crossterm"] }
|
|
||||||
crossterm = "0.22.1"
|
|
||||||
binary-install = "0.0.2"
|
binary-install = "0.0.2"
|
||||||
convert_case = "0.5.0"
|
convert_case = "0.5.0"
|
||||||
structopt = "0.3.25"
|
structopt = "0.3.25"
|
||||||
cargo_metadata = "0.14.1"
|
cargo_metadata = "0.14.1"
|
||||||
tokio = { version = "1.15.0", features = ["full"] }
|
tokio = { version = "1.15.0", features = ["full"] }
|
||||||
atty = "0.2.14"
|
atty = "0.2.14"
|
||||||
|
regex = "1.5.4"
|
||||||
|
chrono = "0.4.19"
|
||||||
|
|
||||||
|
axum = { version = "0.4.4", features = ["ws", "headers"] }
|
||||||
|
tower-http = { version = "0.2.0", features = ["fs", "trace"] }
|
||||||
|
headers = "0.3"
|
||||||
|
# hyper = { version = "0.14.11", features = ["full"] }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
# About
|
# About
|
||||||
|
|
||||||
dioxus-studio (inspired by wasm-pack and webpack) is a tool to help get dioxus projects off the ground. It handles all the build, development, bundling, and publishing to make web development simple.
|
dioxus-cli (inspired by wasm-pack and webpack) is a tool to help get dioxus projects off the ground. It handles all the build, development, bundling, and publishing to make web development simple.
|
||||||
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
@@ -19,5 +19,4 @@ dioxus-studio (inspired by wasm-pack and webpack) is a tool to help get dioxus p
|
|||||||
$ cargo install dioxus-cli
|
$ cargo install dioxus-cli
|
||||||
```
|
```
|
||||||
|
|
||||||
Now, `dioxus` is in your path.
|
Now, `dioxus` is in your path.
|
||||||
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "rsx-translate"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
#[test]
|
|
||||||
fn it_works() {
|
|
||||||
let result = 2 + 2;
|
|
||||||
assert_eq!(result, 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
(function () {
|
||||||
|
var protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||||
|
var url = protocol + '//' + window.location.host + '/_dioxus/ws';
|
||||||
|
var poll_interval = 8080;
|
||||||
|
var reload_upon_connect = () => {
|
||||||
|
window.setTimeout(
|
||||||
|
() => {
|
||||||
|
var ws = new WebSocket(url);
|
||||||
|
ws.onopen = () => window.location.reload();
|
||||||
|
ws.onclose = reload_upon_connect;
|
||||||
|
},
|
||||||
|
poll_interval);
|
||||||
|
};
|
||||||
|
|
||||||
|
var ws = new WebSocket(url);
|
||||||
|
ws.onmessage = (ev) => {
|
||||||
|
if (ev.data == "reload") {
|
||||||
|
// alert("reload!!!");
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ws.onclose = reload_upon_connect;
|
||||||
|
})()
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
[application]
|
||||||
|
|
||||||
|
# App (Project) Name
|
||||||
|
name = "{{project-name}}"
|
||||||
|
|
||||||
|
# Dioxus App Support Platform
|
||||||
|
# desktop, web, mobile, ssr
|
||||||
|
platforms = ["web"]
|
||||||
|
|
||||||
|
# Web `build` & `serve` dist path
|
||||||
|
out_dir = "dist"
|
||||||
|
|
||||||
|
[web.app]
|
||||||
|
|
||||||
|
# HTML title tag content
|
||||||
|
title = "dioxus | ⛺"
|
||||||
|
|
||||||
|
# resource (static) file folder
|
||||||
|
public_dir = "public"
|
||||||
|
|
||||||
|
[web.watcher]
|
||||||
|
|
||||||
|
watch_path = ["src"]
|
||||||
|
|
||||||
|
# include `assets` in web platform
|
||||||
|
[web.resource]
|
||||||
|
|
||||||
|
# CSS style file
|
||||||
|
style = []
|
||||||
|
|
||||||
|
# Javascript code file
|
||||||
|
script = []
|
||||||
|
|
||||||
|
[web.resource.dev]
|
||||||
|
|
||||||
|
# Javascript code file
|
||||||
|
# serve: [dev-server] only
|
||||||
|
script = []
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>{app_title}</title>
|
||||||
|
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
{style_include}
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="main"></div>
|
||||||
|
<!-- Note the usage of `type=module` here as this is an ES6 module -->
|
||||||
|
<script type="module">
|
||||||
|
import init from "./assets/dioxus/{app_name}.js";
|
||||||
|
init("./assets/dioxus/{app_name}_bg.wasm");
|
||||||
|
</script>
|
||||||
|
{script_include}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+165
-51
@@ -1,9 +1,11 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
config::{CrateConfig, ExecutableType},
|
config::{CrateConfig, ExecutableType},
|
||||||
error::{Error, Result},
|
error::{Error, Result},
|
||||||
|
DioxusConfig,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
io::{Read, Write},
|
fs::{copy, create_dir_all, remove_dir_all},
|
||||||
|
path::PathBuf,
|
||||||
process::Command,
|
process::Command,
|
||||||
};
|
};
|
||||||
use wasm_bindgen_cli_support::Bindgen;
|
use wasm_bindgen_cli_support::Bindgen;
|
||||||
@@ -21,22 +23,23 @@ pub fn build(config: &CrateConfig) -> Result<()> {
|
|||||||
out_dir,
|
out_dir,
|
||||||
crate_dir,
|
crate_dir,
|
||||||
target_dir,
|
target_dir,
|
||||||
static_dir,
|
public_dir,
|
||||||
executable,
|
executable,
|
||||||
|
dioxus_config,
|
||||||
..
|
..
|
||||||
} = config;
|
} = config;
|
||||||
|
|
||||||
let t_start = std::time::Instant::now();
|
let t_start = std::time::Instant::now();
|
||||||
|
|
||||||
// [1] Build the .wasm module
|
// [1] Build the .wasm module
|
||||||
log::info!("Running build commands...");
|
log::info!("🚅 Running build command...");
|
||||||
let mut cmd = Command::new("cargo");
|
let mut cmd = Command::new("cargo");
|
||||||
cmd.current_dir(&crate_dir)
|
cmd.current_dir(&crate_dir)
|
||||||
.arg("build")
|
.arg("build")
|
||||||
.arg("--target")
|
.arg("--target")
|
||||||
.arg("wasm32-unknown-unknown")
|
.arg("wasm32-unknown-unknown")
|
||||||
.stdout(std::process::Stdio::piped())
|
.stdout(std::process::Stdio::inherit())
|
||||||
.stderr(std::process::Stdio::piped());
|
.stderr(std::process::Stdio::inherit());
|
||||||
|
|
||||||
if config.release {
|
if config.release {
|
||||||
cmd.arg("--release");
|
cmd.arg("--release");
|
||||||
@@ -48,20 +51,16 @@ pub fn build(config: &CrateConfig) -> Result<()> {
|
|||||||
ExecutableType::Example(name) => cmd.arg("--example").arg(name),
|
ExecutableType::Example(name) => cmd.arg("--example").arg(name),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut child = cmd.spawn()?;
|
let output = cmd.output()?;
|
||||||
let output = child.wait()?;
|
|
||||||
|
|
||||||
if output.success() {
|
if !output.status.success() {
|
||||||
log::info!("Build complete!");
|
|
||||||
} else {
|
|
||||||
log::error!("Build failed!");
|
log::error!("Build failed!");
|
||||||
let mut reason = String::new();
|
let reason = String::from_utf8_lossy(&output.stderr).to_string();
|
||||||
child.stderr.unwrap().read_to_string(&mut reason)?;
|
|
||||||
return Err(Error::BuildFailed(reason));
|
return Err(Error::BuildFailed(reason));
|
||||||
}
|
}
|
||||||
|
|
||||||
// [2] Establish the output directory structure
|
// [2] Establish the output directory structure
|
||||||
let bindgen_outdir = out_dir.join("wasm");
|
let bindgen_outdir = out_dir.join("assets").join("dioxus");
|
||||||
|
|
||||||
// [3] Bindgen the final binary for use easy linking
|
// [3] Bindgen the final binary for use easy linking
|
||||||
let mut bindgen_builder = Bindgen::new();
|
let mut bindgen_builder = Bindgen::new();
|
||||||
@@ -89,53 +88,168 @@ pub fn build(config: &CrateConfig) -> Result<()> {
|
|||||||
.keep_debug(true)
|
.keep_debug(true)
|
||||||
.remove_name_section(false)
|
.remove_name_section(false)
|
||||||
.remove_producers_section(false)
|
.remove_producers_section(false)
|
||||||
.out_name("module")
|
.out_name(&dioxus_config.application.name)
|
||||||
.generate(&bindgen_outdir)?;
|
.generate(&bindgen_outdir)?;
|
||||||
|
|
||||||
// [4]
|
let copy_options = fs_extra::dir::CopyOptions {
|
||||||
// TODO: wasm-opt
|
overwrite: true,
|
||||||
|
skip_exist: false,
|
||||||
// [5] Generate the html file with the module name
|
buffer_size: 64000,
|
||||||
// TODO: support names via options
|
copy_inside: false,
|
||||||
log::info!("Writing to '{:#?}' directory...", out_dir);
|
content_only: false,
|
||||||
let mut file = std::fs::File::create(out_dir.join("index.html"))?;
|
depth: 0,
|
||||||
file.write_all(gen_page("./wasm/module.js").as_str().as_bytes())?;
|
};
|
||||||
|
if public_dir.is_dir() {
|
||||||
let copy_options = fs_extra::dir::CopyOptions::new();
|
for entry in std::fs::read_dir(&public_dir)? {
|
||||||
match fs_extra::dir::copy(static_dir, out_dir, ©_options) {
|
let path = entry?.path();
|
||||||
Ok(_) => {}
|
if path.is_file() {
|
||||||
Err(_e) => {
|
std::fs::copy(&path, out_dir.join(path.file_name().unwrap()))?;
|
||||||
log::warn!("Error copying dir");
|
} else {
|
||||||
|
match fs_extra::dir::copy(&path, out_dir, ©_options) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(_e) => {
|
||||||
|
log::warn!("Error copying dir: {}", _e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let t_end = std::time::Instant::now();
|
let t_end = std::time::Instant::now();
|
||||||
log::info!("Done in {}ms! 🎉", (t_end - t_start).as_millis());
|
log::info!("🏁 Done in {}ms!", (t_end - t_start).as_millis());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gen_page(module: &str) -> String {
|
pub fn build_desktop(config: &CrateConfig) -> Result<()> {
|
||||||
format!(
|
log::info!("🚅 Running build [Desktop] command...");
|
||||||
r#"
|
|
||||||
<html>
|
let mut cmd = Command::new("cargo");
|
||||||
<head>
|
cmd.current_dir(&config.crate_dir)
|
||||||
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
|
.arg("build")
|
||||||
<meta charset="UTF-8" />
|
.stdout(std::process::Stdio::inherit())
|
||||||
</head>
|
.stderr(std::process::Stdio::inherit());
|
||||||
<body>
|
|
||||||
<div id="main">
|
if config.release {
|
||||||
</div>
|
cmd.arg("--release");
|
||||||
<!-- Note the usage of `type=module` here as this is an ES6 module -->
|
}
|
||||||
<script type="module">
|
|
||||||
import init from "{}";
|
match &config.executable {
|
||||||
init("./wasm/module_bg.wasm");
|
crate::ExecutableType::Binary(name) => cmd.arg("--bin").arg(name),
|
||||||
</script>
|
crate::ExecutableType::Lib(name) => cmd.arg("--lib").arg(name),
|
||||||
<div id="dioxusroot"> </div>
|
crate::ExecutableType::Example(name) => cmd.arg("--example").arg(name),
|
||||||
</body>
|
};
|
||||||
</html>
|
|
||||||
"#,
|
let output = cmd.output()?;
|
||||||
module
|
|
||||||
)
|
if !output.status.success() {
|
||||||
|
return Err(Error::BuildFailed("Program build failed.".into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if output.status.success() {
|
||||||
|
if config.out_dir.is_dir() {
|
||||||
|
remove_dir_all(&config.out_dir)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let release_type = match config.release {
|
||||||
|
true => "release",
|
||||||
|
false => "debug",
|
||||||
|
};
|
||||||
|
|
||||||
|
let file_name: String;
|
||||||
|
let mut res_path = match &config.executable {
|
||||||
|
crate::ExecutableType::Binary(name) | crate::ExecutableType::Lib(name) => {
|
||||||
|
file_name = name.clone();
|
||||||
|
config
|
||||||
|
.target_dir
|
||||||
|
.join(release_type.to_string())
|
||||||
|
.join(name.to_string())
|
||||||
|
}
|
||||||
|
crate::ExecutableType::Example(name) => {
|
||||||
|
file_name = name.clone();
|
||||||
|
config
|
||||||
|
.target_dir
|
||||||
|
.join(release_type.to_string())
|
||||||
|
.join("examples")
|
||||||
|
.join(name.to_string())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let target_file = if cfg!(windows) {
|
||||||
|
res_path.set_extension("exe");
|
||||||
|
format!("{}.exe", &file_name)
|
||||||
|
} else {
|
||||||
|
file_name
|
||||||
|
};
|
||||||
|
|
||||||
|
create_dir_all(&config.out_dir)?;
|
||||||
|
copy(res_path, &config.out_dir.join(target_file))?;
|
||||||
|
|
||||||
|
log::info!(
|
||||||
|
"🚩 Build completed: [./{}]",
|
||||||
|
config
|
||||||
|
.dioxus_config
|
||||||
|
.application
|
||||||
|
.out_dir
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| PathBuf::from("dist"))
|
||||||
|
.display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gen_page(config: &DioxusConfig, serve: bool) -> String {
|
||||||
|
let mut html = String::from(include_str!("./assets/index.html"));
|
||||||
|
|
||||||
|
let resouces = config.web.resource.clone();
|
||||||
|
|
||||||
|
let mut style_list = resouces.style.unwrap_or_default();
|
||||||
|
let mut script_list = resouces.script.unwrap_or_default();
|
||||||
|
|
||||||
|
if serve {
|
||||||
|
let mut dev_style = resouces.dev.style.clone().unwrap_or_default();
|
||||||
|
let mut dev_script = resouces.dev.script.unwrap_or_default();
|
||||||
|
style_list.append(&mut dev_style);
|
||||||
|
script_list.append(&mut dev_script);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut style_str = String::new();
|
||||||
|
for style in style_list {
|
||||||
|
style_str.push_str(&format!(
|
||||||
|
"<link rel=\"stylesheet\" href=\"{}\">\n",
|
||||||
|
&style.to_str().unwrap(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
html = html.replace("{style_include}", &style_str);
|
||||||
|
|
||||||
|
let mut script_str = String::new();
|
||||||
|
for script in script_list {
|
||||||
|
script_str.push_str(&format!(
|
||||||
|
"<script src=\"{}\"></script>\n",
|
||||||
|
&script.to_str().unwrap(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
html = html.replace("{script_include}", &script_str);
|
||||||
|
|
||||||
|
if serve {
|
||||||
|
html += &format!(
|
||||||
|
"<script>{}</script>",
|
||||||
|
include_str!("./assets/autoreload.js")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
html = html.replace("{app_name}", &config.application.name);
|
||||||
|
|
||||||
|
let title = config
|
||||||
|
.web
|
||||||
|
.app
|
||||||
|
.title
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| "dioxus | ⛺".into());
|
||||||
|
|
||||||
|
html.replace("{app_title}", &title)
|
||||||
}
|
}
|
||||||
|
|
||||||
// use binary_install::{Cache, Download};
|
// use binary_install::{Cache, Download};
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
todo: wasm splitting
|
|
||||||
*/
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
+8
-3
@@ -1,6 +1,11 @@
|
|||||||
//! Utilities for working with cargo and rust files
|
//! Utilities for working with cargo and rust files
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use std::{env, fs, path::PathBuf, process::Command, str};
|
use std::{
|
||||||
|
env, fs,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
process::Command,
|
||||||
|
str,
|
||||||
|
};
|
||||||
|
|
||||||
/// How many parent folders are searched for a `Cargo.toml`
|
/// How many parent folders are searched for a `Cargo.toml`
|
||||||
const MAX_ANCESTORS: u32 = 10;
|
const MAX_ANCESTORS: u32 = 10;
|
||||||
@@ -44,7 +49,7 @@ pub fn workspace_root() -> Result<PathBuf> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let stdout = str::from_utf8(&output.stdout).unwrap();
|
let stdout = str::from_utf8(&output.stdout).unwrap();
|
||||||
for line in stdout.lines() {
|
if let Some(line) = stdout.lines().next() {
|
||||||
let meta: serde_json::Value = serde_json::from_str(line)
|
let meta: serde_json::Value = serde_json::from_str(line)
|
||||||
.map_err(|_| Error::CargoError("InvalidOutput".to_string()))?;
|
.map_err(|_| Error::CargoError("InvalidOutput".to_string()))?;
|
||||||
|
|
||||||
@@ -58,7 +63,7 @@ pub fn workspace_root() -> Result<PathBuf> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if the directory contains `Cargo.toml`
|
/// Checks if the directory contains `Cargo.toml`
|
||||||
fn contains_manifest(path: &PathBuf) -> bool {
|
fn contains_manifest(path: &Path) -> bool {
|
||||||
fs::read_dir(path)
|
fs::read_dir(path)
|
||||||
.map(|entries| {
|
.map(|entries| {
|
||||||
entries
|
entries
|
||||||
|
|||||||
+46
-4
@@ -1,6 +1,6 @@
|
|||||||
use crate::cfg::ConfigOptsBuild;
|
use std::{io::Write, path::PathBuf};
|
||||||
use anyhow::Result;
|
|
||||||
use std::path::PathBuf;
|
use crate::{cfg::ConfigOptsBuild, gen_page};
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
/// Build the Rust WASM app and all of its assets.
|
/// Build the Rust WASM app and all of its assets.
|
||||||
@@ -11,4 +11,46 @@ pub struct Build {
|
|||||||
pub build: ConfigOptsBuild,
|
pub build: ConfigOptsBuild,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn perform_build() {}
|
impl Build {
|
||||||
|
pub fn build(self) -> anyhow::Result<()> {
|
||||||
|
let mut crate_config = crate::CrateConfig::new()?;
|
||||||
|
|
||||||
|
// change the relase state.
|
||||||
|
crate_config.with_release(self.build.release);
|
||||||
|
|
||||||
|
if self.build.example.is_some() {
|
||||||
|
crate_config.as_example(self.build.example.unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.build.platform.as_str() {
|
||||||
|
"web" => {
|
||||||
|
crate::builder::build(&crate_config)?;
|
||||||
|
}
|
||||||
|
"desktop" => {
|
||||||
|
crate::builder::build_desktop(&crate_config)?;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(anyhow::anyhow!("Unsoppurt platform target."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let temp = gen_page(&crate_config.dioxus_config, false);
|
||||||
|
|
||||||
|
let mut file = std::fs::File::create(
|
||||||
|
crate_config
|
||||||
|
.crate_dir
|
||||||
|
.join(
|
||||||
|
crate_config
|
||||||
|
.dioxus_config
|
||||||
|
.application
|
||||||
|
.out_dir
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| PathBuf::from("dist")),
|
||||||
|
)
|
||||||
|
.join("index.html"),
|
||||||
|
)?;
|
||||||
|
file.write_all(temp.as_bytes())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
+24
-47
@@ -1,11 +1,7 @@
|
|||||||
use anyhow::Result;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::Deserialize;
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::str::FromStr;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
/// Config options for the build system.
|
/// Config options for the build system.
|
||||||
#[derive(Clone, Debug, Default, Deserialize, StructOpt)]
|
#[derive(Clone, Debug, Default, Deserialize, StructOpt)]
|
||||||
@@ -19,52 +15,33 @@ pub struct ConfigOptsBuild {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub release: bool,
|
pub release: bool,
|
||||||
|
|
||||||
/// The output dir for all final assets [default: dist]
|
/// Build a example [default: ""]
|
||||||
#[structopt(short, long, parse(from_os_str))]
|
#[structopt(long)]
|
||||||
pub dist: Option<PathBuf>,
|
pub example: Option<String>,
|
||||||
|
|
||||||
/// The public URL from which assets are to be served [default: /]
|
/// Build platform: support Web & Desktop [default: "web"]
|
||||||
#[structopt(long, parse(from_str=parse_public_url))]
|
#[structopt(long, default_value = "web")]
|
||||||
pub public_url: Option<String>,
|
pub platform: String,
|
||||||
|
}
|
||||||
|
|
||||||
/// Optional pattern for the app loader script [default: None]
|
#[derive(Clone, Debug, Default, Deserialize, StructOpt)]
|
||||||
///
|
pub struct ConfigOptsServe {
|
||||||
/// Patterns should include the sequences `{base}`, `{wasm}`, and `{js}` in order to
|
/// The index HTML file to drive the bundling process [default: index.html]
|
||||||
/// properly load the application. Other sequences may be included corresponding
|
#[structopt(parse(from_os_str))]
|
||||||
/// to key/value pairs provided in `pattern_params`.
|
pub target: Option<PathBuf>,
|
||||||
///
|
|
||||||
/// These values can only be provided via config file.
|
/// Build a example [default: ""]
|
||||||
#[structopt(skip)]
|
#[structopt(long)]
|
||||||
|
pub example: Option<String>,
|
||||||
|
|
||||||
|
/// Build in release mode [default: false]
|
||||||
|
#[structopt(long)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub pattern_script: Option<String>,
|
pub release: bool,
|
||||||
|
|
||||||
/// Optional pattern for the app preload element [default: None]
|
/// Build platform: support Web & Desktop [default: "web"]
|
||||||
///
|
#[structopt(long, default_value = "web")]
|
||||||
/// Patterns should include the sequences `{base}`, `{wasm}`, and `{js}` in order to
|
pub platform: String,
|
||||||
/// properly preload the application. Other sequences may be included corresponding
|
|
||||||
/// to key/value pairs provided in `pattern_params`.
|
|
||||||
///
|
|
||||||
/// These values can only be provided via config file.
|
|
||||||
#[structopt(skip)]
|
|
||||||
#[serde(default)]
|
|
||||||
pub pattern_preload: Option<String>,
|
|
||||||
|
|
||||||
#[structopt(skip)]
|
|
||||||
#[serde(default)]
|
|
||||||
/// Optional replacement parameters corresponding to the patterns provided in
|
|
||||||
/// `pattern_script` and `pattern_preload`.
|
|
||||||
///
|
|
||||||
/// When a pattern is being replaced with its corresponding value from this map, if the value is
|
|
||||||
/// prefixed with the symbol `@`, then the value is expected to be a file path, and the pattern
|
|
||||||
/// will be replaced with the contents of the target file. This allows insertion of some big JSON
|
|
||||||
/// state or even HTML files as a part of the `index.html` build.
|
|
||||||
///
|
|
||||||
/// Trunk will automatically insert the `base`, `wasm` and `js` key/values into this map. In order
|
|
||||||
//// for the app to be loaded properly, the patterns `{base}`, `{wasm}` and `{js}` should be used
|
|
||||||
/// in `pattern_script` and `pattern_preload`.
|
|
||||||
///
|
|
||||||
/// These values can only be provided via config file.
|
|
||||||
pub pattern_params: Option<HashMap<String, String>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ensure the given value for `--public-url` is formatted correctly.
|
/// Ensure the given value for `--public-url` is formatted correctly.
|
||||||
|
|||||||
+34
-3
@@ -1,9 +1,40 @@
|
|||||||
use crate::cfg::ConfigOptsBuild;
|
use std::{
|
||||||
use anyhow::Result;
|
fs::remove_dir_all,
|
||||||
use std::path::PathBuf;
|
path::PathBuf,
|
||||||
|
process::{Command, Stdio},
|
||||||
|
};
|
||||||
|
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
/// Build the Rust WASM app and all of its assets.
|
/// Build the Rust WASM app and all of its assets.
|
||||||
#[derive(Clone, Debug, StructOpt)]
|
#[derive(Clone, Debug, StructOpt)]
|
||||||
#[structopt(name = "clean")]
|
#[structopt(name = "clean")]
|
||||||
pub struct Clean {}
|
pub struct Clean {}
|
||||||
|
|
||||||
|
impl Clean {
|
||||||
|
pub fn clean(self) -> anyhow::Result<()> {
|
||||||
|
let crate_config = crate::CrateConfig::new()?;
|
||||||
|
|
||||||
|
let output = Command::new("cargo")
|
||||||
|
.arg("clean")
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
log::error!("Cargo clean failed.");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let out_dir = crate_config
|
||||||
|
.dioxus_config
|
||||||
|
.application
|
||||||
|
.out_dir
|
||||||
|
.unwrap_or_else(|| PathBuf::from("dist"));
|
||||||
|
if crate_config.crate_dir.join(&out_dir).is_dir() {
|
||||||
|
remove_dir_all(crate_config.crate_dir.join(&out_dir))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
+39
-5
@@ -1,9 +1,43 @@
|
|||||||
use crate::cfg::ConfigOptsBuild;
|
use std::{fs::File, io::Write};
|
||||||
use anyhow::Result;
|
|
||||||
use std::path::PathBuf;
|
use serde::Deserialize;
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
/// Build the Rust WASM app and all of its assets.
|
/// Build the Rust WASM app and all of its assets.
|
||||||
#[derive(Clone, Debug, StructOpt)]
|
#[derive(Clone, Debug, Deserialize, StructOpt)]
|
||||||
#[structopt(name = "config")]
|
#[structopt(name = "config")]
|
||||||
pub struct Config {}
|
pub enum Config {
|
||||||
|
/// Init `Dioxus.toml` for project/folder.
|
||||||
|
Init {
|
||||||
|
/// Init project name
|
||||||
|
name: String,
|
||||||
|
|
||||||
|
/// Cover old config
|
||||||
|
#[structopt(long)]
|
||||||
|
#[serde(default)]
|
||||||
|
force: bool,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn config(self) -> anyhow::Result<()> {
|
||||||
|
let crate_root = crate::cargo::crate_root()?;
|
||||||
|
match self {
|
||||||
|
Config::Init { name, force } => {
|
||||||
|
let conf_path = crate_root.join("Dioxus.toml");
|
||||||
|
if conf_path.is_file() && !force {
|
||||||
|
log::warn!(
|
||||||
|
"config file `Dioxus.toml` already exist, use `--force` to overwrite it."
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let mut file = File::create(conf_path)?;
|
||||||
|
let content = String::from(include_str!("../../assets/dioxus.toml"))
|
||||||
|
.replace("{{project-name}}", &name);
|
||||||
|
file.write_all(content.as_bytes())?;
|
||||||
|
log::info!("🚩 Init config file completed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
io::{Read, Write},
|
||||||
|
path::PathBuf,
|
||||||
|
process::{Command, Stdio},
|
||||||
|
};
|
||||||
|
|
||||||
|
use regex::Regex;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
use crate::{error::Result, Error};
|
||||||
|
|
||||||
|
/// Build the Rust WASM app and all of its assets.
|
||||||
|
#[derive(Clone, Debug, Default, Deserialize, StructOpt)]
|
||||||
|
#[structopt(name = "create")]
|
||||||
|
pub struct Create {
|
||||||
|
/// Init project name
|
||||||
|
#[structopt(default_value = ".")]
|
||||||
|
name: String,
|
||||||
|
|
||||||
|
/// Template path
|
||||||
|
#[structopt(default_value = "gh:dioxuslabs/dioxus-template", long)]
|
||||||
|
template: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Create {
|
||||||
|
pub fn create(self) -> Result<()> {
|
||||||
|
if Self::name_vaild_check(self.name.clone()) {
|
||||||
|
log::error!("❗Unsupported project name.");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let project_path = PathBuf::from(&self.name);
|
||||||
|
|
||||||
|
if project_path.join("Dioxus.toml").is_file() || project_path.join("Cargo.toml").is_file() {
|
||||||
|
return Err(Error::Other(anyhow::anyhow!(
|
||||||
|
"🧨 Folder '{}' is initialized.",
|
||||||
|
&self.name
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!("🔧 Start to create a new project '{}'.", self.name);
|
||||||
|
|
||||||
|
let output = Command::new("cargo")
|
||||||
|
.arg("generate")
|
||||||
|
.arg("--help")
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
log::warn!("Tool is not installed: cargo-generate, try to install it.");
|
||||||
|
let install_output = Command::new("cargo")
|
||||||
|
.arg("install")
|
||||||
|
.arg("cargo-generate")
|
||||||
|
.stdout(Stdio::inherit())
|
||||||
|
.stderr(Stdio::inherit())
|
||||||
|
.output()?;
|
||||||
|
if !install_output.status.success() {
|
||||||
|
return Err(Error::Other(anyhow::anyhow!(
|
||||||
|
"Try to install cargo-generate failed."
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let generate_output = Command::new("cargo")
|
||||||
|
.arg("generate")
|
||||||
|
.arg(&self.template)
|
||||||
|
.arg("--name")
|
||||||
|
.arg(&self.name)
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::inherit())
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
if !generate_output.status.success() {
|
||||||
|
return Err(Error::Other(anyhow::anyhow!("Generate project failed.")));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut dioxus_file = File::open(project_path.join("Dioxus.toml"))?;
|
||||||
|
let mut meta_file = String::new();
|
||||||
|
dioxus_file.read_to_string(&mut meta_file)?;
|
||||||
|
meta_file = meta_file.replace("{{project-name}}", &self.name);
|
||||||
|
File::create(project_path.join("Dioxus.toml"))?.write_all(meta_file.as_bytes())?;
|
||||||
|
|
||||||
|
println!();
|
||||||
|
log::info!("💡 Project initialized:");
|
||||||
|
log::info!("🎯> cd ./{}", self.name);
|
||||||
|
log::info!("🎯> dioxus serve");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name_vaild_check(name: String) -> bool {
|
||||||
|
let r = Regex::new(r"^[a-zA-Z][a-zA-Z0-9\-_]$").unwrap();
|
||||||
|
r.is_match(&name)
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
-13
@@ -1,14 +1,12 @@
|
|||||||
use anyhow::{Context, Result};
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
pub mod build;
|
pub mod build;
|
||||||
pub mod cfg;
|
pub mod cfg;
|
||||||
pub mod clean;
|
pub mod clean;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
pub mod create;
|
||||||
pub mod serve;
|
pub mod serve;
|
||||||
pub mod translate;
|
pub mod translate;
|
||||||
pub mod watch;
|
|
||||||
|
|
||||||
/// Build, bundle, & ship your Dioxus app.
|
/// Build, bundle, & ship your Dioxus app.
|
||||||
///
|
///
|
||||||
@@ -31,16 +29,16 @@ pub struct Cli {
|
|||||||
|
|
||||||
#[derive(StructOpt)]
|
#[derive(StructOpt)]
|
||||||
pub enum Commands {
|
pub enum Commands {
|
||||||
// /// Build the Rust WASM app and all of its assets.
|
/// Build the Rust WASM app and all of its assets.
|
||||||
// Build(build::Build),
|
Build(build::Build),
|
||||||
/// Translate some source file into Dioxus code.
|
/// Translate some source file into Dioxus code.
|
||||||
Translate(translate::Translate),
|
Translate(translate::Translate),
|
||||||
// /// Build, watch & serve the Rust WASM app and all of its assets.
|
/// Build, watch & serve the Rust WASM app and all of its assets.
|
||||||
// Serve(serve::Serve),
|
Serve(serve::Serve),
|
||||||
|
/// Init a new project for Dioxus.
|
||||||
// /// Clean output artifacts.
|
Create(create::Create),
|
||||||
// Clean(clean::Clean),
|
/// Clean output artifacts.
|
||||||
|
Clean(clean::Clean),
|
||||||
// /// Trunk config controls.
|
/// Dioxus config file controls.
|
||||||
// Config(config::Config),
|
Config(config::Config),
|
||||||
}
|
}
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
use crate::{cli::DevelopOptions, config::CrateConfig, error::Result};
|
|
||||||
use async_std::prelude::FutureExt;
|
|
||||||
|
|
||||||
use log::info;
|
|
||||||
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::sync::atomic::AtomicBool;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use tide::http::mime::HTML;
|
|
||||||
use tide::http::Mime;
|
|
||||||
|
|
||||||
pub struct DevelopState {
|
|
||||||
//
|
|
||||||
reload_on_change: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn develop(options: DevelopOptions) -> Result<()> {
|
|
||||||
//
|
|
||||||
log::info!("Starting development server 🚀");
|
|
||||||
let mut cfg = CrateConfig::new()?;
|
|
||||||
cfg.with_develop_options(&options);
|
|
||||||
|
|
||||||
let out_dir = cfg.out_dir.clone();
|
|
||||||
|
|
||||||
let is_err = Arc::new(AtomicBool::new(false));
|
|
||||||
|
|
||||||
// Spawn the server onto a seperate task
|
|
||||||
// This lets the task progress while we handle file updates
|
|
||||||
let server = async_std::task::spawn(launch_server(out_dir, is_err.clone()));
|
|
||||||
let watcher = async_std::task::spawn(watch_directory(cfg.clone(), is_err.clone()));
|
|
||||||
|
|
||||||
match server.race(watcher).await {
|
|
||||||
Err(e) => log::warn!("Error running development server, {:?}", e),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn watch_directory(config: CrateConfig, is_err: ErrStatus) -> Result<()> {
|
|
||||||
// Create a channel to receive the events.
|
|
||||||
let (watcher_tx, watcher_rx) = async_std::channel::bounded(100);
|
|
||||||
|
|
||||||
// Automatically select the best implementation for your platform.
|
|
||||||
// You can also access each implementation directly e.g. INotifyWatcher.
|
|
||||||
let mut watcher: RecommendedWatcher = Watcher::new(move |res| {
|
|
||||||
async_std::task::block_on(watcher_tx.send(res));
|
|
||||||
// send an event
|
|
||||||
let _ = async_std::task::block_on(watcher_tx.send(res));
|
|
||||||
})
|
|
||||||
.expect("failed to make watcher");
|
|
||||||
|
|
||||||
let src_dir = crate::cargo::crate_root()?;
|
|
||||||
|
|
||||||
// Add a path to be watched. All files and directories at that path and
|
|
||||||
// below will be monitored for changes.
|
|
||||||
watcher
|
|
||||||
.watch(&src_dir.join("src"), RecursiveMode::Recursive)
|
|
||||||
.expect("Failed to watch dir");
|
|
||||||
|
|
||||||
match watcher.watch(&src_dir.join("examples"), RecursiveMode::Recursive) {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(e) => log::warn!("Failed to watch examples dir, {:?}", e),
|
|
||||||
}
|
|
||||||
|
|
||||||
'run: loop {
|
|
||||||
match crate::builder::build(&config) {
|
|
||||||
Ok(_) => {
|
|
||||||
is_err.store(false, std::sync::atomic::Ordering::Relaxed);
|
|
||||||
async_std::task::sleep(std::time::Duration::from_millis(500)).await;
|
|
||||||
}
|
|
||||||
Err(err) => is_err.store(true, std::sync::atomic::Ordering::Relaxed),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut msg = None;
|
|
||||||
loop {
|
|
||||||
let new_msg = watcher_rx.recv().await.unwrap().unwrap();
|
|
||||||
if !watcher_rx.is_empty() {
|
|
||||||
msg = Some(new_msg);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("File updated, rebuilding app");
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn launch_server(outdir: PathBuf, is_err: ErrStatus) -> Result<()> {
|
|
||||||
let _crate_dir = crate::cargo::crate_root()?;
|
|
||||||
let _workspace_dir = crate::cargo::workspace_root()?;
|
|
||||||
|
|
||||||
let mut app = tide::with_state(ServerState::new(outdir.to_owned(), is_err));
|
|
||||||
|
|
||||||
let file_path = format!("{}/index.html", outdir.display());
|
|
||||||
log::info!("Serving {}", file_path);
|
|
||||||
let p = outdir.display().to_string();
|
|
||||||
|
|
||||||
app.at("/")
|
|
||||||
.get(|req: tide::Request<ServerState>| async move {
|
|
||||||
log::info!("Connected to development server");
|
|
||||||
let state = req.state();
|
|
||||||
|
|
||||||
match state.is_err.load(std::sync::atomic::Ordering::Relaxed) {
|
|
||||||
true => {
|
|
||||||
//
|
|
||||||
let mut resp =
|
|
||||||
tide::Body::from_string(format!(include_str!("../err.html"), err = "_"));
|
|
||||||
resp.set_mime(HTML);
|
|
||||||
|
|
||||||
Ok(resp)
|
|
||||||
}
|
|
||||||
false => {
|
|
||||||
Ok(tide::Body::from_file(state.serv_path.clone().join("index.html")).await?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.serve_dir(p)?;
|
|
||||||
// .serve_file(file_path)
|
|
||||||
// .unwrap();
|
|
||||||
|
|
||||||
let port = "8080";
|
|
||||||
let serve_addr = format!("127.0.0.1:{}", port);
|
|
||||||
|
|
||||||
info!("App available at http://{}/", serve_addr);
|
|
||||||
app.listen(serve_addr).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// https://github.com/http-rs/tide/blob/main/examples/state.rs
|
|
||||||
/// Tide seems to prefer using state instead of injecting into the app closure
|
|
||||||
/// The app closure needs to be static and
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct ServerState {
|
|
||||||
serv_path: PathBuf,
|
|
||||||
is_err: ErrStatus,
|
|
||||||
}
|
|
||||||
|
|
||||||
type ErrStatus = Arc<AtomicBool>;
|
|
||||||
|
|
||||||
impl ServerState {
|
|
||||||
fn new(serv_path: PathBuf, is_err: ErrStatus) -> Self {
|
|
||||||
Self { serv_path, is_err }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Whitespace-only changes.
Whitespace-only changes.
+83
-5
@@ -1,9 +1,87 @@
|
|||||||
use crate::cfg::ConfigOptsBuild;
|
use crate::{cfg::ConfigOptsServe, gen_page, server, CrateConfig};
|
||||||
use anyhow::Result;
|
use std::{io::Write, path::PathBuf, process::{Command, Stdio}};
|
||||||
use std::path::PathBuf;
|
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
/// Build the Rust WASM app and all of its assets.
|
/// Run the WASM project on dev-server
|
||||||
#[derive(Clone, Debug, StructOpt)]
|
#[derive(Clone, Debug, StructOpt)]
|
||||||
#[structopt(name = "serve")]
|
#[structopt(name = "serve")]
|
||||||
pub struct Serve {}
|
pub struct Serve {
|
||||||
|
#[structopt(flatten)]
|
||||||
|
pub serve: ConfigOptsServe,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serve {
|
||||||
|
pub async fn serve(self) -> anyhow::Result<()> {
|
||||||
|
let mut crate_config = crate::CrateConfig::new()?;
|
||||||
|
|
||||||
|
// change the relase state.
|
||||||
|
crate_config.with_release(self.serve.release);
|
||||||
|
|
||||||
|
if self.serve.example.is_some() {
|
||||||
|
crate_config.as_example(self.serve.example.unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.serve.platform.as_str() {
|
||||||
|
"web" => {
|
||||||
|
crate::builder::build(&crate_config)?;
|
||||||
|
}
|
||||||
|
"desktop" => {
|
||||||
|
crate::builder::build_desktop(&crate_config)?;
|
||||||
|
|
||||||
|
match &crate_config.executable {
|
||||||
|
crate::ExecutableType::Binary(name)
|
||||||
|
| crate::ExecutableType::Lib(name)
|
||||||
|
| crate::ExecutableType::Example(name) => {
|
||||||
|
let mut file = crate_config.out_dir.join(name);
|
||||||
|
if cfg!(windows) {
|
||||||
|
file.set_extension("exe");
|
||||||
|
}
|
||||||
|
Command::new(
|
||||||
|
crate_config
|
||||||
|
.out_dir
|
||||||
|
.join(file)
|
||||||
|
.to_str()
|
||||||
|
.unwrap()
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
.stdout(Stdio::inherit())
|
||||||
|
.output()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(anyhow::anyhow!("Unsoppurt platform target."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate dev-index page
|
||||||
|
Serve::regen_dev_page(&crate_config)?;
|
||||||
|
|
||||||
|
// start the develop server
|
||||||
|
server::startup(crate_config.clone()).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn regen_dev_page(crate_config: &CrateConfig) -> anyhow::Result<()> {
|
||||||
|
let serve_html = gen_page(&crate_config.dioxus_config, true);
|
||||||
|
|
||||||
|
let mut file = std::fs::File::create(
|
||||||
|
crate_config
|
||||||
|
.crate_dir
|
||||||
|
.join(
|
||||||
|
crate_config
|
||||||
|
.dioxus_config
|
||||||
|
.application
|
||||||
|
.out_dir
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| PathBuf::from("dist")),
|
||||||
|
)
|
||||||
|
.join("index.html"),
|
||||||
|
)?;
|
||||||
|
file.write_all(serve_html.as_bytes())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,164 +0,0 @@
|
|||||||
//! It's better to store all the configuration in one spot
|
|
||||||
|
|
||||||
use tui::{
|
|
||||||
backend::{Backend, CrosstermBackend},
|
|
||||||
layout::{Constraint, Direction, Layout, Rect},
|
|
||||||
style::{Color, Modifier, Style},
|
|
||||||
symbols,
|
|
||||||
text::{Span, Spans},
|
|
||||||
widgets::canvas::{Canvas, Line, Map, MapResolution, Rectangle},
|
|
||||||
widgets::{
|
|
||||||
Axis, BarChart, Block, BorderType, Borders, Cell, Chart, Dataset, Gauge, LineGauge, List,
|
|
||||||
ListItem, Paragraph, Row, Sparkline, Table, Tabs, Wrap,
|
|
||||||
},
|
|
||||||
Frame, Terminal,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::*;
|
|
||||||
use std::{any::Any, io::Write, path::PathBuf, process::Command};
|
|
||||||
|
|
||||||
pub struct Cfg {
|
|
||||||
command: LaunchOptions,
|
|
||||||
headless: bool,
|
|
||||||
example: Option<String>,
|
|
||||||
outdir: Option<String>,
|
|
||||||
release: bool,
|
|
||||||
hydrate: Option<String>,
|
|
||||||
template: Option<String>,
|
|
||||||
translate_file: Option<String>,
|
|
||||||
crate_config: Option<CrateConfig>,
|
|
||||||
should_quit: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn start(options: DevelopOptions) -> Result<()> {
|
|
||||||
let mut state = Cfg {
|
|
||||||
command: todo!(),
|
|
||||||
headless: todo!(),
|
|
||||||
example: todo!(),
|
|
||||||
outdir: todo!(),
|
|
||||||
release: todo!(),
|
|
||||||
hydrate: todo!(),
|
|
||||||
template: todo!(),
|
|
||||||
translate_file: todo!(),
|
|
||||||
crate_config: todo!(),
|
|
||||||
should_quit: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
crossterm::terminal::enable_raw_mode()?;
|
|
||||||
|
|
||||||
let backend = CrosstermBackend::new(std::io::stdout());
|
|
||||||
let mut terminal = Terminal::new(backend).unwrap();
|
|
||||||
|
|
||||||
// Setup input handling
|
|
||||||
// let (tx, rx) = futures::channel::mpsc::unbounded();
|
|
||||||
let tick_rate = std::time::Duration::from_millis(100);
|
|
||||||
|
|
||||||
let mut prev_time = std::time::Instant::now();
|
|
||||||
while !state.should_quit {
|
|
||||||
let next_time = prev_time + tick_rate;
|
|
||||||
let now = std::time::Instant::now();
|
|
||||||
|
|
||||||
let diff = next_time - std::time::Instant::now();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TuiStudio {
|
|
||||||
cfg: Cfg,
|
|
||||||
hook_idx: usize,
|
|
||||||
hooks: Vec<Box<dyn Any>>,
|
|
||||||
}
|
|
||||||
impl TuiStudio {
|
|
||||||
fn use_hook<F: 'static>(&mut self, f: impl FnOnce() -> F) -> &mut F {
|
|
||||||
if self.hook_idx == self.hooks.len() {
|
|
||||||
self.hooks.push(Box::new(f()));
|
|
||||||
}
|
|
||||||
let idx = self.hook_idx;
|
|
||||||
self.hook_idx += 1;
|
|
||||||
let hook = self.hooks.get_mut(idx).unwrap();
|
|
||||||
let r = hook.downcast_mut::<F>().unwrap();
|
|
||||||
r
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TuiStudio {
|
|
||||||
fn event_handler(&self, action: crossterm::event::Event) -> anyhow::Result<()> {
|
|
||||||
match action {
|
|
||||||
crossterm::event::Event::Key(_) => {}
|
|
||||||
crossterm::event::Event::Mouse(_) => {}
|
|
||||||
crossterm::event::Event::Resize(_, _) => {}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_key(&mut self, key: crossterm::event::KeyEvent) {}
|
|
||||||
|
|
||||||
fn tick(&mut self) {}
|
|
||||||
|
|
||||||
fn should_quit(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render<B: tui::backend::Backend>(&mut self, f: &mut tui::Frame<B>) {
|
|
||||||
self.hook_idx = 0;
|
|
||||||
|
|
||||||
// Wrapping block for a group
|
|
||||||
// Just draw the block and the group on the same area and build the group
|
|
||||||
// with at least a margin of 1
|
|
||||||
let size = f.size();
|
|
||||||
let block = Block::default()
|
|
||||||
.borders(Borders::ALL)
|
|
||||||
.title("Main block with round corners")
|
|
||||||
.border_type(BorderType::Rounded);
|
|
||||||
f.render_widget(block, size);
|
|
||||||
let chunks = Layout::default()
|
|
||||||
.direction(Direction::Vertical)
|
|
||||||
.margin(4)
|
|
||||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
|
||||||
.split(f.size());
|
|
||||||
|
|
||||||
let top_chunks = Layout::default()
|
|
||||||
.direction(Direction::Horizontal)
|
|
||||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
|
||||||
.split(chunks[0]);
|
|
||||||
let block = Block::default()
|
|
||||||
.title(vec![
|
|
||||||
Span::styled("With", Style::default().fg(Color::Yellow)),
|
|
||||||
Span::from(" background"),
|
|
||||||
])
|
|
||||||
.style(Style::default().bg(Color::Green));
|
|
||||||
f.render_widget(block, top_chunks[0]);
|
|
||||||
|
|
||||||
let block = Block::default().title(Span::styled(
|
|
||||||
"Styled title",
|
|
||||||
Style::default()
|
|
||||||
.fg(Color::White)
|
|
||||||
.bg(Color::Red)
|
|
||||||
.add_modifier(Modifier::BOLD),
|
|
||||||
));
|
|
||||||
f.render_widget(block, top_chunks[1]);
|
|
||||||
|
|
||||||
let bottom_chunks = Layout::default()
|
|
||||||
.direction(Direction::Horizontal)
|
|
||||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
|
||||||
.split(chunks[1]);
|
|
||||||
let block = Block::default().title("With borders").borders(Borders::ALL);
|
|
||||||
f.render_widget(block, bottom_chunks[0]);
|
|
||||||
let block = Block::default()
|
|
||||||
.title("With styled borders and doubled borders")
|
|
||||||
.border_style(Style::default().fg(Color::Cyan))
|
|
||||||
.borders(Borders::LEFT | Borders::RIGHT)
|
|
||||||
.border_type(BorderType::Double);
|
|
||||||
f.render_widget(block, bottom_chunks[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TuiStudio {
|
|
||||||
fn render_list<B: tui::backend::Backend>(&mut self, f: &mut tui::Frame<B>) {
|
|
||||||
let options = [
|
|
||||||
"Bundle", "Develop",
|
|
||||||
//
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -48,7 +48,7 @@ impl Translate {
|
|||||||
exit(0);
|
exit(0);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
if let None = temp {
|
if let Some(..) = temp {
|
||||||
if let Some(s) = source {
|
if let Some(s) = source {
|
||||||
contents = s;
|
contents = s;
|
||||||
} else {
|
} else {
|
||||||
@@ -112,7 +112,7 @@ fn render_child(f: &mut impl Write, child: &Node, il: u32) -> std::fmt::Result {
|
|||||||
}
|
}
|
||||||
write!(f, "\",")?;
|
write!(f, "\",")?;
|
||||||
}
|
}
|
||||||
write!(f, "\n")?;
|
writeln!(f)?;
|
||||||
|
|
||||||
// write the attributes
|
// write the attributes
|
||||||
if let Some(id) = &el.id {
|
if let Some(id) = &el.id {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
//! - [ ] Extracts svgs
|
//! - [ ] Extracts svgs
|
||||||
//! - [ ] Attempts to extract lists
|
//! - [ ] Attempts to extract lists
|
||||||
|
|
||||||
|
#[allow(unused_imports)]
|
||||||
use std::{
|
use std::{
|
||||||
fmt::{Display, Formatter},
|
fmt::{Display, Formatter},
|
||||||
io::Write,
|
io::Write,
|
||||||
@@ -20,6 +21,7 @@ pub fn convert_html_to_component(html: &str) -> Result<ComponentRenderer> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub struct ComponentRenderer {
|
pub struct ComponentRenderer {
|
||||||
dom: Dom,
|
dom: Dom,
|
||||||
icon_index: usize,
|
icon_index: usize,
|
||||||
@@ -118,7 +120,7 @@ fn render_element<'a>(
|
|||||||
}
|
}
|
||||||
write!(f, "\",")?;
|
write!(f, "\",")?;
|
||||||
}
|
}
|
||||||
write!(f, "\n")?;
|
writeln!(f)?;
|
||||||
|
|
||||||
// write the attributes
|
// write the attributes
|
||||||
if let Some(id) = &el.id {
|
if let Some(id) = &el.id {
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
+114
-13
@@ -1,5 +1,99 @@
|
|||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use std::{io::Write, path::PathBuf, process::Command};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{fs::File, io::Read, path::PathBuf};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct DioxusConfig {
|
||||||
|
pub application: ApplicationConfig,
|
||||||
|
pub web: WebConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DioxusConfig {
|
||||||
|
pub fn load() -> crate::error::Result<DioxusConfig> {
|
||||||
|
let crate_dir = crate::cargo::crate_root()?;
|
||||||
|
|
||||||
|
if !crate_dir.join("Dioxus.toml").is_file() {
|
||||||
|
log::warn!("Config file: `Dioxus.toml` not found; using default config.");
|
||||||
|
return Ok(DioxusConfig::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut dioxus_conf_file = File::open(crate_dir.join("Dioxus.toml"))?;
|
||||||
|
let mut meta_str = String::new();
|
||||||
|
dioxus_conf_file.read_to_string(&mut meta_str)?;
|
||||||
|
|
||||||
|
toml::from_str::<DioxusConfig>(&meta_str)
|
||||||
|
.map_err(|_| crate::Error::Unique("Dioxus.toml parse failed".into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DioxusConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
application: ApplicationConfig {
|
||||||
|
name: "dioxus".into(),
|
||||||
|
platforms: vec![String::from("web")],
|
||||||
|
out_dir: Some(PathBuf::from("dist")),
|
||||||
|
},
|
||||||
|
web: WebConfig {
|
||||||
|
app: WebAppConfing {
|
||||||
|
title: Some("dioxus | ⛺".into()),
|
||||||
|
public_dir: Some(PathBuf::from("public")),
|
||||||
|
},
|
||||||
|
watcher: WebWatcherConfing {
|
||||||
|
watch_path: Some(vec![PathBuf::from("src")]),
|
||||||
|
reload_html: Some(false),
|
||||||
|
},
|
||||||
|
resource: WebResourceConfing {
|
||||||
|
dev: WebDevResourceConfing {
|
||||||
|
style: Some(vec![]),
|
||||||
|
script: Some(vec![]),
|
||||||
|
},
|
||||||
|
style: Some(vec![]),
|
||||||
|
script: Some(vec![]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ApplicationConfig {
|
||||||
|
pub name: String,
|
||||||
|
pub platforms: Vec<String>,
|
||||||
|
pub out_dir: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct WebConfig {
|
||||||
|
pub app: WebAppConfing,
|
||||||
|
pub watcher: WebWatcherConfing,
|
||||||
|
pub resource: WebResourceConfing,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct WebAppConfing {
|
||||||
|
pub title: Option<String>,
|
||||||
|
pub public_dir: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct WebWatcherConfing {
|
||||||
|
pub watch_path: Option<Vec<PathBuf>>,
|
||||||
|
pub reload_html: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct WebResourceConfing {
|
||||||
|
pub dev: WebDevResourceConfing,
|
||||||
|
pub style: Option<Vec<PathBuf>>,
|
||||||
|
pub script: Option<Vec<PathBuf>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct WebDevResourceConfing {
|
||||||
|
pub style: Option<Vec<PathBuf>>,
|
||||||
|
pub script: Option<Vec<PathBuf>>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct CrateConfig {
|
pub struct CrateConfig {
|
||||||
@@ -7,9 +101,10 @@ pub struct CrateConfig {
|
|||||||
pub crate_dir: PathBuf,
|
pub crate_dir: PathBuf,
|
||||||
pub workspace_dir: PathBuf,
|
pub workspace_dir: PathBuf,
|
||||||
pub target_dir: PathBuf,
|
pub target_dir: PathBuf,
|
||||||
pub static_dir: PathBuf,
|
pub public_dir: PathBuf,
|
||||||
pub manifest: cargo_toml::Manifest<cargo_toml::Value>,
|
pub manifest: cargo_toml::Manifest<cargo_toml::Value>,
|
||||||
pub executable: ExecutableType,
|
pub executable: ExecutableType,
|
||||||
|
pub dioxus_config: DioxusConfig,
|
||||||
pub release: bool,
|
pub release: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,28 +117,33 @@ pub enum ExecutableType {
|
|||||||
|
|
||||||
impl CrateConfig {
|
impl CrateConfig {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new() -> Result<Self> {
|
||||||
|
let dioxus_config = DioxusConfig::load()?;
|
||||||
|
|
||||||
let crate_dir = crate::cargo::crate_root()?;
|
let crate_dir = crate::cargo::crate_root()?;
|
||||||
let workspace_dir = crate::cargo::workspace_root()?;
|
let workspace_dir = crate::cargo::workspace_root()?;
|
||||||
let target_dir = workspace_dir.join("target");
|
let target_dir = workspace_dir.join("target");
|
||||||
let out_dir = crate_dir.join("public");
|
|
||||||
|
let out_dir = match dioxus_config.application.out_dir {
|
||||||
|
Some(ref v) => crate_dir.join(v),
|
||||||
|
None => crate_dir.join("dist"),
|
||||||
|
};
|
||||||
|
|
||||||
let cargo_def = &crate_dir.join("Cargo.toml");
|
let cargo_def = &crate_dir.join("Cargo.toml");
|
||||||
let static_dir = crate_dir.join("static");
|
|
||||||
|
let public_dir = match dioxus_config.web.app.public_dir {
|
||||||
|
Some(ref v) => crate_dir.join(v),
|
||||||
|
None => crate_dir.join("public"),
|
||||||
|
};
|
||||||
|
|
||||||
let manifest = cargo_toml::Manifest::from_path(&cargo_def).unwrap();
|
let manifest = cargo_toml::Manifest::from_path(&cargo_def).unwrap();
|
||||||
|
|
||||||
// We just assume they're using a 'main.rs'
|
// We just assume they're using a 'main.rs'
|
||||||
// Anyway, we've already parsed the manifest, so it should be easy to change the type
|
// Anyway, we've already parsed the manifest, so it should be easy to change the type
|
||||||
let output_filename = (&manifest)
|
let output_filename = manifest
|
||||||
.lib
|
.lib
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|lib| lib.name.clone())
|
.and_then(|lib| lib.name.clone())
|
||||||
.or_else(|| {
|
.or_else(|| manifest.package.as_ref().map(|pkg| pkg.name.clone()))
|
||||||
(&manifest)
|
|
||||||
.package
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|pkg| Some(pkg.name.replace("-", "_")))
|
|
||||||
.clone()
|
|
||||||
})
|
|
||||||
.expect("No lib found from cargo metadata");
|
.expect("No lib found from cargo metadata");
|
||||||
let executable = ExecutableType::Binary(output_filename);
|
let executable = ExecutableType::Binary(output_filename);
|
||||||
|
|
||||||
@@ -54,10 +154,11 @@ impl CrateConfig {
|
|||||||
crate_dir,
|
crate_dir,
|
||||||
workspace_dir,
|
workspace_dir,
|
||||||
target_dir,
|
target_dir,
|
||||||
static_dir,
|
public_dir,
|
||||||
manifest,
|
manifest,
|
||||||
executable,
|
executable,
|
||||||
release,
|
release,
|
||||||
|
dioxus_config,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
<html>
|
|
||||||
|
|
||||||
<head></head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div>
|
|
||||||
<h1>
|
|
||||||
Sorry, but building your application failed.
|
|
||||||
</h1>
|
|
||||||
<p>
|
|
||||||
Here's the error:
|
|
||||||
</p>
|
|
||||||
<code>
|
|
||||||
{err}
|
|
||||||
</code>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
+1
-10
@@ -1,4 +1,5 @@
|
|||||||
pub mod builder;
|
pub mod builder;
|
||||||
|
pub mod server;
|
||||||
pub use builder::*;
|
pub use builder::*;
|
||||||
|
|
||||||
pub mod cargo;
|
pub mod cargo;
|
||||||
@@ -15,13 +16,3 @@ pub use error::*;
|
|||||||
|
|
||||||
pub mod logging;
|
pub mod logging;
|
||||||
pub use logging::*;
|
pub use logging::*;
|
||||||
|
|
||||||
pub mod watch;
|
|
||||||
|
|
||||||
pub mod buildsystem {
|
|
||||||
pub mod wasm;
|
|
||||||
pub use wasm::*;
|
|
||||||
|
|
||||||
pub mod assets;
|
|
||||||
pub use assets::*;
|
|
||||||
}
|
|
||||||
+1
-1
@@ -14,7 +14,7 @@ pub fn set_up_logging() {
|
|||||||
// configure colors for the name of the level.
|
// configure colors for the name of the level.
|
||||||
// since almost all of them are the same as the color for the whole line, we
|
// since almost all of them are the same as the color for the whole line, we
|
||||||
// just clone `colors_line` and overwrite our changes
|
// just clone `colors_line` and overwrite our changes
|
||||||
let colors_level = colors_line.clone().info(Color::Green);
|
let colors_level = colors_line.info(Color::Green);
|
||||||
// here we set up our fern Dispatch
|
// here we set up our fern Dispatch
|
||||||
fern::Dispatch::new()
|
fern::Dispatch::new()
|
||||||
.format(move |out, message, record| {
|
.format(move |out, message, record| {
|
||||||
|
|||||||
+31
-13
@@ -8,22 +8,40 @@ async fn main() -> Result<()> {
|
|||||||
|
|
||||||
match args.action {
|
match args.action {
|
||||||
Commands::Translate(opts) => {
|
Commands::Translate(opts) => {
|
||||||
opts.translate();
|
if let Err(e) = opts.translate() {
|
||||||
} // Commands::Build(_) => {
|
log::error!("translate error: {}", e);
|
||||||
// //
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// Commands::Clean(_) => {
|
Commands::Build(opts) => {
|
||||||
// //
|
if let Err(e) = opts.build() {
|
||||||
// }
|
log::error!("build error: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Commands::Config(_) => {
|
Commands::Clean(opts) => {
|
||||||
// //
|
if let Err(e) = opts.clean() {
|
||||||
// }
|
log::error!("clean error: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Commands::Serve(_) => {
|
Commands::Serve(opts) => {
|
||||||
// //
|
if let Err(e) = opts.serve().await {
|
||||||
// }
|
log::error!("serve error: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Commands::Create(opts) => {
|
||||||
|
if let Err(e) = opts.create() {
|
||||||
|
log::error!("create error: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Commands::Config(opts) => {
|
||||||
|
if let Err(e) = opts.config() {
|
||||||
|
log::error!("config error: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
use axum::{
|
||||||
|
extract::{ws::Message, Extension, TypedHeader, WebSocketUpgrade},
|
||||||
|
http::StatusCode,
|
||||||
|
response::IntoResponse,
|
||||||
|
routing::{get, get_service},
|
||||||
|
AddExtensionLayer, Router,
|
||||||
|
};
|
||||||
|
use notify::{RecommendedWatcher, Watcher};
|
||||||
|
|
||||||
|
use std::{path::PathBuf, sync::Arc};
|
||||||
|
use tower_http::services::ServeDir;
|
||||||
|
|
||||||
|
use crate::{builder, serve::Serve, CrateConfig};
|
||||||
|
use tokio::sync::broadcast;
|
||||||
|
|
||||||
|
struct WsRelodState {
|
||||||
|
update: broadcast::Sender<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn startup(config: CrateConfig) -> anyhow::Result<()> {
|
||||||
|
log::info!("🚀 Starting development server...");
|
||||||
|
|
||||||
|
let dist_path = config.out_dir.clone();
|
||||||
|
|
||||||
|
let (reload_tx, _) = broadcast::channel(100);
|
||||||
|
|
||||||
|
let ws_reload_state = Arc::new(WsRelodState {
|
||||||
|
update: reload_tx.clone(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut last_update_time = chrono::Local::now().timestamp();
|
||||||
|
|
||||||
|
// file watcher: check file change
|
||||||
|
let watcher_conf = config.clone();
|
||||||
|
let mut watcher = RecommendedWatcher::new(move |_: notify::Result<notify::Event>| {
|
||||||
|
if chrono::Local::now().timestamp() > last_update_time
|
||||||
|
&& builder::build(&watcher_conf).is_ok()
|
||||||
|
{
|
||||||
|
// change the websocket reload state to true;
|
||||||
|
// the page will auto-reload.
|
||||||
|
if watcher_conf
|
||||||
|
.dioxus_config
|
||||||
|
.web
|
||||||
|
.watcher
|
||||||
|
.reload_html
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
let _ = Serve::regen_dev_page(&watcher_conf);
|
||||||
|
}
|
||||||
|
println!("watcher send reload");
|
||||||
|
reload_tx.send("reload".into()).unwrap();
|
||||||
|
last_update_time = chrono::Local::now().timestamp();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
let allow_watch_path = config
|
||||||
|
.dioxus_config
|
||||||
|
.web
|
||||||
|
.watcher
|
||||||
|
.watch_path
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| vec![PathBuf::from("src")]);
|
||||||
|
|
||||||
|
for sub_path in allow_watch_path {
|
||||||
|
watcher
|
||||||
|
.watch(
|
||||||
|
&config.crate_dir.join(sub_path),
|
||||||
|
notify::RecursiveMode::Recursive,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// start serve dev-server at 0.0.0.0:8080
|
||||||
|
let port = "8080";
|
||||||
|
log::info!("📡 Dev-Server is started at: http://127.0.0.1:{}/", port);
|
||||||
|
|
||||||
|
axum::Server::bind(&format!("0.0.0.0:{}", port).parse().unwrap())
|
||||||
|
.serve(
|
||||||
|
Router::new()
|
||||||
|
.route("/_dioxus/ws", get(ws_handler))
|
||||||
|
.fallback(
|
||||||
|
get_service(ServeDir::new(config.crate_dir.join(&dist_path))).handle_error(
|
||||||
|
|error: std::io::Error| async move {
|
||||||
|
(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
format!("Unhandled internal error: {}", error),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.layer(AddExtensionLayer::new(ws_reload_state))
|
||||||
|
.into_make_service(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn ws_handler(
|
||||||
|
ws: WebSocketUpgrade,
|
||||||
|
_: Option<TypedHeader<headers::UserAgent>>,
|
||||||
|
Extension(state): Extension<Arc<WsRelodState>>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
ws.on_upgrade(|mut socket| async move {
|
||||||
|
let mut rx = state.update.subscribe();
|
||||||
|
let reload_watcher = tokio::spawn(async move {
|
||||||
|
loop {
|
||||||
|
let v = rx.recv().await.unwrap();
|
||||||
|
if v == "reload" {
|
||||||
|
// ignore the error
|
||||||
|
if socket
|
||||||
|
.send(Message::Text(String::from("reload")))
|
||||||
|
.await
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
reload_watcher.await.unwrap();
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
Reference in new issue
Block a user