Merge branch 'master' into cli-serde-default

This commit is contained in:
ealmloff authored and GitHub committed 2023-11-10 16:22:53 -06:00
commit 79e3fe677f
159 files changed
+2545 -6250

No files matched your search

+4
View File
@@ -1,3 +1,7 @@
#![doc = include_str!("../README.md")]
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
use std::fmt::{Display, Write};
use crate::writer::*;
-3
View File
@@ -11,13 +11,10 @@ keywords = ["dom", "ui", "gui", "react"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dioxus-rsx = { workspace = true }
proc-macro2 = { version = "1.0.6", features = ["span-locations"] }
quote = "1.0"
syn = { version = "1.0.11", features = ["full", "extra-traits", "visit"] }
serde = { version = "1.0.136", features = ["derive"] }
owo-colors = { version = "3.5.0", features = ["supports-colors"] }
prettyplease = { workspace = true }
[dev-dependencies]
indoc = "2.0.3"
+2 -2
View File
@@ -6,7 +6,7 @@
[![Discord chat][discord-badge]][discord-url]
[crates-badge]: https://img.shields.io/crates/v/dioxus-autofmt.svg
[crates-url]: https://crates.io/crates/dioxus-autofmt
[crates-url]: https://crates.io/crates/dioxus-check
[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
[mit-url]: https://github.com/dioxuslabs/dioxus/blob/master/LICENSE
[actions-badge]: https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg
@@ -16,7 +16,7 @@
[Website](https://dioxuslabs.com) |
[Guides](https://dioxuslabs.com/learn/0.4/) |
[API Docs](https://docs.rs/dioxus-autofmt/latest/dioxus_autofmt) |
[API Docs](https://docs.rs/dioxus-check) |
[Chat](https://discord.gg/XgGxMSkvUM)
## Overview
+4
View File
@@ -1,3 +1,7 @@
#![doc = include_str!("../README.md")]
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
mod check;
mod issues;
mod metadata;
-31
View File
@@ -1,31 +0,0 @@
# .github/workflows/build.yml
on:
release:
types: [created]
jobs:
release:
name: release ${{ matrix.target }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-unknown-linux-gnu
archive: tar.gz tar.xz
- target: x86_64-unknown-linux-musl
archive: tar.gz tar.xz
- target: x86_64-apple-darwin
archive: tar.gz tar.xz
- target: x86_64-pc-windows-gnu
archive: zip
steps:
- uses: actions/checkout@master
- name: Compile and release
uses: rust-build/rust-build.action@latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RUSTTARGET: ${{ matrix.target }}
ARCHIVE_TYPES: ${{ matrix.archive }}
-34
View File
@@ -1,34 +0,0 @@
name: github pages
on:
push:
paths:
- docs/**
branches:
- master
jobs:
deploy:
runs-on: ubuntu-20.04
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
steps:
- uses: actions/checkout@v2
- name: Setup mdBook
uses: peaceiris/actions-mdbook@v1
with:
mdbook-version: '0.4.10'
# mdbook-version: 'latest'
- run: cd docs && mdbook build
- name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@v4.2.3
with:
branch: gh-pages # The branch the action should deploy to.
folder: docs/book # The folder the action should deploy.
target-folder: docs/nightly/cli
repository-name: dioxuslabs/docsite
clean: false
token: ${{ secrets.DEPLOY_KEY }} # let's pretend I don't need it for now
-52
View File
@@ -1,52 +0,0 @@
on: [push, pull_request]
name: Rust CI
jobs:
check:
name: Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v1
- run: cargo check
test:
name: Test Suite
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v1
- run: cargo test
fmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v1
- run: rustup component add rustfmt
- run: cargo fmt --all -- --check
# clippy:
# name: Clippy
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v2
# - uses: actions-rs/toolchain@v1
# with:
# profile: minimal
# toolchain: stable
# override: true
# - uses: Swatinem/rust-cache@v1
# - run: rustup component add clippy
# - uses: actions-rs/cargo@v1
# with:
# command: clippy
# args: -- -D warnings
-4778
View File
File diff suppressed because it is too large. Load diff
+2 -7
View File
@@ -22,16 +22,13 @@ serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0.79"
toml = "0.5.8"
fs_extra = "1.2.0"
cargo_toml = "0.11.4"
cargo_toml = "0.16.0"
futures = "0.3.21"
notify = { version = "5.0.0-pre.16", features = ["serde"] }
html_parser = { workspace = true }
binary-install = "0.0.2"
convert_case = "0.5.0"
cargo_metadata = "0.15.0"
tokio = { version = "1.16.1", features = ["full"] }
tokio = { version = "1.16.1", features = ["fs", "sync", "rt", "macros"] }
atty = "0.2.14"
regex = "1.5.4"
chrono = "0.4.19"
anyhow = "1.0.53"
hyper = "0.14.17"
@@ -59,7 +56,6 @@ tar = "0.4.38"
zip = "0.6.2"
tower = "0.4.12"
syn = { version = "2.0", features = ["full", "extra-traits"] }
proc-macro2 = { version = "1.0", features = ["span-locations"] }
lazy_static = "1.4.0"
# plugin packages
@@ -71,7 +67,6 @@ mlua = { version = "0.8.1", features = [
"macros",
], optional = true }
ctrlc = "3.2.3"
gitignore = "1.0.7"
open = "4.1.0"
cargo-generate = "0.18"
toml_edit = "0.19.11"
+2 -2
View File
@@ -4,14 +4,14 @@
</div>
The **dioxus-cli** (inspired by wasm-pack and webpack) is a tool for getting Dioxus projects up and running.
It handles all building, bundling, development and publishing to simplify development.
It handles building, bundling, development and publishing to simplify development.
## Installation
### Install the stable version (recommended)
```
cargo install dioxus-cli --locked
cargo install dioxus-cli
```
### Install the latest development build through git
-1
View File
@@ -1 +0,0 @@
book
-6
View File
@@ -1,6 +0,0 @@
[book]
authors = ["YuKun Liu"]
language = "en"
multilingual = false
src = "src"
title = "Dioxus CLI"
-13
View File
@@ -1,13 +0,0 @@
# Summary
- [Introduction](./introduction.md)
- [Installation](./installation.md)
- [Create a project](./creating.md)
- [Configure a project](./configure.md)
- [Plugin development](./plugin/README.md)
- [API.Log](plugin/interface/log.md)
- [API.Command](plugin/interface/command.md)
- [API.OS](plugin/interface/os.md)
- [API.Directories](plugin/interface/dirs.md)
- [API.Network](plugin/interface/network.md)
- [API.Path](plugin/interface/path.md)
-197
View File
@@ -1,197 +0,0 @@
# Configure Project
This chapter will teach you how to configure the CLI with the `Dioxus.toml` file.
There's an [example](#config-example) which has comments to describe individual keys.
You can copy that or view this documentation for a more complete learning experience.
"🔒" indicates a mandatory item. Some headers are mandatory, but none of the keys inside them are. It might look weird, but it's normal. Simply don't include any keys.
## Structure
Each header has it's TOML form directly under it.
### Application 🔒
```toml
[application]
```
Application-wide configuration. Applies to both web and desktop.
1. **name** 🔒 - Project name & title.
```toml
name = "my_project"
```
2. **default_platform** 🔒 - The platform this project targets
```toml
# Currently supported platforms: web, desktop
default_platform = "web"
```
3. **out_dir** - The directory to place the build artifacts from `dx build` or `dx serve` into. This is also where the `assets` directory will be copied into.
```toml
out_dir = "dist"
```
4. **asset_dir** - The directory with your static assets. The CLI will automatically copy these assets into the **out_dir** after a build/serve.
```toml
asset_dir = "public"
```
5. **sub_package** - The sub package in the workspace to build by default.
```toml
sub_package = "my-crate"
```
### Web.App 🔒
```toml
[web.app]
```
Web-specific configuration.
1. **title** - The title of the web page.
```toml
# HTML title tag content
title = "project_name"
```
2. **base_path** - The base path to build the application for serving at. This can be useful when serving your application in a subdirectory under a domain. For example when building a site to be served on GitHub Pages.
```toml
# The application will be served at domain.com/my_application/, so we need to modify the base_path to the path where the application will be served
base_path = "my_application"
```
### Web.Watcher ✍
```toml
[web.watcher]
```
Development server configuration.
1. **reload_html** - If this is true, the cli will rebuild the index.html file every time the application is rebuilt
```toml
reload_html = true
```
2. **watch_path** - The files & directories to monitor for changes
```toml
watch_path = ["src", "public"]
```
3. **index_on_404** - If enabled, Dioxus will serve the root page when a route is not found.
*This is needed when serving an application that uses the router*.
However, when serving your app using something else than Dioxus (e.g. GitHub Pages), you will have to check how to configure it on that platform.
In GitHub Pages, you can make a copy of `index.html` named `404.html` in the same directory.
```toml
index_on_404 = true
```
### Web.Resource 🔒
```toml
[web.resource]
```
Static resource configuration.
1. **style** - CSS files to include in your application.
```toml
style = [
# Include from public_dir.
"./assets/style.css",
# Or some asset from online cdn.
"https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.css"
]
```
2. **script** - JavaScript files to include in your application.
```toml
script = [
# Include from asset_dir.
"./public/index.js",
# Or from an online CDN.
"https://cdn.jsdelivr.net/npm/bootstrap/dist/js/bootstrap.js"
]
```
### Web.Resource.Dev 🔒
```toml
[web.resource.dev]
```
This is the same as [`[web.resource]`](#webresource-), but it only works in development servers.
For example, if you want to include a file in a `dx serve` server, but not a `dx serve --release` server, put it here.
### Web.Proxy
```toml
[[web.proxy]]
```
Configuration related to any proxies your application requires during development. Proxies will forward requests to a new service.
1. **backend** - The URL to the server to proxy. The CLI will forward any requests under the backend relative route to the backend instead of returning 404
```toml
backend = "http://localhost:8000/api/"
```
This will cause any requests made to the dev server with prefix /api/ to be redirected to the backend server at http://localhost:8000. The path and query parameters will be passed on as-is (path rewriting is currently not supported).
## Config example
This includes all fields, mandatory or not.
```toml
[application]
# App name
name = "project_name"
# The Dioxus platform to default to
default_platform = "web"
# `build` & `serve` output path
out_dir = "dist"
# The static resource path
asset_dir = "public"
[web.app]
# HTML title tag content
title = "project_name"
[web.watcher]
# When watcher is triggered, regenerate the `index.html`
reload_html = true
# Which files or dirs will be monitored
watch_path = ["src", "public"]
# Include style or script assets
[web.resource]
# CSS style file
style = []
# Javascript code file
script = []
[web.resource.dev]
# Same as [web.resource], but for development servers
# CSS style file
style = []
# JavaScript files
script = []
[[web.proxy]]
backend = "http://localhost:8000/api/"
```
-37
View File
@@ -1,37 +0,0 @@
# Create a Project
Once you have the Dioxus CLI installed, you can use it to create your own project!
## Initializing a default project
First, run the `dx create` command to create a new project:
```
dx create hello-dioxus
```
> It will clone this [template](https://github.com/DioxusLabs/dioxus-template).
> This default template is used for `web` platform application.
>
> You can choose to create your project from a different template by passing the `template` argument:
> ```
> dx init hello-dioxus --template=gh:dioxuslabs/dioxus-template
> ```
Next, navigate into your new project:
```
cd hello-dioxus
```
> Make sure the WASM target is installed before running the projects.
> You can install the WASM target for rust using rustup:
> ```
> rustup target add wasm32-unknown-unknown
> ```
Finally, serve your project:
```
dx serve
```
By default, the CLI serves your website at [`http://127.0.0.1:8080/`](http://127.0.0.1:8080/).
-23
View File
@@ -1,23 +0,0 @@
# Installation
## Install the latest development build through git
To get the latest bug fixes and features, you can install the development version from git.
```
cargo install --git https://github.com/Dioxuslabs/cli
```
This will download `Dioxus-CLI` source from GitHub master branch,
and install it in Cargo's global binary directory (`~/.cargo/bin/` by default).
## Install stable through `crates.io`
The published version of the Dioxus CLI is updated less often, but is more stable than the git version.
```
cargo install dioxus-cli --locked
```
Run `dx --help` for a list of all the available commands.
Furthermore, you can run `dx <COMMAND> --help` to get help with a specific command.
-18
View File
@@ -1,18 +0,0 @@
# Introduction
The 📦✨ **Dioxus CLI** is a tool to help get Dioxus projects off the ground.
## Features
* Build and pack a Dioxus project
* `html` to `rsx` conversion tool
* Hot Reload for `web` platform
* Create a Dioxus project from `git` repo
* And more!
<!-- Checkmarks don't render on the website, so I've just made a normal list. You can uncomment this if the website rendering is fixed.
- [x] `html` to `rsx` conversion tool
- [x] Hot Reload for `web` platform
- [x] Create a Dioxus project from `git` repo
- [x] Build & pack Dioxus project
- [ ] Automatically format Dioxus `rsx` code
-->
-140
View File
@@ -1,140 +0,0 @@
# CLI Plugin development
**IMPORTANT: Ignore this documentation. Plugins are yet to be released and chances are it won't work for you. This is just what plugins *could* look like.**
In the past we used `dx tool` to use and install tools, but it was a flawed system.
Tools were hard-coded by us, but people want more tools than we could code, so this plugin system was made to let
anyone develop plugins and use them in Dioxus projects.
Plugin resources:
* [Source code](https://github.com/DioxusLabs/dioxus/tree/master/packages/cli/src/plugin)
* [Unofficial Dioxus plugin community](https://github.com/DioxusPluginCommunity). Contains certain plugins you can use right now.
### Why Lua?
We chose Lua `5.4` to be the plugin developing language,
because it's extremely lightweight, embeddable and easy to learn.
We installed Lua into the CLI, so you don't need to do it yourself.
Lua resources:
* [Official website](https://www.lua.org/). You can basically find everything here.
* [Awesome Lua](https://github.com/LewisJEllis/awesome-lua). Additional resources (such as Lua plugins for your favorite IDE), and other *awesome* tools!
## Creating a plugin
A plugin is just an `init.lua` file.
You can include other files using `dofile(path)`.
You need to have a plugin and a manager instance, which you can get using `require`:
```lua
local plugin = require("plugin")
local manager = require("manager")
```
You need to set some `manager` fields and then initialize the plugin:
```lua
manager.name = "My first plugin"
manager.repository = "https://github.com/john-doe/my-first-plugin" -- The repository URL.
manager.author = "John Doe <john.doe@example.com>"
manager.version = "0.1.0"
plugin.init(manager)
```
You also need to return the `manager`, which basically represents your plugin:
```lua
-- Your code here.
-- End of file.
manager.serve.interval = 1000
return manager
```
And you're ready to go. Now, go and have a look at the stuff below and the API documentation.
### Plugin info
You will encounter this type in the events below. The keys are as follows:
* `name: string` - The name of the plugin.
* `repository: string` - The plugin repository URL.
* `author: string` - The author of the plugin.
* `version: string` - The plugin version.
### Event management
The plugin library has certain events that you can subscribe to.
* `manager.on_init` - Triggers the first time the plugin is loaded.
* `manager.build.on_start(info)` - Triggers before the build process. E.g., before `dx build`.
* `manager.build.on_finish(info)` - Triggers after the build process. E.g., after `dx build`.
* `manager.serve.on_start(info)` - Triggers before the serving process. E.g., before `dx serve`.
* `manager.serve.on_rebuild_start(info)` - Triggers before the server rebuilds the web with hot reload.
* `manager.serve.on_rebuild_end(info)` - Triggers after the server rebuilds the web with hot reload.
* `manager.serve.on_shutdown` - Triggers when the server is shutdown. E.g., when the `dx serve` process is terminated.
To subscribe to an event, you simply need to assign it to a function:
```lua
manager.build.on_start = function (info)
log.info("[plugin] Build starting: " .. info.name)
end
```
### Plugin template
```lua
package.path = library_dir .. "/?.lua"
local plugin = require("plugin")
local manager = require("manager")
-- deconstruct api functions
local log = plugin.log
-- plugin information
manager.name = "Hello Dixous Plugin"
manager.repository = "https://github.com/mrxiaozhuox/hello-dioxus-plugin"
manager.author = "YuKun Liu <mrxzx.info@gmail.com>"
manager.version = "0.0.1"
-- init manager info to plugin api
plugin.init(manager)
manager.on_init = function ()
-- when the first time plugin been load, this function will be execute.
-- system will create a `dcp.json` file to verify init state.
log.info("[plugin] Start to init plugin: " .. manager.name)
end
---@param info BuildInfo
manager.build.on_start = function (info)
-- before the build work start, system will execute this function.
log.info("[plugin] Build starting: " .. info.name)
end
---@param info BuildInfo
manager.build.on_finish = function (info)
-- when the build work is done, system will execute this function.
log.info("[plugin] Build finished: " .. info.name)
end
---@param info ServeStartInfo
manager.serve.on_start = function (info)
-- this function will after clean & print to run, so you can print some thing.
log.info("[plugin] Serve start: " .. info.name)
end
---@param info ServeRebuildInfo
manager.serve.on_rebuild = function (info)
-- this function will after clean & print to run, so you can print some thing.
local files = plugin.tool.dump(info.changed_files)
log.info("[plugin] Serve rebuild: '" .. files .. "'")
end
manager.serve.on_shutdown = function ()
log.info("[plugin] Serve shutdown")
end
manager.serve.interval = 1000
return manager
```
@@ -1,21 +0,0 @@
# Command Functions
You can use command functions to execute code and scripts.
Type definition:
```
Stdio: "Inherit" | "Piped" | "Null"
```
### `exec(commands: [string], stdout: Stdio, stderr: Stdio)`
You can use this function to run some commands on the current system.
```lua
local cmd = plugin.command
manager.test = function ()
cmd.exec({"git", "clone", "https://github.com/DioxusLabs/cli-plugin-library"})
end
```
> Warning: This function doesn't catch exceptions.
@@ -1,30 +0,0 @@
# Dirs Functions
Dirs functions are for getting various directory paths. Not to be confused with `plugin.path`.
### `plugin_dir() -> string`
Get the plugin's root directory path.
```lua
local path = plugin.dirs.plugin_dir()
-- example: ~/Development/DioxusCli/plugin/test-plugin/
```
### `bin_dir() -> string`
Get the plugin's binary directory path. Put binary files like `tailwind-cli` or `sass-cli` in this directory.
```lua
local path = plugin.dirs.bin_dir()
-- example: ~/Development/DioxusCli/plugin/test-plugin/bin/
```
### `temp_dir() -> string`
Get the plugin's temporary directory path. Put any temporary files here.
```lua
local path = plugin.dirs.bin_dir()
-- example: ~/Development/DioxusCli/plugin/test-plugin/temp/
```
@@ -1,48 +0,0 @@
# Log Functions
You can use log functions to print various logging information.
### `trace(info: string)`
Print trace log info.
```lua
local log = plugin.log
log.trace("trace information")
```
### `debug(info: string)`
Print debug log info.
```lua
local log = plugin.log
log.debug("debug information")
```
### `info(info: string)`
Print info log info.
```lua
local log = plugin.log
log.info("info information")
```
### `warn(info: string)`
Print warning log info.
```lua
local log = plugin.log
log.warn("warn information")
```
### `error(info: string)`
Print error log info.
```lua
local log = plugin.log
log.error("error information")
```
@@ -1,37 +0,0 @@
# Network Functions
You can use Network functions to download & read some data from the internet.
### `download_file(url: string, path: string) -> boolean`
Downloads a file from the specified URL,
and returns a `boolean` that represents the download status (true: success, false: failure).
You need to pass a target URL and a local path (where you want to save this file).
```lua
-- this file will download to plugin temp directory
local status = plugin.network.download_file(
"http://xxx.com/xxx.zip",
plugin.dirs.temp_dir()
)
if status != true then
log.error("Download Failed")
end
```
### `clone_repo(url: string, path: string) -> boolean`
Clone a repository from the given URL into the given path.
Returns a `boolean` that represents the clone status (true: success, false: failure).
The system executing this function must have git installed.
```lua
local status = plugin.network.clone_repo(
"http://github.com/mrxiaozhuox/dioxus-starter",
plugin.dirs.bin_dir()
)
if status != true then
log.error("Clone Failed")
end
```
@@ -1,11 +0,0 @@
# OS Functions
OS functions are for getting system information.
### `current_platform() -> string ("windows" | "macos" | "linux")`
Get the current OS platform.
```lua
local platform = plugin.os.current_platform()
```
@@ -1,38 +0,0 @@
# Path Functions
You can use path functions to perform operations on valid path strings.
### `join(path: string, extra: string) -> string`
<!-- TODO: Add specifics.
From the example given, it seems like it just creates a subdirectory path.
What would it do when "extending" file paths? -->
Extend a path; you can extend both directory and file paths.
```lua
local current_path = "~/hello/dioxus"
local new_path = plugin.path.join(current_path, "world")
-- new_path = "~/hello/dioxus/world"
```
### `parent(path: string) -> string`
Return the parent path of the specified path. The parent path is always a directory.
```lua
local current_path = "~/hello/dioxus"
local new_path = plugin.path.parent(current_path)
-- new_path = "~/hello/"
```
### `exists(path: string) -> boolean`
Check if the specified path exists, as either a file or a directory.
### `is_file(path: string) -> boolean`
Check if the specified path is a file.
### `is_dir(path: string) -> boolean`
Check if the specified path is a directory.
-18
View File
@@ -1,18 +0,0 @@
local Api = require("./interface")
local log = Api.log;
local manager = {
name = "Dioxus-CLI Plugin Demo",
repository = "http://github.com/DioxusLabs/cli",
author = "YuKun Liu <mrxzx.info@gmail.com>",
}
manager.onLoad = function ()
log.info("plugin loaded.")
end
manager.onStartBuild = function ()
log.warn("system start to build")
end
return manager
@@ -1,25 +0,0 @@
local interface = {}
if plugin_logger ~= nil then
interface.log = plugin_logger
else
interface.log = {
trace = function (info)
print("trace: " .. info)
end,
debug = function (info)
print("debug: " .. info)
end,
info = function (info)
print("info: " .. info)
end,
warn = function (info)
print("warn: " .. info)
end,
error = function (info)
print("error: " .. info)
end,
}
end
return interface
+1 -1
View File
@@ -23,7 +23,7 @@ title = "Dioxus | An elegant GUI library for Rust"
index_on_404 = true
watch_path = ["src"]
watch_path = ["src", "examples"]
# include `assets` in web platform
[web.resource]
+6 -37
View File
@@ -254,6 +254,7 @@ pub fn build_desktop(config: &CrateConfig, _is_serve: bool) -> Result<BuildResul
let mut cmd = subprocess::Exec::cmd("cargo")
.cwd(&config.crate_dir)
.arg("build")
.arg("--quiet")
.arg("--message-format=json");
if config.release {
@@ -312,7 +313,7 @@ pub fn build_desktop(config: &CrateConfig, _is_serve: bool) -> Result<BuildResul
if !config.out_dir.is_dir() {
create_dir_all(&config.out_dir)?;
}
copy(res_path, &config.out_dir.join(target_file))?;
copy(res_path, config.out_dir.join(target_file))?;
// this code will copy all public file to the output dir
if config.asset_dir.is_dir() {
@@ -436,14 +437,14 @@ pub fn gen_page(config: &DioxusConfig, serve: bool) -> String {
String::from(include_str!("./assets/index.html"))
};
let resouces = config.web.resource.clone();
let resources = config.web.resource.clone();
let mut style_list = resouces.style.unwrap_or_default();
let mut script_list = resouces.script.unwrap_or_default();
let mut style_list = resources.style.unwrap_or_default();
let mut script_list = resources.script.unwrap_or_default();
if serve {
let mut dev_style = resouces.dev.style.clone();
let mut dev_script = resouces.dev.script;
let mut dev_script = resouces.dev.script.clone();
style_list.append(&mut dev_style);
script_list.append(&mut dev_script);
}
@@ -671,35 +672,3 @@ fn build_assets(config: &CrateConfig) -> Result<Vec<PathBuf>> {
Ok(result)
}
// use binary_install::{Cache, Download};
// /// Attempts to find `wasm-opt` in `PATH` locally, or failing that downloads a
// /// precompiled binary.
// ///
// /// Returns `Some` if a binary was found or it was successfully downloaded.
// /// Returns `None` if a binary wasn't found in `PATH` and this platform doesn't
// /// have precompiled binaries. Returns an error if we failed to download the
// /// binary.
// pub fn find_wasm_opt(
// cache: &Cache,
// install_permitted: bool,
// ) -> Result<install::Status, failure::Error> {
// // First attempt to look up in PATH. If found assume it works.
// if let Ok(path) = which::which("wasm-opt") {
// PBAR.info(&format!("found wasm-opt at {:?}", path));
// match path.as_path().parent() {
// Some(path) => return Ok(install::Status::Found(Download::at(path))),
// None => {}
// }
// }
// let version = "version_78";
// Ok(install::download_prebuilt(
// &install::Tool::WasmOpt,
// cache,
// version,
// install_permitted,
// )?)
// }
+17 -7
View File
@@ -46,18 +46,28 @@ impl Autoformat {
// Format single file
if let Some(file) = self.file {
let file_content = fs::read_to_string(&file);
let file_content = if file == "-" {
let mut contents = String::new();
std::io::stdin().read_to_string(&mut contents)?;
Ok(contents)
} else {
fs::read_to_string(&file)
};
match file_content {
Ok(s) => {
let edits = dioxus_autofmt::fmt_file(&s);
let out = dioxus_autofmt::apply_formats(&s, edits);
match fs::write(&file, out) {
Ok(_) => {
println!("formatted {}", file);
}
Err(e) => {
eprintln!("failed to write formatted content to file: {}", e);
if file == "-" {
print!("{}", out);
} else {
match fs::write(&file, out) {
Ok(_) => {
println!("formatted {}", file);
}
Err(e) => {
eprintln!("failed to write formatted content to file: {}", e);
}
}
}
}
+4 -4
View File
@@ -132,10 +132,10 @@ impl Bundle {
.project_out_directory(crate_config.out_dir)
.package_settings(PackageSettings {
product_name: crate_config.dioxus_config.application.name.clone(),
version: package.version,
description: package.description.unwrap_or_default(),
homepage: package.homepage,
authors: Some(package.authors),
version: package.version().to_string(),
description: package.description().unwrap_or_default().to_string(),
homepage: Some(package.homepage().unwrap_or_default().to_string()),
authors: Some(Vec::from(package.authors())),
default_run: Some(crate_config.dioxus_config.application.name.clone()),
})
.binaries(binaries)
+1 -1
View File
@@ -216,7 +216,7 @@ pub struct WebWatcherConfig {
}
fn watch_path_default() -> Vec<PathBuf> {
vec![PathBuf::from("src")]
vec![PathBuf::from("src"), PathBuf::from("examples")]
}
#[derive(Debug, Clone, Serialize, Deserialize)]
+4
View File
@@ -1,3 +1,7 @@
#![doc = include_str!("../README.md")]
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
pub const DIOXUS_CLI_VERSION: &str = "0.4.1";
pub mod builder;
+26 -19
View File
@@ -124,27 +124,34 @@ async fn start_desktop_hot_reload(hot_reload_state: HotReloadState) -> Result<()
let _ = local_socket_stream.set_nonblocking(true);
move || {
loop {
if let Ok(mut connection) = local_socket_stream.accept() {
// send any templates than have changed before the socket connected
let templates: Vec<_> = {
file_map
.lock()
.unwrap()
.map
.values()
.filter_map(|(_, template_slot)| *template_slot)
.collect()
};
for template in templates {
if !send_msg(
HotReloadMsg::UpdateTemplate(template),
&mut connection,
) {
continue;
match local_socket_stream.accept() {
Ok(mut connection) => {
// send any templates than have changed before the socket connected
let templates: Vec<_> = {
file_map
.lock()
.unwrap()
.map
.values()
.filter_map(|(_, template_slot)| *template_slot)
.collect()
};
for template in templates {
if !send_msg(
HotReloadMsg::UpdateTemplate(template),
&mut connection,
) {
continue;
}
}
channels.lock().unwrap().push(connection);
println!("Connected to hot reloading 🚀");
}
Err(err) => {
if err.kind() != std::io::ErrorKind::WouldBlock {
println!("Error connecting to hot reloading: {} (Hot reloading is a feature of the dioxus-cli. If you are not using the CLI, this error can be ignored)", err);
}
}
channels.lock().unwrap().push(connection);
println!("Connected to hot reloading 🚀");
}
if *aborted.lock().unwrap() {
break;
+6 -6
View File
@@ -112,12 +112,12 @@ async fn setup_file_watcher<F: Fn() -> Result<BuildResult> + Send + 'static>(
.unwrap();
for sub_path in allow_watch_path {
watcher
.watch(
&config.crate_dir.join(sub_path),
notify::RecursiveMode::Recursive,
)
.unwrap();
if let Err(err) = watcher.watch(
&config.crate_dir.join(sub_path),
notify::RecursiveMode::Recursive,
) {
log::error!("Failed to watch path: {}", err);
}
}
Ok(watcher)
}
+1 -1
View File
@@ -121,7 +121,7 @@ pub fn print_console_info(
log::warn!(
"{}",
format!(
"There were {} warning messages during the build.",
"There were {} warning messages during the build. Run `cargo check` to see them.",
options.warnings.len() - 1
)
.yellow()
+1
View File
@@ -19,6 +19,7 @@ syn = { version = "2.0", features = ["full", "extra-traits"] }
dioxus-rsx = { workspace = true }
dioxus-core = { workspace = true }
constcat = "0.3.0"
prettyplease = "0.2.15"
# testing
[dev-dependencies]
+6 -3
View File
@@ -23,9 +23,12 @@
`dioxus-core-macro` provides a handful of helpful macros used by the `dioxus` crate. These include:
- The `rsx!` macro that underpins templates and node creation
- The `inline_props` macro transforms function arguments into an auto-derived struct
- The `format_args_f` macro which allows f-string formatting with support for expressions
- The `rsx!` macro that underpins templates and node creation.
- The `component` attribute macro denotes a function as a Dioxus component. Currently, this:
- Transforms function arguments into an auto-derived struct.
- Ensures that your component name uses PascalCase.
- Probably more stuff in the future. This macro allows us to have a way of distinguishing functions and components, which can be quite handy.
- The `format_args_f` macro which allows f-string formatting with support for expressions.
## Contributing
@@ -31,6 +31,7 @@ fn get_out_comp_fn(orig_comp_fn: &ItemFn, cx_pat: &Pat) -> ItemFn {
block: parse_quote! {
{
#[warn(non_snake_case)]
#[allow(clippy::inline_always)]
#[inline(always)]
#inner_comp_fn
#inner_comp_ident (#cx_pat)
@@ -30,166 +30,312 @@ impl ToTokens for InlinePropsDeserializerOutput {
impl DeserializerArgs<InlinePropsDeserializerOutput> for InlinePropsDeserializerArgs {
fn to_output(&self, component_body: &ComponentBody) -> Result<InlinePropsDeserializerOutput> {
Ok(InlinePropsDeserializerOutput {
comp_fn: Self::get_function(component_body),
props_struct: Self::get_props_struct(component_body),
comp_fn: get_function(component_body),
props_struct: get_props_struct(component_body),
})
}
}
impl InlinePropsDeserializerArgs {
fn get_props_struct(component_body: &ComponentBody) -> ItemStruct {
let ComponentBody { item_fn, .. } = component_body;
let ItemFn { vis, sig, .. } = item_fn;
let Signature {
inputs,
ident: fn_ident,
generics,
..
} = sig;
fn get_props_struct(component_body: &ComponentBody) -> ItemStruct {
let ComponentBody { item_fn, .. } = component_body;
let ItemFn { vis, sig, .. } = item_fn;
let Signature {
inputs,
ident: fn_ident,
generics,
..
} = sig;
// Skip first arg since that's the context
let struct_fields = inputs.iter().skip(1).map(move |f| {
match f {
FnArg::Receiver(_) => unreachable!(), // Unreachable because of ComponentBody parsing
FnArg::Typed(pt) => {
let arg_pat = &pt.pat; // Pattern (identifier)
let arg_colon = &pt.colon_token;
let arg_ty = &pt.ty; // Type
let arg_attrs = &pt.attrs; // Attributes
// Skip first arg since that's the context
let struct_fields = inputs.iter().skip(1).map(move |f| {
match f {
FnArg::Receiver(_) => unreachable!(), // Unreachable because of ComponentBody parsing
FnArg::Typed(pt) => {
let arg_pat = &pt.pat; // Pattern (identifier)
let arg_colon = &pt.colon_token;
let arg_ty = &pt.ty; // Type
let arg_attrs = &pt.attrs; // Attributes
quote! {
#(#arg_attrs)
*
#vis #arg_pat #arg_colon #arg_ty
}
quote! {
#(#arg_attrs)
*
#vis #arg_pat #arg_colon #arg_ty
}
}
});
let struct_ident = Ident::new(&format!("{fn_ident}Props"), fn_ident.span());
let first_lifetime = if let Some(GenericParam::Lifetime(lt)) = generics.params.first() {
Some(lt)
} else {
None
};
let struct_attrs = if first_lifetime.is_some() {
quote! { #[derive(Props)] }
} else {
quote! { #[derive(Props, PartialEq)] }
};
let struct_generics = if first_lifetime.is_some() {
let struct_generics: Punctuated<GenericParam, Comma> = component_body
.item_fn
.sig
.generics
.params
.iter()
.map(|it| match it {
GenericParam::Type(tp) => {
let mut tp = tp.clone();
tp.bounds.push(parse_quote!( 'a ));
GenericParam::Type(tp)
}
_ => it.clone(),
})
.collect();
quote! { <#struct_generics> }
} else {
quote! { #generics }
};
parse_quote! {
#struct_attrs
#[allow(non_camel_case_types)]
#vis struct #struct_ident #struct_generics
{
#(#struct_fields),*
}
}
}
});
fn get_function(component_body: &ComponentBody) -> ItemFn {
let ComponentBody {
item_fn,
cx_pat_type,
..
} = component_body;
let ItemFn {
attrs: fn_attrs,
vis,
sig,
block: fn_block,
} = item_fn;
let Signature {
inputs,
ident: fn_ident,
generics,
output: fn_output,
asyncness,
..
} = sig;
let Generics { where_clause, .. } = generics;
let struct_ident = Ident::new(&format!("{fn_ident}Props"), fn_ident.span());
let cx_pat = &cx_pat_type.pat;
let struct_ident = Ident::new(&format!("{fn_ident}Props"), fn_ident.span());
let first_lifetime = if let Some(GenericParam::Lifetime(lt)) = generics.params.first() {
Some(lt)
} else {
None
};
// Skip first arg since that's the context
let struct_field_names = inputs.iter().skip(1).filter_map(|f| match f {
FnArg::Receiver(_) => unreachable!(), // ComponentBody prohibits receiver parameters.
FnArg::Typed(t) => Some(&t.pat),
});
let struct_attrs = if first_lifetime.is_some() {
quote! { #[derive(Props)] }
} else {
quote! { #[derive(Props, PartialEq)] }
};
let first_lifetime = if let Some(GenericParam::Lifetime(lt)) = generics.params.first() {
Some(lt)
} else {
None
};
let struct_generics = if first_lifetime.is_some() {
let struct_generics: Punctuated<GenericParam, Comma> = component_body
.item_fn
.sig
.generics
.params
.iter()
.map(|it| match it {
GenericParam::Type(tp) => {
let mut tp = tp.clone();
tp.bounds.push(parse_quote!( 'a ));
let (scope_lifetime, fn_generics) = if let Some(lt) = first_lifetime {
(quote! { #lt, }, generics.clone())
} else {
let lifetime: LifetimeParam = parse_quote! { 'a };
GenericParam::Type(tp)
}
_ => it.clone(),
})
.collect();
let mut fn_generics = generics.clone();
fn_generics
.params
.insert(0, GenericParam::Lifetime(lifetime.clone()));
quote! { <#struct_generics> }
} else {
quote! { #generics }
};
(quote! { #lifetime, }, fn_generics)
};
let generics_no_bounds = {
let mut generics = generics.clone();
generics.params = generics
.params
.iter()
.map(|it| match it {
GenericParam::Type(tp) => {
let mut tp = tp.clone();
tp.bounds.clear();
GenericParam::Type(tp)
}
_ => it.clone(),
})
.collect();
generics
};
parse_quote! {
#(#fn_attrs)*
#asyncness #vis fn #fn_ident #fn_generics (#cx_pat: Scope<#scope_lifetime #struct_ident #generics_no_bounds>) #fn_output
#where_clause
{
let #struct_ident { #(#struct_field_names),* } = &#cx_pat.props;
#fn_block
}
parse_quote! {
#struct_attrs
#[allow(non_camel_case_types)]
#vis struct #struct_ident #struct_generics
{
#(#struct_fields),*
}
}
}
fn get_props_docs(fn_ident: &Ident, inputs: Vec<&FnArg>) -> Vec<Attribute> {
if inputs.len() <= 1 {
return Vec::new();
}
let arg_docs = inputs
.iter()
.filter_map(|f| match f {
FnArg::Receiver(_) => unreachable!(), // ComponentBody prohibits receiver parameters.
FnArg::Typed(pt) => {
let arg_doc = pt
.attrs
.iter()
.filter_map(|attr| {
// TODO: Error reporting
// Check if the path of the attribute is "doc"
if !is_attr_doc(attr) {
return None;
};
let Meta::NameValue(meta_name_value) = &attr.meta else {
return None;
};
let Expr::Lit(doc_lit) = &meta_name_value.value else {
return None;
};
let Lit::Str(doc_lit_str) = &doc_lit.lit else {
return None;
};
Some(doc_lit_str.value())
})
.fold(String::new(), |mut doc, next_doc_line| {
doc.push('\n');
doc.push_str(&next_doc_line);
doc
});
Some((
&pt.pat,
&pt.ty,
pt.attrs.iter().find_map(|attr| {
if attr.path() != &parse_quote!(deprecated) {
return None;
}
let res = crate::utils::DeprecatedAttribute::from_meta(&attr.meta);
match res {
Err(e) => panic!("{}", e.to_string()),
Ok(v) => Some(v),
}
}),
arg_doc,
))
}
})
.collect::<Vec<_>>();
let mut props_docs = Vec::with_capacity(5);
let props_def_link = fn_ident.to_string() + "Props";
let header =
format!("# Props\n*For details, see the [props struct definition]({props_def_link}).*");
props_docs.push(parse_quote! {
#[doc = #header]
});
for (arg_name, arg_type, deprecation, input_arg_doc) in arg_docs {
let arg_name = arg_name.into_token_stream().to_string();
let arg_type = crate::utils::format_type_string(arg_type);
let input_arg_doc = keep_up_to_n_consecutive_chars(input_arg_doc.trim(), 2, '\n')
.replace("\n\n", "</p><p>");
let prop_def_link = format!("{props_def_link}::{arg_name}");
let mut arg_doc = format!("- [`{arg_name}`]({prop_def_link}) : `{arg_type}`");
if let Some(deprecation) = deprecation {
arg_doc.push_str("<p>👎 Deprecated");
if let Some(since) = deprecation.since {
arg_doc.push_str(&format!(" since {since}"));
}
if let Some(note) = deprecation.note {
let note = keep_up_to_n_consecutive_chars(&note, 1, '\n').replace('\n', " ");
let note = keep_up_to_n_consecutive_chars(&note, 1, '\t').replace('\t', " ");
arg_doc.push_str(&format!(": {note}"));
}
arg_doc.push_str("</p>");
if !input_arg_doc.is_empty() {
arg_doc.push_str("<hr/>");
}
}
if !input_arg_doc.is_empty() {
arg_doc.push_str(&format!("<p>{input_arg_doc}</p>"));
}
props_docs.push(parse_quote! {
#[doc = #arg_doc]
});
}
props_docs
}
fn get_function(component_body: &ComponentBody) -> ItemFn {
let ComponentBody {
item_fn,
cx_pat_type,
..
} = component_body;
let ItemFn {
attrs: fn_attrs,
vis,
sig,
block: fn_block,
} = item_fn;
let Signature {
inputs,
ident: fn_ident,
generics,
output: fn_output,
asyncness,
..
} = sig;
let Generics { where_clause, .. } = generics;
let cx_pat = &cx_pat_type.pat;
let struct_ident = Ident::new(&format!("{fn_ident}Props"), fn_ident.span());
// Skip first arg since that's the context
let struct_field_names = inputs.iter().skip(1).filter_map(|f| match f {
FnArg::Receiver(_) => unreachable!(), // ComponentBody prohibits receiver parameters.
FnArg::Typed(pt) => Some(&pt.pat),
});
let first_lifetime = if let Some(GenericParam::Lifetime(lt)) = generics.params.first() {
Some(lt)
} else {
None
};
let (scope_lifetime, fn_generics) = if let Some(lt) = first_lifetime {
(quote! { #lt, }, generics.clone())
} else {
let lifetime: LifetimeParam = parse_quote! { 'a };
let mut fn_generics = generics.clone();
fn_generics
.params
.insert(0, GenericParam::Lifetime(lifetime.clone()));
(quote! { #lifetime, }, fn_generics)
};
let generics_no_bounds = {
let mut generics = generics.clone();
generics.params = generics
.params
.iter()
.map(|it| match it {
GenericParam::Type(tp) => {
let mut tp = tp.clone();
tp.bounds.clear();
GenericParam::Type(tp)
}
_ => it.clone(),
})
.collect();
generics
};
let props_docs = get_props_docs(fn_ident, inputs.iter().skip(1).collect());
parse_quote! {
#(#fn_attrs)*
#(#props_docs)*
#asyncness #vis fn #fn_ident #fn_generics (#cx_pat: Scope<#scope_lifetime #struct_ident #generics_no_bounds>) #fn_output
#where_clause
{
let #struct_ident { #(#struct_field_names),* } = &#cx_pat.props;
#fn_block
}
}
}
/// Checks if the attribute is a `#[doc]` attribute.
fn is_attr_doc(attr: &Attribute) -> bool {
attr.path() == &parse_quote!(doc)
}
fn keep_up_to_n_consecutive_chars(
input: &str,
n_of_consecutive_chars_allowed: usize,
target_char: char,
) -> String {
let mut output = String::new();
let mut prev_char: Option<char> = None;
let mut consecutive_count = 0;
for c in input.chars() {
match prev_char {
Some(prev) if c == target_char && prev == target_char => {
if consecutive_count < n_of_consecutive_chars_allowed {
output.push(c);
consecutive_count += 1;
}
}
_ => {
output.push(c);
prev_char = Some(c);
consecutive_count = 1;
}
}
}
output
}
+5
View File
@@ -1,3 +1,7 @@
#![doc = include_str!("../README.md")]
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
use proc_macro::TokenStream;
use quote::ToTokens;
use rsx::RenderCallBody;
@@ -8,6 +12,7 @@ use syn::{parse_macro_input, Path, Token};
mod component_body;
mod component_body_deserializers;
mod props;
mod utils;
// mod rsx;
use crate::component_body::ComponentBody;
+15 -12
View File
@@ -551,18 +551,16 @@ mod struct_info {
let generics_with_empty = modify_types_generics_hack(&ty_generics, |args| {
args.insert(0, syn::GenericArgument::Type(empties_tuple.clone().into()));
});
let phantom_generics = self.generics.params.iter().map(|param| match param {
let phantom_generics = self.generics.params.iter().filter_map(|param| match param {
syn::GenericParam::Lifetime(lifetime) => {
let lifetime = &lifetime.lifetime;
quote!(::core::marker::PhantomData<&#lifetime ()>)
Some(quote!(::core::marker::PhantomData<&#lifetime ()>))
}
syn::GenericParam::Type(ty) => {
let ty = &ty.ident;
quote!(::core::marker::PhantomData<#ty>)
}
syn::GenericParam::Const(_cnst) => {
quote!()
Some(quote!(::core::marker::PhantomData<#ty>))
}
syn::GenericParam::Const(_cnst) => None,
});
let builder_method_doc = match self.builder_attr.builder_method_doc {
Some(ref doc) => quote!(#doc),
@@ -633,7 +631,7 @@ Finally, call `.build()` to create the instance of `{name}`.
Ok(quote! {
impl #impl_generics #name #ty_generics #where_clause {
#[doc = #builder_method_doc]
#[allow(dead_code)]
#[allow(dead_code, clippy::type_complexity)]
#vis fn builder() -> #builder_name #generics_with_empty {
#builder_name {
fields: #empties_tuple,
@@ -701,6 +699,14 @@ Finally, call `.build()` to create the instance of `{name}`.
}
pub fn field_impl(&self, field: &FieldInfo) -> Result<TokenStream, Error> {
let FieldInfo {
name: field_name,
ty: field_type,
..
} = field;
if *field_name == "key" {
return Err(Error::new_spanned(field_name, "Naming a prop `key` is not allowed because the name can conflict with the built in key attribute. See https://dioxuslabs.com/learn/0.4/reference/dynamic_rendering#rendering-lists for more information about keys"));
}
let StructInfo {
ref builder_name, ..
} = *self;
@@ -715,11 +721,6 @@ Finally, call `.build()` to create the instance of `{name}`.
});
let reconstructing = self.included_fields().map(|f| f.name);
let FieldInfo {
name: field_name,
ty: field_type,
..
} = field;
let mut ty_generics: Vec<syn::GenericArgument> = self
.generics
.params
@@ -822,6 +823,7 @@ Finally, call `.build()` to create the instance of `{name}`.
#[allow(dead_code, non_camel_case_types, missing_docs)]
impl #impl_generics #builder_name < #( #ty_generics ),* > #where_clause {
#doc
#[allow(clippy::type_complexity)]
pub fn #field_name (self, #field_name: #arg_type) -> #builder_name < #( #target_generics ),* > {
let #field_name = (#arg_expr,);
let ( #(#descructuring,)* ) = self.fields;
@@ -840,6 +842,7 @@ Finally, call `.build()` to create the instance of `{name}`.
#[deprecated(
note = #repeated_fields_error_message
)]
#[allow(clippy::type_complexity)]
pub fn #field_name (self, _: #repeated_fields_error_type_name) -> #builder_name < #( #target_generics ),* > {
self
}
+129
View File
@@ -0,0 +1,129 @@
use quote::ToTokens;
use syn::parse::{Parse, ParseStream};
use syn::spanned::Spanned;
use syn::{parse_quote, Expr, Lit, Meta, Token, Type};
const FORMATTED_TYPE_START: &str = "static TY_AFTER_HERE:";
const FORMATTED_TYPE_END: &str = "= todo!();";
/// Attempts to convert the given literal to a string.
/// Converts ints and floats to their base 10 counterparts.
///
/// Returns `None` if the literal is [`Lit::Verbatim`] or if the literal is [`Lit::ByteStr`]
/// and the byte string could not be converted to UTF-8.
pub fn lit_to_string(lit: Lit) -> Option<String> {
match lit {
Lit::Str(l) => Some(l.value()),
Lit::ByteStr(l) => String::from_utf8(l.value()).ok(),
Lit::Byte(l) => Some(String::from(l.value() as char)),
Lit::Char(l) => Some(l.value().to_string()),
Lit::Int(l) => Some(l.base10_digits().to_string()),
Lit::Float(l) => Some(l.base10_digits().to_string()),
Lit::Bool(l) => Some(l.value().to_string()),
Lit::Verbatim(_) => None,
_ => None,
}
}
pub fn format_type_string(ty: &Type) -> String {
let ty_unformatted = ty.into_token_stream().to_string();
let ty_unformatted = ty_unformatted.trim();
// This should always be valid syntax.
// Not Rust code, but syntax, which is the only thing that `syn` cares about.
let Ok(file_unformatted) = syn::parse_file(&format!(
"{FORMATTED_TYPE_START}{ty_unformatted}{FORMATTED_TYPE_END}"
)) else {
return ty_unformatted.to_string();
};
let file_formatted = prettyplease::unparse(&file_unformatted);
let file_trimmed = file_formatted.trim();
let start_removed = file_trimmed.trim_start_matches(FORMATTED_TYPE_START);
let end_removed = start_removed.trim_end_matches(FORMATTED_TYPE_END);
let ty_formatted = end_removed.trim();
ty_formatted.to_string()
}
/// Represents the `#[deprecated]` attribute.
///
/// You can use the [`DeprecatedAttribute::from_meta`] function to try to parse an attribute to this struct.
#[derive(Default)]
pub struct DeprecatedAttribute {
pub since: Option<String>,
pub note: Option<String>,
}
impl DeprecatedAttribute {
/// Returns `None` if the given attribute was not a valid form of the `#[deprecated]` attribute.
pub fn from_meta(meta: &Meta) -> syn::Result<Self> {
if meta.path() != &parse_quote!(deprecated) {
return Err(syn::Error::new(
meta.span(),
"attribute path is not `deprecated`",
));
}
match &meta {
Meta::Path(_) => Ok(Self::default()),
Meta::NameValue(name_value) => {
let Expr::Lit(expr_lit) = &name_value.value else {
return Err(syn::Error::new(
name_value.span(),
"literal in `deprecated` value must be a string",
));
};
Ok(Self {
since: None,
note: lit_to_string(expr_lit.lit.clone()).map(|s| s.trim().to_string()),
})
}
Meta::List(list) => {
let parsed = list.parse_args::<DeprecatedAttributeArgsParser>()?;
Ok(Self {
since: parsed.since.map(|s| s.trim().to_string()),
note: parsed.note.map(|s| s.trim().to_string()),
})
}
}
}
}
mod kw {
use syn::custom_keyword;
custom_keyword!(since);
custom_keyword!(note);
}
struct DeprecatedAttributeArgsParser {
since: Option<String>,
note: Option<String>,
}
impl Parse for DeprecatedAttributeArgsParser {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut since: Option<String> = None;
let mut note: Option<String> = None;
if input.peek(kw::since) {
input.parse::<kw::since>()?;
input.parse::<Token![=]>()?;
since = lit_to_string(input.parse()?);
}
if input.peek(Token![,]) && input.peek2(kw::note) {
input.parse::<Token![,]>()?;
input.parse::<kw::note>()?;
input.parse::<Token![=]>()?;
note = lit_to_string(input.parse()?);
}
Ok(Self { since, note })
}
}
+3 -9
View File
@@ -164,17 +164,11 @@ impl VirtualDom {
});
// Now that all the references are gone, we can safely drop our own references in our listeners.
let mut listeners = scope.attributes_to_drop.borrow_mut();
let mut listeners = scope.attributes_to_drop_before_render.borrow_mut();
listeners.drain(..).for_each(|listener| {
let listener = unsafe { &*listener };
match &listener.value {
AttributeValue::Listener(l) => {
_ = l.take();
}
AttributeValue::Any(a) => {
_ = a.take();
}
_ => (),
if let AttributeValue::Listener(l) = &listener.value {
_ = l.take();
}
});
}
+22 -3
View File
@@ -1,10 +1,13 @@
use crate::nodes::RenderReturn;
use crate::{Attribute, AttributeValue};
use bumpalo::Bump;
use std::cell::RefCell;
use std::cell::{Cell, UnsafeCell};
pub(crate) struct BumpFrame {
pub bump: UnsafeCell<Bump>,
pub node: Cell<*const RenderReturn<'static>>,
pub(crate) attributes_to_drop_before_reset: RefCell<Vec<*const Attribute<'static>>>,
}
impl BumpFrame {
@@ -13,6 +16,7 @@ impl BumpFrame {
Self {
bump: UnsafeCell::new(bump),
node: Cell::new(std::ptr::null()),
attributes_to_drop_before_reset: Default::default(),
}
}
@@ -31,8 +35,23 @@ impl BumpFrame {
unsafe { &*self.bump.get() }
}
#[allow(clippy::mut_from_ref)]
pub(crate) unsafe fn bump_mut(&self) -> &mut Bump {
unsafe { &mut *self.bump.get() }
pub(crate) fn add_attribute_to_drop(&self, attribute: *const Attribute<'static>) {
self.attributes_to_drop_before_reset
.borrow_mut()
.push(attribute);
}
pub(crate) unsafe fn reset(&self) {
let mut attributes = self.attributes_to_drop_before_reset.borrow_mut();
attributes.drain(..).for_each(|attribute| {
let attribute = unsafe { &*attribute };
if let AttributeValue::Any(l) = &attribute.value {
_ = l.take();
}
});
unsafe {
let bump = &mut *self.bump.get();
bump.reset();
}
}
}
+8 -6
View File
@@ -1,4 +1,6 @@
#![doc = include_str!("../README.md")]
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
#![warn(missing_docs)]
mod any_props;
@@ -32,7 +34,7 @@ pub(crate) mod innerlude {
pub use crate::nodes::RenderReturn;
pub use crate::nodes::*;
pub use crate::properties::*;
pub use crate::runtime::{in_runtime, override_runtime, Runtime};
pub use crate::runtime::{Runtime, RuntimeGuard};
pub use crate::scheduler::*;
pub use crate::scope_context::*;
pub use crate::scopes::*;
@@ -87,11 +89,11 @@ pub use crate::innerlude::{
pub mod prelude {
pub use crate::innerlude::{
consume_context, consume_context_from_scope, current_scope_id, fc_to_builder, has_context,
in_runtime, override_runtime, provide_context, provide_context_to_scope,
provide_root_context, push_future, remove_future, schedule_update_any, spawn,
spawn_forever, suspend, throw, AnyValue, Component, Element, Event, EventHandler, Fragment,
IntoAttributeValue, LazyNodes, Properties, Runtime, Scope, ScopeId, ScopeState, Scoped,
TaskId, Template, TemplateAttribute, TemplateNode, Throw, VNode, VirtualDom,
provide_context, provide_context_to_scope, provide_root_context, push_future,
remove_future, schedule_update_any, spawn, spawn_forever, suspend, throw, AnyValue,
Component, Element, Event, EventHandler, Fragment, IntoAttributeValue, IntoDynNode,
LazyNodes, Properties, Runtime, RuntimeGuard, Scope, ScopeId, ScopeState, Scoped, TaskId,
Template, TemplateAttribute, TemplateNode, Throw, VNode, VirtualDom,
};
}
+34 -47
View File
@@ -7,51 +7,6 @@ thread_local! {
static RUNTIMES: RefCell<Vec<Rc<Runtime>>> = RefCell::new(vec![]);
}
/// Run some code within a runtime
pub fn in_runtime<R>(runtime: Rc<Runtime>, f: impl FnOnce() -> R) -> R {
let _guard = RuntimeGuard::new(runtime);
f()
}
/// Override the current runtime. This must be used to override the current runtime when importing components from a dynamic library that has it's own runtime.
///
/// ```rust
/// use dioxus::prelude::*;
///
/// fn main() {
/// let virtual_dom = VirtualDom::new(app);
/// }
///
/// fn app(cx: Scope) -> Element {
/// render!{ Component { runtime: Runtime::current().unwrap() } }
/// }
///
/// // In a dynamic library
/// #[derive(Props)]
/// struct ComponentProps {
/// runtime: std::rc::Rc<Runtime>,
/// }
///
/// impl PartialEq for ComponentProps {
/// fn eq(&self, _other: &Self) -> bool {
/// true
/// }
/// }
///
/// fn Component(cx: Scope<ComponentProps>) -> Element {
/// cx.use_hook(|| override_runtime(cx.props.runtime.clone()));
///
/// render! { div {} }
/// }
/// ```
pub fn override_runtime(runtime: Rc<Runtime>) {
RUNTIMES.with(|stack| {
let mut stack = stack.borrow_mut();
stack.pop();
stack.push(runtime);
});
}
/// Pushes a new scope onto the stack
pub(crate) fn push_runtime(runtime: Rc<Runtime>) {
RUNTIMES.with(|stack| stack.borrow_mut().push(runtime));
@@ -143,10 +98,42 @@ impl Runtime {
}
}
pub(crate) struct RuntimeGuard(Rc<Runtime>);
/// A gaurd for a new runtime. This must be used to override the current runtime when importing components from a dynamic library that has it's own runtime.
///
/// ```rust
/// use dioxus::prelude::*;
///
/// fn main() {
/// let virtual_dom = VirtualDom::new(app);
/// }
///
/// fn app(cx: Scope) -> Element {
/// render!{ Component { runtime: Runtime::current().unwrap() } }
/// }
///
/// // In a dynamic library
/// #[derive(Props)]
/// struct ComponentProps {
/// runtime: std::rc::Rc<Runtime>,
/// }
///
/// impl PartialEq for ComponentProps {
/// fn eq(&self, _other: &Self) -> bool {
/// true
/// }
/// }
///
/// fn Component(cx: Scope<ComponentProps>) -> Element {
/// cx.use_hook(|| RuntimeGuard::new(cx.props.runtime.clone()));
///
/// render! { div {} }
/// }
/// ```
pub struct RuntimeGuard(Rc<Runtime>);
impl RuntimeGuard {
pub(crate) fn new(runtime: Rc<Runtime>) -> Self {
/// Create a new runtime guard that sets the current Dioxus runtime. The runtime will be reset when the guard is dropped
pub fn new(runtime: Rc<Runtime>) -> Self {
push_runtime(runtime.clone());
Self(runtime)
}
+2 -2
View File
@@ -35,7 +35,7 @@ impl VirtualDom {
hook_idx: Default::default(),
borrowed_props: Default::default(),
attributes_to_drop: Default::default(),
attributes_to_drop_before_render: Default::default(),
}));
let context =
@@ -54,7 +54,7 @@ impl VirtualDom {
let new_nodes = unsafe {
let scope = &self.scopes[scope_id.0];
scope.previous_frame().bump_mut().reset();
scope.previous_frame().reset();
scope.context().suspended.set(false);
+9 -3
View File
@@ -94,7 +94,7 @@ pub struct ScopeState {
pub(crate) hook_idx: Cell<usize>,
pub(crate) borrowed_props: RefCell<Vec<*const VComponent<'static>>>,
pub(crate) attributes_to_drop: RefCell<Vec<*const Attribute<'static>>>,
pub(crate) attributes_to_drop_before_render: RefCell<Vec<*const Attribute<'static>>>,
pub(crate) props: Option<Box<dyn AnyProps<'static>>>,
}
@@ -348,13 +348,19 @@ impl<'src> ScopeState {
pub fn render(&'src self, rsx: LazyNodes<'src, '_>) -> Element<'src> {
let element = rsx.call(self);
let mut listeners = self.attributes_to_drop.borrow_mut();
let mut listeners = self.attributes_to_drop_before_render.borrow_mut();
for attr in element.dynamic_attrs {
match attr.value {
AttributeValue::Any(_) | AttributeValue::Listener(_) => {
// We need to drop listeners before the next render because they may borrow data from the borrowed props which will be dropped
AttributeValue::Listener(_) => {
let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) };
listeners.push(unbounded);
}
// We need to drop any values manually to make sure that their drop implementation is called before the next render
AttributeValue::Any(_) => {
let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) };
self.previous_frame().add_attribute_to_drop(unbounded);
}
_ => (),
}
+1
View File
@@ -56,6 +56,7 @@ tokio_runtime = ["tokio"]
fullscreen = ["wry/fullscreen"]
transparent = ["wry/transparent"]
devtools = ["wry/devtools"]
tray = ["wry/tray"]
dox = ["wry/dox"]
hot-reload = ["dioxus-hot-reload"]
+7
View File
@@ -161,6 +161,7 @@ pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config)
// iOS panics if we create a window before the event loop is started
let props = Rc::new(Cell::new(Some(props)));
let cfg = Rc::new(Cell::new(Some(cfg)));
let mut is_visible_before_start = true;
event_loop.run(move |window_event, event_loop, control_flow| {
*control_flow = ControlFlow::Wait;
@@ -210,6 +211,8 @@ pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config)
// Create a dom
let dom = VirtualDom::new_with_props(root, props);
is_visible_before_start = cfg.window.window.visible;
let handler = create_new_window(
cfg,
event_loop,
@@ -323,6 +326,10 @@ pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config)
EventData::Ipc(msg) if msg.method() == "initialize" => {
let view = webviews.get_mut(&event.1).unwrap();
send_edits(view.dom.rebuild(), &view.desktop_context.webview);
view.desktop_context
.webview
.window()
.set_visible(is_visible_before_start);
}
EventData::Ipc(msg) if msg.method() == "browser_open" => {
+1 -1
View File
@@ -153,7 +153,7 @@ fn get_asset_root() -> Option<PathBuf> {
/// Get the mime type from a path-like string
fn get_mime_from_path(trimmed: &Path) -> Result<&'static str> {
if trimmed.ends_with(".svg") {
if trimmed.extension().is_some_and(|ext| ext == "svg") {
return Ok("image/svg+xml");
}
+1 -1
View File
@@ -13,7 +13,7 @@ pub fn build(
proxy: EventLoopProxy<UserWindowEvent>,
) -> (WebView, WebContext) {
let builder = cfg.window.clone();
let window = builder.build(event_loop).unwrap();
let window = builder.with_visible(false).build(event_loop).unwrap();
let file_handler = cfg.file_drop_handler.take();
let custom_head = cfg.custom_head.clone();
let index_file = cfg.custom_index.clone();
-1
View File
@@ -10,7 +10,6 @@ keywords = ["dom", "ui", "gui", "react", "terminal"]
license = "MIT OR Apache-2.0"
[dependencies]
dioxus = { workspace = true }
dioxus-core = { workspace = true, features = ["serialize"] }
dioxus-html = { workspace = true }
dioxus-native-core = { workspace = true, features = ["dioxus"] }
+4
View File
@@ -1,3 +1,7 @@
#![doc = include_str!("../README.md")]
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
mod element;
use std::{
+4
View File
@@ -1,3 +1,7 @@
#![doc = include_str!("../README.md")]
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
pub use dioxus_core as core;
#[cfg(feature = "hooks")]
+1
View File
@@ -28,6 +28,7 @@ impl CallbackApi {
}
}
#[must_use]
pub fn use_atom_context(cx: &ScopeState) -> &CallbackApi {
todo!()
}
+1
View File
@@ -13,6 +13,7 @@ use std::{
///
///
///
#[must_use]
pub fn use_atom_ref<'a, T: 'static>(
cx: &'a ScopeState,
atom: &'static AtomRef<T>,
+1 -1
View File
@@ -7,6 +7,6 @@ use dioxus_core::ScopeState;
pub fn use_atom_root(cx: &ScopeState) -> &Rc<AtomRoot> {
cx.use_hook(|| match cx.consume_context::<Rc<AtomRoot>>() {
Some(root) => root,
None => panic!("No atom root found in context. Did you forget place an AtomRoot component at the top of your app?"),
None => panic!("No atom root found in context. Did you forget to call use_init_atom_root at the top of your app?"),
})
}
+2
View File
@@ -2,10 +2,12 @@ use crate::{use_atom_root, AtomId, AtomRoot, Readable};
use dioxus_core::{ScopeId, ScopeState};
use std::rc::Rc;
#[must_use]
pub fn use_read<V: 'static>(cx: &ScopeState, f: impl Readable<V>) -> &V {
use_read_rc(cx, f).as_ref()
}
#[must_use]
pub fn use_read_rc<V: 'static>(cx: &ScopeState, f: impl Readable<V>) -> &Rc<V> {
let root = use_atom_root(cx);
+1
View File
@@ -2,6 +2,7 @@ use crate::{use_atom_root, Writable};
use dioxus_core::ScopeState;
use std::rc::Rc;
#[must_use]
pub fn use_set<T: 'static>(cx: &ScopeState, f: impl Writable<T>) -> &Rc<dyn Fn(T)> {
let root = use_atom_root(cx);
cx.use_hook(|| {
+4 -1
View File
@@ -30,6 +30,7 @@ use std::{
/// ))
/// }
/// ```
#[must_use]
pub fn use_atom_state<T: 'static>(cx: &ScopeState, f: impl Writable<T>) -> &AtomState<T> {
let root = crate::use_atom_root(cx);
@@ -85,7 +86,9 @@ impl<T: 'static> AtomState<T> {
/// ```
#[must_use]
pub fn current(&self) -> Rc<T> {
self.value.as_ref().unwrap().clone()
let atoms = self.root.atoms.borrow();
let slot = atoms.get(&self.id).unwrap();
slot.value.clone().downcast().unwrap()
}
/// Get the `setter` function directly without the `AtomState` wrapper.
+2
View File
@@ -1,4 +1,6 @@
#![doc = include_str!("../README.md")]
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
pub mod prelude {
pub use crate::*;
+1 -1
View File
@@ -11,7 +11,7 @@ keywords = ["ui", "gui", "react", "ssr", "fullstack"]
[dependencies]
# server functions
server_fn = { version = "0.4.6", default-features = false }
server_fn = { version = "0.5.2", default-features = false }
dioxus_server_macro = { workspace = true }
# warp
@@ -22,6 +22,7 @@ use std::sync::Arc;
/// will be allowed to continue
///
/// - dependencies: a tuple of references to values that are PartialEq + Clone
#[must_use = "Consider using `cx.spawn` to run a future without reading its value"]
pub fn use_server_future<T, F, D>(
cx: &ScopeState,
dependencies: D,
+9 -2
View File
@@ -121,8 +121,15 @@ impl<Props: Clone + serde::Serialize + serde::de::DeserializeOwned + Send + Sync
#[cfg(feature = "web")]
/// Launch the web application
pub fn launch_web(self) {
let cfg = self.web_cfg.hydrate(true);
dioxus_web::launch_with_props(self.component, get_root_props_from_document().unwrap(), cfg);
#[cfg(not(feature = "ssr"))]
{
let cfg = self.web_cfg.hydrate(true);
dioxus_web::launch_with_props(
self.component,
get_root_props_from_document().unwrap(),
cfg,
);
}
}
#[cfg(feature = "desktop")]
+7
View File
@@ -64,3 +64,10 @@ pub mod prelude {
pub use hooks::{server_cached::server_cached, server_future::use_server_future};
}
// Warn users about overlapping features
#[cfg(all(feature = "ssr", feature = "web"))]
compile_error!("The `ssr` feature (enabled by `warp`, `axum`, or `salvo`) and `web` feature are overlapping. Please choose one or the other.");
#[cfg(all(feature = "ssr", feature = "desktop"))]
compile_error!("The `ssr` feature (enabled by `warp`, `axum`, or `salvo`) and `desktop` feature are overlapping. Please choose one or the other.");
-8
View File
@@ -125,14 +125,6 @@ impl server_fn::ServerFunctionRegistry<()> for DioxusServerFnRegistry {
}
}
fn register(
url: &'static str,
server_function: ServerFunction,
encoding: server_fn::Encoding,
) -> Result<(), Self::Error> {
Self::register_explicit("", url, server_function, encoding)
}
/// Returns the server function registered at the given URL, or `None` if no function is registered at that URL.
fn get(url: &str) -> Option<server_fn::ServerFnTraitObj<()>> {
REGISTERED_SERVER_FUNCTIONS
+1 -1
View File
@@ -31,4 +31,4 @@ let store = Store::default();
## How it works
Internally
Internally, `generational-box` creates an arena of generational RefCell's that are recyled when the owner is dropped. You can think of the cells as something like `&'static RefCell<Box<dyn Any>>` with a generational check to make recyling a cell easier to debug. Then GenerationalBox's are `Copy` because the `&'static` pointer is `Copy`
+4 -4
View File
@@ -184,7 +184,7 @@ impl<T: 'static> GenerationalBox<T> {
}
/// Try to read the value. Returns None if the value is no longer valid.
pub fn try_read(&self) -> Option<Ref<'_, T>> {
pub fn try_read(&self) -> Option<Ref<'static, T>> {
self.validate()
.then(|| {
Ref::filter_map(self.raw.data.borrow(), |any| {
@@ -196,12 +196,12 @@ impl<T: 'static> GenerationalBox<T> {
}
/// Read the value. Panics if the value is no longer valid.
pub fn read(&self) -> Ref<'_, T> {
pub fn read(&self) -> Ref<'static, T> {
self.try_read().unwrap()
}
/// Try to write the value. Returns None if the value is no longer valid.
pub fn try_write(&self) -> Option<RefMut<'_, T>> {
pub fn try_write(&self) -> Option<RefMut<'static, T>> {
self.validate()
.then(|| {
RefMut::filter_map(self.raw.data.borrow_mut(), |any| {
@@ -213,7 +213,7 @@ impl<T: 'static> GenerationalBox<T> {
}
/// Write the value. Panics if the value is no longer valid.
pub fn write(&self) -> RefMut<'_, T> {
pub fn write(&self) -> RefMut<'static, T> {
self.try_write().unwrap()
}
+2
View File
@@ -37,6 +37,7 @@ use std::{
/// }
/// }
/// ```
#[must_use]
pub fn use_tracked_state<T: 'static>(cx: &ScopeState, init: impl FnOnce() -> T) -> &Tracked<T> {
cx.use_hook(|| {
let init = init();
@@ -160,6 +161,7 @@ impl<I> Drop for Tracker<I> {
}
}
#[must_use = "Consider using the `use_effect` hook to rerun an effect whenever the tracked state changes if you don't need the result of the computation"]
pub fn use_selector<I: 'static, O: Clone + PartialEq + 'static>(
cx: &ScopeState,
tracked: &Tracked<I>,
+26 -21
View File
@@ -1,7 +1,10 @@
#![doc = include_str!("../README.md")]
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
#![cfg_attr(feature = "nightly-features", feature(debug_refcell))]
#[macro_export]
/// A helper macro for using hooks and properties in async environements.
/// A helper macro for using hooks and properties in async environments.
///
/// # Usage
///
@@ -54,35 +57,37 @@ macro_rules! to_owned {
pub mod computed;
mod use_on_unmount;
pub use use_on_unmount::*;
mod use_on_destroy;
pub use use_on_destroy::*;
mod usecontext;
pub use usecontext::*;
mod use_context;
pub use use_context::*;
mod usestate;
pub use usestate::{use_state, UseState};
mod use_state;
pub use use_state::{use_state, UseState};
mod useref;
pub use useref::*;
mod use_ref;
pub use use_ref::*;
mod use_shared_state;
pub use use_shared_state::*;
mod usecoroutine;
pub use usecoroutine::*;
mod use_coroutine;
pub use use_coroutine::*;
mod usefuture;
pub use usefuture::*;
mod use_future;
pub use use_future::*;
mod useeffect;
pub use useeffect::*;
mod use_effect;
pub use use_effect::*;
mod usecallback;
pub use usecallback::*;
mod use_callback;
pub use use_callback::*;
mod usememo;
pub use usememo::*;
mod use_memo;
pub use use_memo::*;
mod userootcontext;
pub use userootcontext::*;
mod use_on_create;
pub use use_on_create::*;
mod use_root_context;
pub use use_root_context::*;
@@ -24,6 +24,7 @@ macro_rules! use_callback {
)
};
}
pub fn use_callback<T, R, F>(cx: &ScopeState, make: impl FnOnce() -> R) -> impl FnMut(T) + '_
where
R: FnMut(T) -> F + 'static,
File renamed without changes.
@@ -3,6 +3,7 @@ use dioxus_core::ScopeState;
/// Consume some context in the tree, providing a sharable handle to the value
///
/// Does not regenerate the value if the value is changed at the parent.
#[must_use]
pub fn use_context<T: 'static + Clone>(cx: &ScopeState) -> Option<&T> {
cx.use_hook(|| cx.consume_context::<T>()).as_ref()
}
@@ -79,6 +79,7 @@ where
/// Get a handle to a coroutine higher in the tree
///
/// See the docs for [`use_coroutine`] for more details.
#[must_use]
pub fn use_coroutine_handle<M: 'static>(cx: &ScopeState) -> Option<&Coroutine<M>> {
cx.use_hook(|| cx.consume_context::<Coroutine<M>>())
.as_ref()
@@ -3,13 +3,18 @@ use std::{any::Any, cell::Cell, future::Future};
use crate::UseFutureDep;
/// A hook that provides a future that executes after the hooks have been applied
/// A hook that provides a future that executes after the hooks have been applied.
///
/// Whenever the hooks dependencies change, the future will be re-evaluated.
/// If a future is pending when the dependencies change, the previous future
/// will be allowed to continue
/// will be allowed to continue.
///
/// - dependencies: a tuple of references to values that are `PartialEq` + `Clone`
/// **Note:** If your dependency list is always empty, use [`use_on_create`](crate::use_on_create).
///
/// ## Arguments
///
/// - `dependencies`: a tuple of references to values that are `PartialEq` + `Clone`.
/// - `future`: a closure that takes the `dependencies` as arguments and returns a `'static` future.
///
/// ## Examples
///
File renamed without changes.
@@ -28,6 +28,7 @@ use crate::UseFutureDep;
/// render!(Calculator { number: 0 })
/// }
/// ```
#[must_use = "Consider using `use_effect` to run rerun a callback when dependencies change"]
pub fn use_memo<T, D>(cx: &ScopeState, dependencies: D, callback: impl FnOnce(D::Out) -> T) -> &T
where
T: 'static,
File renamed without changes.
+27
View File
@@ -0,0 +1,27 @@
use dioxus_core::ScopeState;
use std::cell::Cell;
use std::future::Future;
/// A hook that runs a future when the component is mounted.
///
/// This is just [`use_effect`](crate::use_effect), but with no dependencies.
/// If you have no dependencies, it's recommended to use this, not just because it's more readable,
/// but also because it's a tiny bit more efficient.
pub fn use_on_create<T, F>(cx: &ScopeState, future: impl FnOnce() -> F)
where
T: 'static,
F: Future<Output = T> + 'static,
{
let needs_regen = cx.use_hook(|| Cell::new(true));
if needs_regen.get() {
// We don't need regen anymore
needs_regen.set(false);
let fut = future();
cx.push_future(async move {
fut.await;
});
}
}
@@ -1,9 +1,20 @@
/// Creates a callback that will be run before the component is removed. This can be used to clean up side effects from the component (created with use_effect)
#[deprecated(
note = "Use `use_on_destroy` instead, which has the same functionality. \
This is deprecated because of the introduction of `use_on_create` which is better mirrored by `use_on_destroy`. \
The reason why `use_on_create` is not `use_on_mount` is because of potential confusion with `dioxus::events::onmounted`."
)]
pub fn use_on_unmount<D: FnOnce() + 'static>(cx: &dioxus_core::ScopeState, destroy: D) {
use_on_destroy(cx, destroy);
}
/// Creates a callback that will be run before the component is removed.
/// This can be used to clean up side effects from the component
/// (created with [`use_effect`](crate::use_effect)).
///
/// Example:
/// ```rust
/// use dioxus::prelude::*;
///
/// fn app(cx: Scope) -> Element {
/// let state = use_state(cx, || true);
/// render! {
@@ -25,7 +36,7 @@
/// }
/// }
/// }
///
/// fn child_component(cx: Scope) -> Element {
/// let original_scroll_position = use_state(cx, || 0.0);
/// use_effect(cx, (), move |_| {
@@ -38,8 +49,8 @@
/// original_scroll_position.set(window.scroll_y().unwrap());
/// }
/// });
/// use_on_unmount(cx, {
///
/// use_on_destroy(cx, {
/// to_owned![original_scroll_position];
/// /// restore scroll to the top of the page
/// move || {
@@ -47,7 +58,7 @@
/// window.scroll_with_x_and_y(*original_scroll_position.current(), 0.0);
/// }
/// });
///
/// render!{
/// div {
/// id: "my_element",
@@ -56,7 +67,7 @@
/// }
/// }
/// ```
pub fn use_on_unmount<D: FnOnce() + 'static>(cx: &dioxus_core::ScopeState, destroy: D) {
pub fn use_on_destroy<D: FnOnce() + 'static>(cx: &dioxus_core::ScopeState, destroy: D) {
cx.use_hook(|| LifeCycle {
ondestroy: Some(destroy),
});
@@ -110,6 +110,7 @@ use std::{
/// }
/// })
/// ```
#[must_use]
pub fn use_ref<T: 'static>(cx: &ScopeState, initialize_refcell: impl FnOnce() -> T) -> &UseRef<T> {
let hook = cx.use_hook(|| UseRef {
update: cx.schedule_update(),
File renamed without changes.
+1
View File
@@ -158,6 +158,7 @@ impl<T> ProvidedStateInner<T> {
/// Any time a component calls `write`, every consumer of the state will be notified - excluding the provider.
///
/// Right now, there is not a distinction between read-only and write-only, so every consumer will be notified.
#[must_use]
pub fn use_shared_state<T: 'static>(cx: &ScopeState) -> Option<&UseSharedState<T>> {
let state_owner: &mut Option<UseSharedStateOwner<T>> = &mut *cx.use_hook(move || {
let scope_id = cx.scope_id();
File renamed without changes.
@@ -30,6 +30,7 @@ use std::{
/// ))
/// }
/// ```
#[must_use]
pub fn use_state<T: 'static>(
cx: &ScopeState,
initial_state_fn: impl FnOnce() -> T,
@@ -336,6 +337,50 @@ impl<T> PartialEq<UseState<T>> for UseState<T> {
}
}
impl<T: std::cmp::PartialOrd> PartialOrd<T> for UseState<T> {
fn ge(&self, other: &T) -> bool {
*self.current_val >= *other
}
fn gt(&self, other: &T) -> bool {
*self.current_val > *other
}
fn le(&self, other: &T) -> bool {
*self.current_val <= *other
}
fn lt(&self, other: &T) -> bool {
*self.current_val < *other
}
fn partial_cmp(&self, other: &T) -> Option<std::cmp::Ordering> {
(*self.current_val).partial_cmp(other)
}
}
impl<T: std::cmp::PartialOrd> PartialOrd<UseState<T>> for UseState<T> {
fn ge(&self, other: &UseState<T>) -> bool {
self.current_val >= other.current_val
}
fn gt(&self, other: &UseState<T>) -> bool {
self.current_val > other.current_val
}
fn le(&self, other: &UseState<T>) -> bool {
self.current_val <= other.current_val
}
fn lt(&self, other: &UseState<T>) -> bool {
self.current_val < other.current_val
}
fn partial_cmp(&self, other: &UseState<T>) -> Option<std::cmp::Ordering> {
self.current_val.partial_cmp(&other.current_val)
}
}
impl<T: Debug> Debug for UseState<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.current_val)
View File
Whitespace-only changes.
+4 -3
View File
@@ -12,7 +12,7 @@ keywords = ["dom", "ui", "gui", "react", "hot-reloading"]
[dependencies]
dioxus-rsx = { workspace = true }
dioxus-core = { workspace = true, features = ["serialize"] }
dioxus-html = { workspace = true }
dioxus-html = { workspace = true, optional = true }
interprocess-docfix = { version = "1.2.2" }
notify = { version = "5.0.0", optional = true }
@@ -24,5 +24,6 @@ once_cell = { version = "1.17.0", optional = true }
ignore = { version = "0.4.19", optional = true }
[features]
default = []
file_watcher = ["ignore", "chrono", "notify", "execute", "once_cell", "ignore", "dioxus-html/hot-reload-context"]
default = ["dioxus-html"]
custom_file_watcher = ["ignore", "chrono", "notify", "execute", "once_cell", "ignore"]
file_watcher = ["custom_file_watcher", "dioxus-html/hot-reload-context"]
+5 -3
View File
@@ -13,11 +13,12 @@ use dioxus_rsx::{
};
use interprocess_docfix::local_socket::{LocalSocketListener, LocalSocketStream};
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
pub use dioxus_html::HtmlCtx;
use serde::{Deserialize, Serialize};
pub struct Config<Ctx: HotReloadingContext = HtmlCtx> {
#[cfg(feature = "file_watcher")]
use dioxus_html::HtmlCtx;
pub struct Config<Ctx: HotReloadingContext> {
root_path: &'static str,
listening_paths: &'static [&'static str],
excluded_paths: &'static [&'static str],
@@ -39,6 +40,7 @@ impl<Ctx: HotReloadingContext> Default for Config<Ctx> {
}
}
#[cfg(feature = "file_watcher")]
impl Config<HtmlCtx> {
pub const fn new() -> Self {
Self {
+2 -2
View File
@@ -6,9 +6,9 @@ pub use dioxus_html::HtmlCtx;
use interprocess_docfix::local_socket::LocalSocketStream;
use serde::{Deserialize, Serialize};
#[cfg(feature = "file_watcher")]
#[cfg(feature = "custom_file_watcher")]
mod file_watcher;
#[cfg(feature = "file_watcher")]
#[cfg(feature = "custom_file_watcher")]
pub use file_watcher::*;
/// A message the hot reloading server sends to the client
+2
View File
@@ -1107,6 +1107,7 @@ builder_constructors! {
formnovalidate: Bool DEFAULT,
formtarget: Target DEFAULT,
height: isize DEFAULT,
initial_checked: Bool DEFAULT,
list: Id DEFAULT,
max: String DEFAULT,
maxlength: usize DEFAULT,
@@ -1203,6 +1204,7 @@ builder_constructors! {
value: String DEFAULT,
selected: Bool volatile,
initial_selected: Bool DEFAULT,
};
/// Build a
+1
View File
@@ -33,6 +33,7 @@ type EvalCreator = Rc<dyn Fn(&str) -> Result<UseEval, EvalError>>;
/// parts is practically asking for a hacker to find an XSS vulnerability in
/// it. **This applies especially to web targets, where the JavaScript context
/// has access to most, if not all of your application data.**
#[must_use]
pub fn use_eval(cx: &ScopeState) -> &EvalCreator {
&*cx.use_hook(|| {
let eval_provider = cx
+3
View File
@@ -1,3 +1,6 @@
#![doc = include_str!("../README.md")]
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
#![allow(non_snake_case)]
//! # Dioxus Namespace for HTML
+6
View File
@@ -51,9 +51,15 @@ export function setAttributeInner(node, field, value, ns) {
case "checked":
node.checked = truthy(value);
break;
case "initial_checked":
node.defaultChecked = truthy(value);
break;
case "selected":
node.selected = truthy(value);
break;
case "initial_selected":
node.defaultSelected = truthy(value);
break;
case "dangerous_inner_html":
node.innerHTML = value;
break;
+4
View File
@@ -1,3 +1,7 @@
#![doc = include_str!("../README.md")]
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
pub static INTERPRETER_JS: &str = include_str!("./interpreter.js");
pub static COMMON_JS: &str = include_str!("./common.js");
@@ -80,9 +80,15 @@ mod js {
case "checked":
node.checked = truthy(value);
break;
case "initial_checked":
node.defaultChecked = truthy(value);
break;
case "selected":
node.selected = truthy(value);
break;
case "initial_selected":
node.defaultSelected = truthy(value);
break;
case "dangerous_inner_html":
node.innerHTML = value;
break;
+36 -25
View File
@@ -19,32 +19,43 @@ async fn main() {
let addr: std::net::SocketAddr = ([127, 0, 0, 1], 3030).into();
let view = dioxus_liveview::LiveViewPool::new();
let index_page_with_glue = |glue: &str| {
Html(format!(
r#"
<!DOCTYPE html>
<html>
<head> <title>Dioxus LiveView with axum</title> </head>
<body> <div id="main"></div> </body>
{glue}
</html>
"#,
))
};
let app = Router::new()
.route(
"/",
get(move || async move {
Html(format!(
r#"
<!DOCTYPE html>
<html>
<head> <title>Dioxus LiveView with axum</title> </head>
<body> <div id="main"></div> </body>
{glue}
</html>
"#,
glue = dioxus_liveview::interpreter_glue(&format!("ws://{addr}/ws"))
))
}),
)
.route(
"/ws",
get(move |ws: WebSocketUpgrade| async move {
ws.on_upgrade(move |socket| async move {
_ = view.launch(dioxus_liveview::axum_socket(socket), app).await;
})
}),
);
let app =
Router::new()
.route(
"/",
get(move || async move {
index_page_with_glue(&dioxus_liveview::interpreter_glue(&format!(
"ws://{addr}/ws"
)))
}),
)
.route(
"/as-path",
get(move || async move {
index_page_with_glue(&dioxus_liveview::interpreter_glue("/ws"))
}),
)
.route(
"/ws",
get(move |ws: WebSocketUpgrade| async move {
ws.on_upgrade(move |socket| async move {
_ = view.launch(dioxus_liveview::axum_socket(socket), app).await;
})
}),
);
println!("Listening on http://{addr}");
+42 -3
View File
@@ -1,3 +1,7 @@
#![doc = include_str!("../README.md")]
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
pub mod adapters {
#[cfg(feature = "warp")]
pub mod warp_adapter;
@@ -93,14 +97,49 @@ static MAIN_JS: &str = include_str!("./main.js");
/// This script that gets injected into your app connects this page to the websocket endpoint
///
/// Once the endpoint is connected, it will send the initial state of the app, and then start
/// processing user events and returning edits to the liveview instance
pub fn interpreter_glue(url: &str) -> String {
/// processing user events and returning edits to the liveview instance.
///
/// You can pass a relative path prefixed with "/", or enter a full URL including the protocol
/// (`ws:` or `wss:`) as an argument.
///
/// If you enter a relative path, the web client automatically prefixes the host address in
/// `window.location` when creating a web socket to LiveView.
///
/// ```
/// // Creates websocket connection to same host as current page
/// interpreter_glue("/api/liveview");
///
/// // Creates websocket connection to specified url
/// interpreter_glue("ws://localhost:8080/api/liveview");
/// ```
pub fn interpreter_glue(url_or_path: &str) -> String {
// If the url starts with a `/`, generate glue which reuses current host
let get_ws_url = if url_or_path.starts_with('/') {
r#"
let loc = window.location;
let new_url = "";
if (loc.protocol === "https:") {{
new_url = "wss:";
}} else {{
new_url = "ws:";
}}
new_url += "//" + loc.host + path;
return new_url;
"#
} else {
"return path;"
};
let js = &*INTERPRETER_JS;
let common = &*COMMON_JS;
format!(
r#"
<script>
var WS_ADDR = "{url}";
function __dioxusGetWsUrl(path) {{
{get_ws_url}
}}
var WS_ADDR = __dioxusGetWsUrl("{url_or_path}");
{js}
{common}
{MAIN_JS}
+2
View File
@@ -1,3 +1,5 @@
#![doc = include_str!("../README.md")]
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
pub use dioxus_desktop::*;
+4
View File
@@ -1,3 +1,7 @@
#![doc = include_str!("../README.md")]
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
extern crate proc_macro;
use std::collections::HashSet;
Loaded 100 of 159 files, more files were not shown because too many files have changed in this diff. Show more