Merge branch 'fullstack-serve' into intigrate-collect-assets
This commit is contained in:
commit
afb8578605
24 files changed
+421
-128
No files matched your search
@@ -0,0 +1,54 @@
|
|||||||
|
name: Build CLI for Release
|
||||||
|
|
||||||
|
# Will run automatically on every new release
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-upload:
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
runs-on: ${{ matrix.platform.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
platform:
|
||||||
|
- {
|
||||||
|
target: x86_64-pc-windows-msvc,
|
||||||
|
os: windows-latest,
|
||||||
|
toolchain: "1.70.0",
|
||||||
|
}
|
||||||
|
- {
|
||||||
|
target: x86_64-apple-darwin,
|
||||||
|
os: macos-latest,
|
||||||
|
toolchain: "1.70.0",
|
||||||
|
}
|
||||||
|
- {
|
||||||
|
target: x86_64-unknown-linux-gnu,
|
||||||
|
os: ubuntu-latest,
|
||||||
|
toolchain: "1.70.0",
|
||||||
|
}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Install stable
|
||||||
|
uses: dtolnay/rust-toolchain@master
|
||||||
|
with:
|
||||||
|
toolchain: ${{ matrix.platform.toolchain }}
|
||||||
|
targets: ${{ matrix.platform.target }}
|
||||||
|
|
||||||
|
# Setup the Github Actions Cache for the CLI package
|
||||||
|
- name: Setup cache
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
workspaces: packages/cli -> ../../target
|
||||||
|
|
||||||
|
# This neat action can build and upload the binary in one go!
|
||||||
|
- name: Build and upload binary
|
||||||
|
uses: taiki-e/upload-rust-binary-action@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
target: ${{ matrix.platform.target }}
|
||||||
|
bin: dx
|
||||||
|
archive: dx-${{ matrix.platform.target }}
|
||||||
|
checksum: sha256
|
||||||
|
manifest_path: packages/cli/Cargo.toml
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
<span> | </span>
|
<span> | </span>
|
||||||
<a href="https://github.com/DioxusLabs/example-projects"> Examples </a>
|
<a href="https://github.com/DioxusLabs/example-projects"> Examples </a>
|
||||||
<span> | </span>
|
<span> | </span>
|
||||||
<a href="https://dioxuslabs.com/docs/0.3/guide/en/"> Guide </a>
|
<a href="https://dioxuslabs.com/learn/0.4/guide"> Guide </a>
|
||||||
<span> | </span>
|
<span> | </span>
|
||||||
<a href="https://github.com/DioxusLabs/dioxus/blob/master/notes/README/ZH_CN.md"> 中文 </a>
|
<a href="https://github.com/DioxusLabs/dioxus/blob/master/notes/README/ZH_CN.md"> 中文 </a>
|
||||||
<span> | </span>
|
<span> | </span>
|
||||||
|
|||||||
@@ -89,7 +89,8 @@ fn app(cx: Scope) -> Element {
|
|||||||
key: None,
|
key: None,
|
||||||
// The static template this node will use. The template is stored in a Cell so it can be replaced with a new template when hot rsx reloading is enabled
|
// The static template this node will use. The template is stored in a Cell so it can be replaced with a new template when hot rsx reloading is enabled
|
||||||
template: std::cell::Cell::new(TEMPLATE),
|
template: std::cell::Cell::new(TEMPLATE),
|
||||||
root_ids: Default::default(),
|
root_ids: dioxus::core::exports::bumpalo::collections::Vec::new_in(__cx.bump())
|
||||||
|
.into(),
|
||||||
dynamic_nodes: __cx.bump().alloc([
|
dynamic_nodes: __cx.bump().alloc([
|
||||||
// The dynamic count text node (dynamic node id 0)
|
// The dynamic count text node (dynamic node id 0)
|
||||||
__cx.text_node(format_args!("High-Five counter: {0}", count)),
|
__cx.text_node(format_args!("High-Five counter: {0}", count)),
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ Any event handlers will still be called.
|
|||||||
|
|
||||||
> Normally, in React or JavaScript, you'd call "preventDefault" on the event in the callback. Dioxus does _not_ currently support this behavior. Note: this means you cannot conditionally prevent default behavior based on the data in the event.
|
> Normally, in React or JavaScript, you'd call "preventDefault" on the event in the callback. Dioxus does _not_ currently support this behavior. Note: this means you cannot conditionally prevent default behavior based on the data in the event.
|
||||||
|
|
||||||
|
> Note about forms: if an event handler is attached to the `onsubmit` event of a form, default behavior is to **not submit it**, meaning having `prevent_default: "onsubmit"` will submit it in this case.
|
||||||
|
|
||||||
## Handler Props
|
## Handler Props
|
||||||
|
|
||||||
Sometimes, you might want to make a component that accepts an event handler. A simple example would be a `FancyButton` component, which accepts an `on_click` handler:
|
Sometimes, you might want to make a component that accepts an event handler. A simple example would be a `FancyButton` component, which accepts an `on_click` handler:
|
||||||
|
|||||||
@@ -64,3 +64,5 @@ Então, você pode usá-lo como qualquer outro manipulador:
|
|||||||
> Nota: assim como qualquer outro atributo, você pode nomear os manipuladores como quiser! Embora eles devam começar com `on`, para que o prop seja automaticamente transformado em um `EventHandler` no local da chamada.
|
> Nota: assim como qualquer outro atributo, você pode nomear os manipuladores como quiser! Embora eles devam começar com `on`, para que o prop seja automaticamente transformado em um `EventHandler` no local da chamada.
|
||||||
>
|
>
|
||||||
> Você também pode colocar dados personalizados no evento, em vez de, por exemplo, `MouseData`
|
> Você também pode colocar dados personalizados no evento, em vez de, por exemplo, `MouseData`
|
||||||
|
|
||||||
|
> Nota sobre formulários: se um manipulador de evento está anexado ao evento `onsubmit` em um formulário, o comportamento padrão é de **não submetê-lo**. Portanto, especificar `prevent_default: "onsubmit"` irá submetê-lo.
|
||||||
@@ -102,3 +102,9 @@ name = "dx"
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.3"
|
tempfile = "3.3"
|
||||||
|
|
||||||
|
[package.metadata.binstall]
|
||||||
|
pkg-url = "{ repo }/releases/download/v{ version }/dx-{ target }{ archive-suffix }"
|
||||||
|
|
||||||
|
[package.metadata.binstall.overrides.x86_64-pc-windows-msvc]
|
||||||
|
pkg-fmt = "zip"
|
||||||
@@ -47,6 +47,32 @@ impl Build {
|
|||||||
Platform::Desktop => {
|
Platform::Desktop => {
|
||||||
crate::builder::build_desktop(&crate_config, false)?;
|
crate::builder::build_desktop(&crate_config, false)?;
|
||||||
}
|
}
|
||||||
|
Platform::Fullstack => {
|
||||||
|
{
|
||||||
|
let mut web_config = crate_config.clone();
|
||||||
|
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)?;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
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]),
|
||||||
|
};
|
||||||
|
crate::builder::build_desktop(&desktop_config, false)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let temp = gen_page(&crate_config, false);
|
let temp = gen_page(&crate_config, false);
|
||||||
|
|||||||
@@ -35,6 +35,30 @@ pub struct ConfigOptsBuild {
|
|||||||
/// Space separated list of features to activate
|
/// Space separated list of features to activate
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub features: Option<Vec<String>>,
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Parser)]
|
#[derive(Clone, Debug, Default, Deserialize, Parser)]
|
||||||
@@ -89,6 +113,14 @@ pub struct ConfigOptsServe {
|
|||||||
/// Space separated list of features to activate
|
/// Space separated list of features to activate
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub features: Option<Vec<String>>,
|
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,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize, Debug)]
|
||||||
@@ -99,6 +131,9 @@ pub enum Platform {
|
|||||||
#[clap(name = "desktop")]
|
#[clap(name = "desktop")]
|
||||||
#[serde(rename = "desktop")]
|
#[serde(rename = "desktop")]
|
||||||
Desktop,
|
Desktop,
|
||||||
|
#[clap(name = "fullstack")]
|
||||||
|
#[serde(rename = "fullstack")]
|
||||||
|
Fullstack,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Config options for the bundling system.
|
/// Config options for the bundling system.
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ pub struct Serve {
|
|||||||
impl Serve {
|
impl Serve {
|
||||||
pub async fn serve(self, bin: Option<PathBuf>) -> Result<()> {
|
pub async fn serve(self, bin: Option<PathBuf>) -> Result<()> {
|
||||||
let mut crate_config = crate::CrateConfig::new(bin)?;
|
let mut crate_config = crate::CrateConfig::new(bin)?;
|
||||||
|
let serve_cfg = self.serve.clone();
|
||||||
|
|
||||||
// change the relase state.
|
// change the relase state.
|
||||||
crate_config.with_hot_reload(self.serve.hot_reload);
|
crate_config.with_hot_reload(self.serve.hot_reload);
|
||||||
@@ -49,7 +50,10 @@ impl Serve {
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
cfg::Platform::Desktop => {
|
cfg::Platform::Desktop => {
|
||||||
server::desktop::startup(crate_config.clone()).await?;
|
server::desktop::startup(crate_config.clone(), &serve_cfg).await?;
|
||||||
|
}
|
||||||
|
cfg::Platform::Fullstack => {
|
||||||
|
server::fullstack::startup(crate_config.clone(), &serve_cfg).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
|
cfg::ConfigOptsServe,
|
||||||
server::{
|
server::{
|
||||||
output::{print_console_info, PrettierOptions},
|
output::{print_console_info, PrettierOptions},
|
||||||
setup_file_watcher, setup_file_watcher_hot_reload,
|
setup_file_watcher, setup_file_watcher_hot_reload,
|
||||||
@@ -19,7 +20,16 @@ use tokio::sync::broadcast::{self};
|
|||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
use plugin::PluginManager;
|
use plugin::PluginManager;
|
||||||
|
|
||||||
pub async fn startup(config: CrateConfig) -> Result<()> {
|
use super::Platform;
|
||||||
|
|
||||||
|
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: &ConfigOptsServe,
|
||||||
|
) -> Result<()> {
|
||||||
// ctrl-c shutdown checker
|
// ctrl-c shutdown checker
|
||||||
let _crate_config = config.clone();
|
let _crate_config = config.clone();
|
||||||
let _ = ctrlc::set_handler(move || {
|
let _ = ctrlc::set_handler(move || {
|
||||||
@@ -29,17 +39,19 @@ pub async fn startup(config: CrateConfig) -> Result<()> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
match config.hot_reload {
|
match config.hot_reload {
|
||||||
true => serve_hot_reload(config).await?,
|
true => serve_hot_reload::<P>(config, serve).await?,
|
||||||
false => serve_default(config).await?,
|
false => serve_default::<P>(config, serve).await?,
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start the server without hot reload
|
/// Start the server without hot reload
|
||||||
pub async fn serve_default(config: CrateConfig) -> Result<()> {
|
async fn serve_default<P: Platform + Send + 'static>(
|
||||||
let (child, first_build_result) = start_desktop(&config)?;
|
config: CrateConfig,
|
||||||
let currently_running_child: RwLock<Child> = RwLock::new(child);
|
serve: &ConfigOptsServe,
|
||||||
|
) -> Result<()> {
|
||||||
|
let platform = RwLock::new(P::start(&config, serve)?);
|
||||||
|
|
||||||
log::info!("🚀 Starting development server...");
|
log::info!("🚀 Starting development server...");
|
||||||
|
|
||||||
@@ -49,49 +61,29 @@ pub async fn serve_default(config: CrateConfig) -> Result<()> {
|
|||||||
{
|
{
|
||||||
let config = config.clone();
|
let config = config.clone();
|
||||||
|
|
||||||
move || {
|
move || platform.write().unwrap().rebuild(&config)
|
||||||
let mut current_child = currently_running_child.write().unwrap();
|
|
||||||
current_child.kill()?;
|
|
||||||
let (child, result) = start_desktop(&config)?;
|
|
||||||
*current_child = child;
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
&config,
|
&config,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Print serve info
|
|
||||||
print_console_info(
|
|
||||||
&config,
|
|
||||||
PrettierOptions {
|
|
||||||
changed: vec![],
|
|
||||||
warnings: first_build_result.warnings,
|
|
||||||
elapsed_time: first_build_result.elapsed_time,
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
|
|
||||||
std::future::pending::<()>().await;
|
std::future::pending::<()>().await;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start the server without hot reload
|
|
||||||
|
|
||||||
/// Start dx serve with hot reload
|
/// Start dx serve with hot reload
|
||||||
pub async fn serve_hot_reload(config: CrateConfig) -> Result<()> {
|
async fn serve_hot_reload<P: Platform + Send + 'static>(
|
||||||
let (_, first_build_result) = start_desktop(&config)?;
|
config: CrateConfig,
|
||||||
|
serve: &ConfigOptsServe,
|
||||||
println!("🚀 Starting development server...");
|
) -> Result<()> {
|
||||||
|
let platform = RwLock::new(P::start(&config, serve)?);
|
||||||
|
|
||||||
// Setup hot reload
|
// Setup hot reload
|
||||||
let FileMapBuildResult { map, errors } =
|
let FileMapBuildResult { map, errors } =
|
||||||
FileMap::<HtmlCtx>::create(config.crate_dir.clone()).unwrap();
|
FileMap::<HtmlCtx>::create(config.crate_dir.clone()).unwrap();
|
||||||
|
|
||||||
println!("🚀 Starting development server...");
|
|
||||||
|
|
||||||
for err in errors {
|
for err in errors {
|
||||||
log::error!("{}", err);
|
log::error!("{}", err);
|
||||||
}
|
}
|
||||||
@@ -119,24 +111,13 @@ pub async fn serve_hot_reload(config: CrateConfig) -> Result<()> {
|
|||||||
for channel in &mut *channels.lock().unwrap() {
|
for channel in &mut *channels.lock().unwrap() {
|
||||||
send_msg(HotReloadMsg::Shutdown, channel);
|
send_msg(HotReloadMsg::Shutdown, channel);
|
||||||
}
|
}
|
||||||
Ok(start_desktop(&config)?.1)
|
platform.write().unwrap().rebuild(&config)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Print serve info
|
|
||||||
print_console_info(
|
|
||||||
&config,
|
|
||||||
PrettierOptions {
|
|
||||||
changed: vec![],
|
|
||||||
warnings: first_build_result.warnings,
|
|
||||||
elapsed_time: first_build_result.elapsed_time,
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
|
|
||||||
clear_paths();
|
clear_paths();
|
||||||
|
|
||||||
match LocalSocketListener::bind("@dioxusin") {
|
match LocalSocketListener::bind("@dioxusin") {
|
||||||
@@ -228,7 +209,7 @@ 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) -> Result<(Child, BuildResult)> {
|
||||||
// Run the desktop application
|
// Run the desktop application
|
||||||
let result = crate::builder::build_desktop(config, true)?;
|
let result = crate::builder::build_desktop(config, true)?;
|
||||||
|
|
||||||
@@ -249,3 +230,37 @@ pub fn start_desktop(config: &CrateConfig) -> Result<(Child, BuildResult)> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) struct DesktopPlatform {
|
||||||
|
currently_running_child: Child,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Platform for DesktopPlatform {
|
||||||
|
fn start(config: &CrateConfig, _serve: &ConfigOptsServe) -> Result<Self> {
|
||||||
|
let (child, first_build_result) = start_desktop(config)?;
|
||||||
|
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rebuild(&mut self, config: &CrateConfig) -> Result<BuildResult> {
|
||||||
|
self.currently_running_child.kill()?;
|
||||||
|
let (child, result) = start_desktop(config)?;
|
||||||
|
self.currently_running_child = child;
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
use crate::{
|
||||||
|
cfg::{ConfigOptsBuild, ConfigOptsServe},
|
||||||
|
CrateConfig, Result,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{desktop, Platform};
|
||||||
|
|
||||||
|
pub async fn startup(config: CrateConfig, serve: &ConfigOptsServe) -> Result<()> {
|
||||||
|
desktop::startup_with_platform::<FullstackPlatform>(config, serve).await
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FullstackPlatform {
|
||||||
|
serve: ConfigOptsServe,
|
||||||
|
desktop: desktop::DesktopPlatform,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Platform for FullstackPlatform {
|
||||||
|
fn start(config: &CrateConfig, serve: &ConfigOptsServe) -> Result<Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
{
|
||||||
|
build_web(serve.clone())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 desktop = desktop::DesktopPlatform::start(&desktop_config, serve)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
desktop,
|
||||||
|
serve: serve.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rebuild(&mut self, crate_config: &CrateConfig) -> Result<crate::BuildResult> {
|
||||||
|
build_web(self.serve.clone())?;
|
||||||
|
{
|
||||||
|
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]),
|
||||||
|
};
|
||||||
|
self.desktop.rebuild(&desktop_config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_web(serve: ConfigOptsServe) -> 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);
|
||||||
|
crate::cli::build::Build { build: web_config }.build(None)
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::{BuildResult, CrateConfig, Result};
|
use crate::{cfg::ConfigOptsServe, BuildResult, CrateConfig, Result};
|
||||||
|
|
||||||
use cargo_metadata::diagnostic::Diagnostic;
|
use cargo_metadata::diagnostic::Diagnostic;
|
||||||
use dioxus_core::Template;
|
use dioxus_core::Template;
|
||||||
@@ -14,6 +14,7 @@ use tokio::sync::broadcast::Sender;
|
|||||||
mod output;
|
mod output;
|
||||||
use output::*;
|
use output::*;
|
||||||
pub mod desktop;
|
pub mod desktop;
|
||||||
|
pub mod fullstack;
|
||||||
pub mod web;
|
pub mod web;
|
||||||
|
|
||||||
/// Sets up a file watcher
|
/// Sets up a file watcher
|
||||||
@@ -180,3 +181,10 @@ async fn setup_file_watcher_hot_reload<F: Fn() -> Result<BuildResult> + Send + '
|
|||||||
|
|
||||||
Ok(watcher)
|
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>;
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ use crate::{
|
|||||||
output::{print_console_info, PrettierOptions, WebServerInfo},
|
output::{print_console_info, PrettierOptions, WebServerInfo},
|
||||||
setup_file_watcher, setup_file_watcher_hot_reload,
|
setup_file_watcher, setup_file_watcher_hot_reload,
|
||||||
},
|
},
|
||||||
BuildResult, CrateConfig, Result,
|
BuildResult, CrateConfig, Result, WebHttpsConfig,
|
||||||
};
|
};
|
||||||
use axum::{
|
use axum::{
|
||||||
body::{Full, HttpBody},
|
body::{Full, HttpBody},
|
||||||
@@ -67,7 +67,7 @@ pub async fn startup(port: u16, config: CrateConfig, start_browser: bool) -> Res
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Start the server without hot reload
|
/// Start the server without hot reload
|
||||||
pub async fn serve_default(
|
async fn serve_default(
|
||||||
ip: String,
|
ip: String,
|
||||||
port: u16,
|
port: u16,
|
||||||
config: CrateConfig,
|
config: CrateConfig,
|
||||||
@@ -128,7 +128,7 @@ pub async fn serve_default(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Start dx serve with hot reload
|
/// Start dx serve with hot reload
|
||||||
pub async fn serve_hot_reload(
|
async fn serve_hot_reload(
|
||||||
ip: String,
|
ip: String,
|
||||||
port: u16,
|
port: u16,
|
||||||
config: CrateConfig,
|
config: CrateConfig,
|
||||||
@@ -218,67 +218,12 @@ async fn get_rustls(config: &CrateConfig) -> Result<Option<RustlsConfig>> {
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (cert_path, key_path) = match web_config.mkcert {
|
let (cert_path, key_path) = if let Some(true) = web_config.mkcert {
|
||||||
// mkcert, use it
|
// mkcert, use it
|
||||||
Some(true) => {
|
get_rustls_with_mkcert(web_config)?
|
||||||
// Get paths to store certs, otherwise use ssl/item.pem
|
} else {
|
||||||
let key_path = web_config
|
// if mkcert not specified or false, don't use it
|
||||||
.key_path
|
get_rustls_without_mkcert(web_config)?
|
||||||
.clone()
|
|
||||||
.unwrap_or(DEFAULT_KEY_PATH.to_string());
|
|
||||||
|
|
||||||
let cert_path = web_config
|
|
||||||
.cert_path
|
|
||||||
.clone()
|
|
||||||
.unwrap_or(DEFAULT_CERT_PATH.to_string());
|
|
||||||
|
|
||||||
// Create ssl directory if using defaults
|
|
||||||
if key_path == DEFAULT_KEY_PATH && cert_path == DEFAULT_CERT_PATH {
|
|
||||||
_ = fs::create_dir("ssl");
|
|
||||||
}
|
|
||||||
|
|
||||||
let cmd = Command::new("mkcert")
|
|
||||||
.args([
|
|
||||||
"-install",
|
|
||||||
"-key-file",
|
|
||||||
&key_path,
|
|
||||||
"-cert-file",
|
|
||||||
&cert_path,
|
|
||||||
"localhost",
|
|
||||||
"::1",
|
|
||||||
"127.0.0.1",
|
|
||||||
])
|
|
||||||
.spawn();
|
|
||||||
|
|
||||||
match cmd {
|
|
||||||
Err(e) => {
|
|
||||||
match e.kind() {
|
|
||||||
io::ErrorKind::NotFound => log::error!("mkcert is not installed. See https://github.com/FiloSottile/mkcert#installation for installation instructions."),
|
|
||||||
e => log::error!("an error occured while generating mkcert certificates: {}", e.to_string()),
|
|
||||||
};
|
|
||||||
return Err("failed to generate mkcert certificates".into());
|
|
||||||
}
|
|
||||||
Ok(mut cmd) => {
|
|
||||||
cmd.wait()?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(cert_path, key_path)
|
|
||||||
}
|
|
||||||
// not mkcert
|
|
||||||
Some(false) => {
|
|
||||||
// get paths to cert & key
|
|
||||||
if let (Some(key), Some(cert)) =
|
|
||||||
(web_config.key_path.clone(), web_config.cert_path.clone())
|
|
||||||
{
|
|
||||||
(cert, key)
|
|
||||||
} else {
|
|
||||||
// missing cert or key
|
|
||||||
return Err("https is enabled but cert or key path is missing".into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// other
|
|
||||||
_ => return Ok(None),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Some(
|
Ok(Some(
|
||||||
@@ -286,6 +231,62 @@ async fn get_rustls(config: &CrateConfig) -> Result<Option<RustlsConfig>> {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_rustls_with_mkcert(web_config: &WebHttpsConfig) -> Result<(String, String)> {
|
||||||
|
// Get paths to store certs, otherwise use ssl/item.pem
|
||||||
|
let key_path = web_config
|
||||||
|
.key_path
|
||||||
|
.clone()
|
||||||
|
.unwrap_or(DEFAULT_KEY_PATH.to_string());
|
||||||
|
|
||||||
|
let cert_path = web_config
|
||||||
|
.cert_path
|
||||||
|
.clone()
|
||||||
|
.unwrap_or(DEFAULT_CERT_PATH.to_string());
|
||||||
|
|
||||||
|
// Create ssl directory if using defaults
|
||||||
|
if key_path == DEFAULT_KEY_PATH && cert_path == DEFAULT_CERT_PATH {
|
||||||
|
_ = fs::create_dir("ssl");
|
||||||
|
}
|
||||||
|
|
||||||
|
let cmd = Command::new("mkcert")
|
||||||
|
.args([
|
||||||
|
"-install",
|
||||||
|
"-key-file",
|
||||||
|
&key_path,
|
||||||
|
"-cert-file",
|
||||||
|
&cert_path,
|
||||||
|
"localhost",
|
||||||
|
"::1",
|
||||||
|
"127.0.0.1",
|
||||||
|
])
|
||||||
|
.spawn();
|
||||||
|
|
||||||
|
match cmd {
|
||||||
|
Err(e) => {
|
||||||
|
match e.kind() {
|
||||||
|
io::ErrorKind::NotFound => log::error!("mkcert is not installed. See https://github.com/FiloSottile/mkcert#installation for installation instructions."),
|
||||||
|
e => log::error!("an error occured while generating mkcert certificates: {}", e.to_string()),
|
||||||
|
};
|
||||||
|
return Err("failed to generate mkcert certificates".into());
|
||||||
|
}
|
||||||
|
Ok(mut cmd) => {
|
||||||
|
cmd.wait()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((cert_path, key_path))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_rustls_without_mkcert(web_config: &WebHttpsConfig) -> Result<(String, String)> {
|
||||||
|
// get paths to cert & key
|
||||||
|
if let (Some(key), Some(cert)) = (web_config.key_path.clone(), web_config.cert_path.clone()) {
|
||||||
|
Ok((cert, key))
|
||||||
|
} else {
|
||||||
|
// missing cert or key
|
||||||
|
Err("https is enabled but cert or key path is missing".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets up and returns a router
|
/// Sets up and returns a router
|
||||||
async fn setup_router(
|
async fn setup_router(
|
||||||
config: CrateConfig,
|
config: CrateConfig,
|
||||||
|
|||||||
@@ -87,8 +87,12 @@ impl<'b> VirtualDom {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Intialize the root nodes slice
|
// Initialize the root nodes slice
|
||||||
*node.root_ids.borrow_mut() = vec![ElementId(0); node.template.get().roots.len()];
|
{
|
||||||
|
let mut nodes_mut = node.root_ids.borrow_mut();
|
||||||
|
let len = node.template.get().roots.len();
|
||||||
|
nodes_mut.resize(len, ElementId::default());
|
||||||
|
};
|
||||||
|
|
||||||
// The best renderers will have templates prehydrated and registered
|
// The best renderers will have templates prehydrated and registered
|
||||||
// Just in case, let's create the template using instructions anyways
|
// Just in case, let's create the template using instructions anyways
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ use DynamicNode::*;
|
|||||||
|
|
||||||
impl<'b> VirtualDom {
|
impl<'b> VirtualDom {
|
||||||
pub(super) fn diff_scope(&mut self, scope: ScopeId) {
|
pub(super) fn diff_scope(&mut self, scope: ScopeId) {
|
||||||
|
self.runtime.scope_stack.borrow_mut().push(scope);
|
||||||
let scope_state = &mut self.get_scope(scope).unwrap();
|
let scope_state = &mut self.get_scope(scope).unwrap();
|
||||||
unsafe {
|
unsafe {
|
||||||
// Load the old and new bump arenas
|
// Load the old and new bump arenas
|
||||||
@@ -45,6 +46,7 @@ impl<'b> VirtualDom {
|
|||||||
(Aborted(l), Ready(r)) => self.replace_placeholder(l, [r]),
|
(Aborted(l), Ready(r)) => self.replace_placeholder(l, [r]),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
self.runtime.scope_stack.borrow_mut().pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn diff_ok_to_err(&mut self, l: &'b VNode<'b>, p: &'b VPlaceholder) {
|
fn diff_ok_to_err(&mut self, l: &'b VNode<'b>, p: &'b VPlaceholder) {
|
||||||
@@ -126,7 +128,13 @@ impl<'b> VirtualDom {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Make sure the roots get transferred over while we're here
|
// Make sure the roots get transferred over while we're here
|
||||||
*right_template.root_ids.borrow_mut() = left_template.root_ids.borrow().clone();
|
{
|
||||||
|
let mut right = right_template.root_ids.borrow_mut();
|
||||||
|
right.clear();
|
||||||
|
for &element in left_template.root_ids.borrow().iter() {
|
||||||
|
right.push(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let root_ids = right_template.root_ids.borrow();
|
let root_ids = right_template.root_ids.borrow();
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ pub struct VNode<'a> {
|
|||||||
|
|
||||||
/// The IDs for the roots of this template - to be used when moving the template around and removing it from
|
/// The IDs for the roots of this template - to be used when moving the template around and removing it from
|
||||||
/// the actual Dom
|
/// the actual Dom
|
||||||
pub root_ids: RefCell<Vec<ElementId>>,
|
pub root_ids: RefCell<bumpalo::collections::Vec<'a, ElementId>>,
|
||||||
|
|
||||||
/// The dynamic parts of the template
|
/// The dynamic parts of the template
|
||||||
pub dynamic_nodes: &'a [DynamicNode<'a>],
|
pub dynamic_nodes: &'a [DynamicNode<'a>],
|
||||||
@@ -65,11 +65,11 @@ pub struct VNode<'a> {
|
|||||||
|
|
||||||
impl<'a> VNode<'a> {
|
impl<'a> VNode<'a> {
|
||||||
/// Create a template with no nodes that will be skipped over during diffing
|
/// Create a template with no nodes that will be skipped over during diffing
|
||||||
pub fn empty() -> Element<'a> {
|
pub fn empty(cx: &'a ScopeState) -> Element<'a> {
|
||||||
Some(VNode {
|
Some(VNode {
|
||||||
key: None,
|
key: None,
|
||||||
parent: None,
|
parent: None,
|
||||||
root_ids: Default::default(),
|
root_ids: RefCell::new(bumpalo::collections::Vec::new_in(cx.bump())),
|
||||||
dynamic_nodes: &[],
|
dynamic_nodes: &[],
|
||||||
dynamic_attrs: &[],
|
dynamic_attrs: &[],
|
||||||
template: Cell::new(Template {
|
template: Cell::new(Template {
|
||||||
@@ -698,7 +698,7 @@ impl<'a, 'b> IntoDynNode<'a> for LazyNodes<'a, 'b> {
|
|||||||
impl<'a, 'b> IntoDynNode<'b> for &'a str {
|
impl<'a, 'b> IntoDynNode<'b> for &'a str {
|
||||||
fn into_vnode(self, cx: &'b ScopeState) -> DynamicNode<'b> {
|
fn into_vnode(self, cx: &'b ScopeState) -> DynamicNode<'b> {
|
||||||
DynamicNode::Text(VText {
|
DynamicNode::Text(VText {
|
||||||
value: bumpalo::collections::String::from_str_in(self, cx.bump()).into_bump_str(),
|
value: cx.bump().alloc_str(self),
|
||||||
id: Default::default(),
|
id: Default::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -741,10 +741,10 @@ impl<'a> IntoTemplate<'a> for VNode<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<'a> IntoTemplate<'a> for Element<'a> {
|
impl<'a> IntoTemplate<'a> for Element<'a> {
|
||||||
fn into_template(self, _cx: &'a ScopeState) -> VNode<'a> {
|
fn into_template(self, cx: &'a ScopeState) -> VNode<'a> {
|
||||||
match self {
|
match self {
|
||||||
Some(val) => val.into_template(_cx),
|
Some(val) => val.into_template(cx),
|
||||||
_ => VNode::empty().unwrap(),
|
_ => VNode::empty(cx).unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ fn create_random_dynamic_node(cx: &ScopeState, depth: usize) -> DynamicNode {
|
|||||||
node_paths: &[&[0]],
|
node_paths: &[&[0]],
|
||||||
attr_paths: &[],
|
attr_paths: &[],
|
||||||
}),
|
}),
|
||||||
root_ids: Default::default(),
|
root_ids: bumpalo::collections::Vec::new_in(cx.bump()).into(),
|
||||||
dynamic_nodes: cx.bump().alloc([cx.component(
|
dynamic_nodes: cx.bump().alloc([cx.component(
|
||||||
create_random_element,
|
create_random_element,
|
||||||
DepthProps { depth, root: false },
|
DepthProps { depth, root: false },
|
||||||
@@ -276,7 +276,7 @@ fn create_random_element(cx: Scope<DepthProps>) -> Element {
|
|||||||
key: None,
|
key: None,
|
||||||
parent: None,
|
parent: None,
|
||||||
template: Cell::new(template),
|
template: Cell::new(template),
|
||||||
root_ids: Default::default(),
|
root_ids: bumpalo::collections::Vec::new_in(cx.bump()).into(),
|
||||||
dynamic_nodes: {
|
dynamic_nodes: {
|
||||||
let dynamic_nodes: Vec<_> = dynamic_node_types
|
let dynamic_nodes: Vec<_> = dynamic_node_types
|
||||||
.iter()
|
.iter()
|
||||||
|
|||||||
@@ -282,6 +282,20 @@ impl<T> UseSharedState<T> {
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Take a reference to the inner value temporarily and produce a new value
|
||||||
|
#[cfg_attr(debug_assertions, track_caller)]
|
||||||
|
#[cfg_attr(debug_assertions, inline(never))]
|
||||||
|
pub fn with<O>(&self, immutable_callback: impl FnOnce(&T) -> O) -> O {
|
||||||
|
immutable_callback(&*self.read())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Take a mutable reference to the inner value temporarily and produce a new value
|
||||||
|
#[cfg_attr(debug_assertions, track_caller)]
|
||||||
|
#[cfg_attr(debug_assertions, inline(never))]
|
||||||
|
pub fn with_mut<O>(&self, mutable_callback: impl FnOnce(&mut T) -> O) -> O {
|
||||||
|
mutable_callback(&mut *self.write())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Clone for UseSharedState<T> {
|
impl<T> Clone for UseSharedState<T> {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::{collections::HashMap, fmt::Debug};
|
use std::{any::Any, collections::HashMap, fmt::Debug};
|
||||||
|
|
||||||
use dioxus_core::Event;
|
use dioxus_core::Event;
|
||||||
|
|
||||||
@@ -45,6 +45,12 @@ impl FileEngine for SerializedFileEngine {
|
|||||||
.await
|
.await
|
||||||
.map(|bytes| String::from_utf8_lossy(&bytes).to_string())
|
.map(|bytes| String::from_utf8_lossy(&bytes).to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_native_file(&self, file: &str) -> Option<Box<dyn Any>> {
|
||||||
|
self.read_file(file)
|
||||||
|
.await
|
||||||
|
.map(|val| Box::new(val) as Box<dyn Any>)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "serialize")]
|
#[cfg(feature = "serialize")]
|
||||||
@@ -89,6 +95,9 @@ pub trait FileEngine {
|
|||||||
|
|
||||||
// read a file to string
|
// read a file to string
|
||||||
async fn read_file_to_string(&self, file: &str) -> Option<String>;
|
async fn read_file_to_string(&self, file: &str) -> Option<String>;
|
||||||
|
|
||||||
|
// returns a file in platform's native representation
|
||||||
|
async fn get_native_file(&self, file: &str) -> Option<Box<dyn Any>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_event! {
|
impl_event! {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use std::any::Any;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::FileEngine;
|
use crate::FileEngine;
|
||||||
@@ -40,4 +41,9 @@ impl FileEngine for NativeFileEngine {
|
|||||||
|
|
||||||
Some(contents)
|
Some(contents)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_native_file(&self, file: &str) -> Option<Box<dyn Any>> {
|
||||||
|
let file = File::open(file).await.ok()?;
|
||||||
|
Some(Box::new(file))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -187,7 +187,7 @@ fn create_random_dynamic_node(cx: &ScopeState, depth: usize) -> DynamicNode {
|
|||||||
node_paths: &[&[0]],
|
node_paths: &[&[0]],
|
||||||
attr_paths: &[],
|
attr_paths: &[],
|
||||||
}),
|
}),
|
||||||
root_ids: Default::default(),
|
root_ids: dioxus::core::exports::bumpalo::collections::Vec::new_in(cx.bump()).into(),
|
||||||
dynamic_nodes: cx.bump().alloc([cx.component(
|
dynamic_nodes: cx.bump().alloc([cx.component(
|
||||||
create_random_element,
|
create_random_element,
|
||||||
DepthProps { depth, root: false },
|
DepthProps { depth, root: false },
|
||||||
@@ -257,7 +257,8 @@ fn create_random_element(cx: Scope<DepthProps>) -> Element {
|
|||||||
key: None,
|
key: None,
|
||||||
parent: None,
|
parent: None,
|
||||||
template: Cell::new(template),
|
template: Cell::new(template),
|
||||||
root_ids: Default::default(),
|
root_ids: dioxus::core::exports::bumpalo::collections::Vec::new_in(cx.bump())
|
||||||
|
.into(),
|
||||||
dynamic_nodes: {
|
dynamic_nodes: {
|
||||||
let dynamic_nodes: Vec<_> = dynamic_node_types
|
let dynamic_nodes: Vec<_> = dynamic_node_types
|
||||||
.iter()
|
.iter()
|
||||||
|
|||||||
@@ -231,6 +231,7 @@ impl<'a> ToTokens for TemplateRenderer<'a> {
|
|||||||
|
|
||||||
// Render and release the mutable borrow on context
|
// Render and release the mutable borrow on context
|
||||||
let roots = quote! { #( #root_printer ),* };
|
let roots = quote! { #( #root_printer ),* };
|
||||||
|
let root_count = self.roots.len();
|
||||||
let node_printer = &context.dynamic_nodes;
|
let node_printer = &context.dynamic_nodes;
|
||||||
let dyn_attr_printer = &context.dynamic_attributes;
|
let dyn_attr_printer = &context.dynamic_attributes;
|
||||||
let node_paths = context.node_paths.iter().map(|it| quote!(&[#(#it),*]));
|
let node_paths = context.node_paths.iter().map(|it| quote!(&[#(#it),*]));
|
||||||
@@ -247,7 +248,7 @@ impl<'a> ToTokens for TemplateRenderer<'a> {
|
|||||||
parent: None,
|
parent: None,
|
||||||
key: #key_tokens,
|
key: #key_tokens,
|
||||||
template: std::cell::Cell::new(TEMPLATE),
|
template: std::cell::Cell::new(TEMPLATE),
|
||||||
root_ids: Default::default(),
|
root_ids: dioxus::core::exports::bumpalo::collections::Vec::with_capacity_in(#root_count, __cx.bump()).into(),
|
||||||
dynamic_nodes: __cx.bump().alloc([ #( #node_printer ),* ]),
|
dynamic_nodes: __cx.bump().alloc([ #( #node_printer ),* ]),
|
||||||
dynamic_attrs: __cx.bump().alloc([ #( #dyn_attr_printer ),* ]),
|
dynamic_attrs: __cx.bump().alloc([ #( #dyn_attr_printer ),* ]),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use std::any::Any;
|
||||||
|
|
||||||
use dioxus_html::FileEngine;
|
use dioxus_html::FileEngine;
|
||||||
use futures_channel::oneshot;
|
use futures_channel::oneshot;
|
||||||
use js_sys::Uint8Array;
|
use js_sys::Uint8Array;
|
||||||
@@ -100,4 +102,25 @@ impl FileEngine for WebFileEngine {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_native_file(&self, file: &str) -> Option<Box<dyn Any>> {
|
||||||
|
let file = self.find(file)?;
|
||||||
|
Some(Box::new(file))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper trait for WebFileEngine
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
pub trait WebFileEngineExt {
|
||||||
|
/// returns web_sys::File
|
||||||
|
async fn get_web_file(&self, file: &str) -> Option<web_sys::File>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl WebFileEngineExt for std::sync::Arc<dyn FileEngine> {
|
||||||
|
async fn get_web_file(&self, file: &str) -> Option<web_sys::File> {
|
||||||
|
let native_file = self.get_native_file(file).await?;
|
||||||
|
let ret = native_file.downcast::<web_sys::File>().ok()?;
|
||||||
|
Some(*ret)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -54,6 +54,7 @@
|
|||||||
// - Do DOM work in the next requestAnimationFrame callback
|
// - Do DOM work in the next requestAnimationFrame callback
|
||||||
|
|
||||||
pub use crate::cfg::Config;
|
pub use crate::cfg::Config;
|
||||||
|
pub use crate::file_engine::WebFileEngineExt;
|
||||||
use dioxus_core::{Element, Scope, VirtualDom};
|
use dioxus_core::{Element, Scope, VirtualDom};
|
||||||
use futures_util::{
|
use futures_util::{
|
||||||
future::{select, Either},
|
future::{select, Either},
|
||||||
|
|||||||
Reference in new issue
Block a user