feat: add init command
This commit is contained in:
1 parent
d18a93f129
commit
0e2c05be66
15 files changed
+147
-188
No files matched your search
@@ -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
|
||||||
@@ -20,4 +20,3 @@ $ cargo install dioxus-cli
|
|||||||
```
|
```
|
||||||
|
|
||||||
Now, `dioxus` is in your path.
|
Now, `dioxus` is in your path.
|
||||||
|
|
||||||
+2
-6
@@ -2,10 +2,7 @@ use crate::{
|
|||||||
config::{CrateConfig, ExecutableType},
|
config::{CrateConfig, ExecutableType},
|
||||||
error::{Error, Result},
|
error::{Error, Result},
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{io::Write, process::Command};
|
||||||
io::Write,
|
|
||||||
process::Command,
|
|
||||||
};
|
|
||||||
use wasm_bindgen_cli_support::Bindgen;
|
use wasm_bindgen_cli_support::Bindgen;
|
||||||
|
|
||||||
pub fn build(config: &CrateConfig) -> Result<()> {
|
pub fn build(config: &CrateConfig) -> Result<()> {
|
||||||
@@ -36,8 +33,7 @@ pub fn build(config: &CrateConfig) -> Result<()> {
|
|||||||
.arg("--target")
|
.arg("--target")
|
||||||
.arg("wasm32-unknown-unknown")
|
.arg("wasm32-unknown-unknown")
|
||||||
.stdout(std::process::Stdio::inherit())
|
.stdout(std::process::Stdio::inherit())
|
||||||
.stderr(std::process::Stdio::inherit())
|
.stderr(std::process::Stdio::inherit());
|
||||||
;
|
|
||||||
|
|
||||||
if config.release {
|
if config.release {
|
||||||
cmd.arg("--release");
|
cmd.arg("--release");
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
use std::{process::{Command, Stdio}, fs::File, path::PathBuf, io::Write};
|
||||||
|
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
use crate::{error::{Error, Result}, cargo};
|
||||||
|
|
||||||
|
/// Build the Rust WASM app and all of its assets.
|
||||||
|
#[derive(Clone, Debug, StructOpt)]
|
||||||
|
#[structopt(name = "init")]
|
||||||
|
pub struct Init {
|
||||||
|
|
||||||
|
/// Init project path
|
||||||
|
#[structopt(default_value = ".")]
|
||||||
|
path: String,
|
||||||
|
|
||||||
|
/// Template path
|
||||||
|
#[structopt(default_value = "default", long)]
|
||||||
|
template: String,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Init {
|
||||||
|
pub fn init(self) -> Result<()> {
|
||||||
|
|
||||||
|
log::info!("🔧 Start to init a new project '{}'.", self.path);
|
||||||
|
|
||||||
|
let project_path = PathBuf::from(&self.path);
|
||||||
|
|
||||||
|
let output = Command::new("cargo")
|
||||||
|
.arg("init")
|
||||||
|
.arg(&self.path)
|
||||||
|
.arg("--bin")
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.output()?
|
||||||
|
;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
return Err(Error::CargoError("Cargo init failed".into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the template code
|
||||||
|
let template_str = match self.template {
|
||||||
|
_ => include_str!("../../template/default.rs"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let main_rs_file = project_path.join("src").join("main.rs");
|
||||||
|
if !main_rs_file.is_file() {
|
||||||
|
return Err(Error::FailedToWrite);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut file = File::create(main_rs_file)?;
|
||||||
|
file.write_all(&template_str.as_bytes())?;
|
||||||
|
|
||||||
|
// log::info!("🎯 Project initialization completed.");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
-2
@@ -5,6 +5,7 @@ pub mod cfg;
|
|||||||
pub mod clean;
|
pub mod clean;
|
||||||
pub mod serve;
|
pub mod serve;
|
||||||
pub mod translate;
|
pub mod translate;
|
||||||
|
pub mod init;
|
||||||
|
|
||||||
/// Build, bundle, & ship your Dioxus app.
|
/// Build, bundle, & ship your Dioxus app.
|
||||||
///
|
///
|
||||||
@@ -27,12 +28,15 @@ 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.
|
||||||
|
Init(init::Init),
|
||||||
|
|
||||||
// /// Clean output artifacts.
|
// /// Clean output artifacts.
|
||||||
// Clean(clean::Clean),
|
// Clean(clean::Clean),
|
||||||
|
|
||||||
|
|||||||
@@ -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.
@@ -2,8 +2,6 @@ use crate::{cfg::ConfigOptsServe, server};
|
|||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
mod develop;
|
|
||||||
|
|
||||||
/// Run the WASM project on dev-server
|
/// Run the WASM project on dev-server
|
||||||
#[derive(Clone, Debug, StructOpt)]
|
#[derive(Clone, Debug, StructOpt)]
|
||||||
#[structopt(name = "serve")]
|
#[structopt(name = "serve")]
|
||||||
@@ -14,7 +12,6 @@ pub struct Serve {
|
|||||||
|
|
||||||
impl Serve {
|
impl Serve {
|
||||||
pub async fn serve(self) -> anyhow::Result<()> {
|
pub async fn serve(self) -> anyhow::Result<()> {
|
||||||
|
|
||||||
let mut crate_config = crate::CrateConfig::new()?;
|
let mut crate_config = crate::CrateConfig::new()?;
|
||||||
// change the relase state.
|
// change the relase state.
|
||||||
crate_config.with_release(self.serve.release);
|
crate_config.with_release(self.serve.release);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -16,5 +16,3 @@ pub use error::*;
|
|||||||
|
|
||||||
pub mod logging;
|
pub mod logging;
|
||||||
pub use logging::*;
|
pub use logging::*;
|
||||||
|
|
||||||
pub mod watch;
|
|
||||||
+15
-3
@@ -8,11 +8,15 @@ async fn main() -> Result<()> {
|
|||||||
|
|
||||||
match args.action {
|
match args.action {
|
||||||
Commands::Translate(opts) => {
|
Commands::Translate(opts) => {
|
||||||
opts.translate()?;
|
if let Err(e) = opts.translate() {
|
||||||
|
log::error!("translate error: {}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Commands::Build(opts) => {
|
Commands::Build(opts) => {
|
||||||
opts.build()?;
|
if let Err(e) = opts.build() {
|
||||||
|
log::error!("build error: {}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commands::Clean(_) => {
|
// Commands::Clean(_) => {
|
||||||
@@ -23,7 +27,15 @@ async fn main() -> Result<()> {
|
|||||||
// //
|
// //
|
||||||
// }
|
// }
|
||||||
Commands::Serve(opts) => {
|
Commands::Serve(opts) => {
|
||||||
opts.serve().await?;
|
if let Err(e) = opts.serve().await {
|
||||||
|
log::error!("serve error: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Commands::Init(opts) => {
|
||||||
|
if let Err(e) = opts.init() {
|
||||||
|
log::error!("init error: {}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File renamed without changes.
+36
-24
@@ -1,34 +1,40 @@
|
|||||||
use axum::{
|
use axum::{
|
||||||
extract::{
|
extract::{ws::Message, Extension, TypedHeader, WebSocketUpgrade},
|
||||||
ws::Message,
|
|
||||||
Extension, TypedHeader, WebSocketUpgrade,
|
|
||||||
},
|
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
routing::{get, get_service},
|
routing::{get, get_service},
|
||||||
AddExtensionLayer, Router,
|
AddExtensionLayer, Router,
|
||||||
};
|
};
|
||||||
use notify::{watcher, Watcher, DebouncedEvent};
|
use notify::{watcher, DebouncedEvent, Watcher};
|
||||||
use std::sync::{mpsc::channel, Arc, Mutex};
|
use std::sync::{mpsc::channel, Arc, Mutex};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tower_http::services::ServeDir;
|
use tower_http::services::ServeDir;
|
||||||
|
|
||||||
use crate::{CrateConfig, builder};
|
use crate::{builder, CrateConfig};
|
||||||
|
|
||||||
struct WsRelodState {
|
struct WsRelodState {
|
||||||
update: bool,
|
update: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WsRelodState { fn change(&mut self) { self.update = !self.update; } }
|
impl WsRelodState {
|
||||||
|
fn change(&mut self) {
|
||||||
|
self.update = !self.update;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn startup(config: CrateConfig) -> anyhow::Result<()> {
|
pub async fn startup(config: CrateConfig) -> anyhow::Result<()> {
|
||||||
|
|
||||||
log::info!("🚀 Starting development server...");
|
log::info!("🚀 Starting development server...");
|
||||||
|
|
||||||
let (tx, rx) = channel();
|
let (tx, rx) = channel();
|
||||||
|
|
||||||
|
// file watcher: check file change
|
||||||
let mut watcher = watcher(tx, Duration::from_secs(2)).unwrap();
|
let mut watcher = watcher(tx, Duration::from_secs(2)).unwrap();
|
||||||
watcher.watch(config.crate_dir.join("src").clone(), notify::RecursiveMode::Recursive).unwrap();
|
watcher
|
||||||
|
.watch(
|
||||||
|
config.crate_dir.join("src").clone(),
|
||||||
|
notify::RecursiveMode::Recursive,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let ws_reload_state = Arc::new(Mutex::new(WsRelodState { update: false }));
|
let ws_reload_state = Arc::new(Mutex::new(WsRelodState { update: false }));
|
||||||
|
|
||||||
@@ -38,12 +44,16 @@ pub async fn startup(config: CrateConfig) -> anyhow::Result<()> {
|
|||||||
loop {
|
loop {
|
||||||
if let Ok(v) = rx.recv() {
|
if let Ok(v) = rx.recv() {
|
||||||
match v {
|
match v {
|
||||||
DebouncedEvent::Create(_) | DebouncedEvent::Write(_) |
|
DebouncedEvent::Create(_)
|
||||||
DebouncedEvent::Remove(_) | DebouncedEvent::Rename(_, _) => {
|
| DebouncedEvent::Write(_)
|
||||||
|
| DebouncedEvent::Remove(_)
|
||||||
|
| DebouncedEvent::Rename(_, _) => {
|
||||||
if let Ok(_) = builder::build(&watcher_conf) {
|
if let Ok(_) = builder::build(&watcher_conf) {
|
||||||
|
// change the websocket reload state to true;
|
||||||
|
// the page will auto-reload.
|
||||||
watcher_ws_state.lock().unwrap().change();
|
watcher_ws_state.lock().unwrap().change();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,23 +62,22 @@ pub async fn startup(config: CrateConfig) -> anyhow::Result<()> {
|
|||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/ws", get(ws_handler))
|
.route("/ws", get(ws_handler))
|
||||||
.fallback(
|
.fallback(get_service(ServeDir::new(config.out_dir)).handle_error(
|
||||||
get_service(ServeDir::new(config.out_dir)).handle_error(
|
|error: std::io::Error| async move {
|
||||||
|error: std::io::Error| async move {
|
(
|
||||||
(
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
format!("Unhandled internal error: {}", error),
|
||||||
format!("Unhandled internal error: {}", error),
|
)
|
||||||
)
|
},
|
||||||
},
|
))
|
||||||
),
|
|
||||||
)
|
|
||||||
.layer(AddExtensionLayer::new(ws_reload_state.clone()));
|
.layer(AddExtensionLayer::new(ws_reload_state.clone()));
|
||||||
|
|
||||||
|
// start serve dev-server at 0.0.0.0:8080
|
||||||
let port = "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())
|
axum::Server::bind(&format!("0.0.0.0:{}", port).parse().unwrap())
|
||||||
.serve(app.into_make_service())
|
.serve(app.into_make_service())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +89,10 @@ async fn ws_handler(
|
|||||||
ws.on_upgrade(|mut socket| async move {
|
ws.on_upgrade(|mut socket| async move {
|
||||||
loop {
|
loop {
|
||||||
if state.lock().unwrap().update {
|
if state.lock().unwrap().update {
|
||||||
socket.send(Message::Text(String::from("reload"))).await.unwrap();
|
socket
|
||||||
|
.send(Message::Text(String::from("reload")))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
state.lock().unwrap().change();
|
state.lock().unwrap().change();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "{project}"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
dioxus = { version = "0.1.7", features = ["web"] }
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
dioxus::desktop::launch(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
cx.render(rsx! (
|
||||||
|
div {
|
||||||
|
style: "text-align: center;",
|
||||||
|
h2 { "🌗🚀 Dioxus" }
|
||||||
|
p { strong { "Frontend that scales." } }
|
||||||
|
p { "Dioxus is a portable, performant, and ergonomic framework for building cross-platform user interfaces in Rust." }
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
Reference in new issue
Block a user