Merge branch 'master' into use-server-function

This commit is contained in:
Evan Almloff 2023-07-19 14:19:03 -07:00
parent f3d1600822
commit 7e2ef1260f
81 changed files with 483 additions and 375 deletions

View File

@ -3,6 +3,10 @@ name: docs stable
on:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
build-deploy:
runs-on: ubuntu-latest

View File

@ -8,6 +8,10 @@ on:
branches:
- master
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
build-deploy:
runs-on: ubuntu-latest

View File

@ -24,6 +24,10 @@ on:
- lib.rs
- Cargo.toml
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
test:
if: github.event.pull_request.draft == false

View File

@ -27,6 +27,10 @@ on:
- lib.rs
- Cargo.toml
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
check:
if: github.event.pull_request.draft == false

View File

@ -6,6 +6,13 @@ on:
branches:
- 'auto'
- 'try'
paths:
- packages/**
- examples/**
- src/**
- .github/**
- lib.rs
- Cargo.toml
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
branches:
@ -31,7 +38,9 @@ env:
# - tokio-stream/Cargo.toml
# rust_min: 1.49.0
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
test:

View File

@ -4,22 +4,25 @@ on:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
defaults:
run:
working-directory: ./playwright-tests
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
test:
if: github.event.pull_request.draft == false
timeout-minutes: 60
runs-on: ubuntu-20.04
steps:
# Do our best to cache the toolchain and node install steps
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- name: Install dependencies
run: npm ci
- name: Install Playwright
run: npm install -D @playwright/test
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
@ -29,11 +32,18 @@ jobs:
- uses: Swatinem/rust-cache@v2
- name: Install WASM toolchain
run: rustup target add wasm32-unknown-unknown
- name: Install Dioxus CLI
uses: actions-rs/cargo@v1
with:
command: install
args: --path packages/cli
- name: Install dependencies
run: npm ci
- name: Install Playwright
run: npm install -D @playwright/test
- name: Install Playwright Browsers
run: npx playwright install --with-deps
# Cache the CLI by using cargo run internally
# - name: Install Dioxus CLI
# uses: actions-rs/cargo@v1
# with:
# command: install
# args: --path packages/cli
- name: Run Playwright tests
run: npx playwright test
- uses: actions/upload-artifact@v3

View File

@ -24,6 +24,10 @@ on:
- lib.rs
- Cargo.toml
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
test:
if: github.event.pull_request.draft == false

View File

@ -21,7 +21,7 @@ fn runs_in_browser() {
Then, when you run
```console
dioxus test --chrome
dx test --chrome
```
Dioxus will build and test your code using the Chrome browser as a harness.

View File

@ -14,13 +14,13 @@ We start will a hello world program. This program renders a desktop app with the
## The rsx! Macro
Before the Rust compiler runs the program, it will expand all macros. Here is what the hello world example looks like expanded:
Before the Rust compiler runs the program, it will expand all [macros](https://doc.rust-lang.org/reference/procedural-macros.html). Here is what the hello world example looks like expanded:
```rust, no_run
{{#include ../../../examples/readme_expanded.rs}}
```
The rsx macro separates the static parts of the rsx (the template) and the dynamic parts (the dynamic_nodes and dynamic_attributes).
The rsx macro separates the static parts of the rsx (the template) and the dynamic parts (the [dynamic_nodes](https://docs.rs/dioxus-core/0.3.2/dioxus_core/prelude/struct.VNode.html#structfield.dynamic_nodes) and [dynamic_attributes](https://docs.rs/dioxus-core/0.3.2/dioxus_core/prelude/struct.VNode.html#structfield.dynamic_attrs)).
The static template only contains the parts of the rsx that cannot change at runtime with holes for the dynamic parts:
@ -32,17 +32,17 @@ The dynamic_nodes and dynamic_attributes are the parts of the rsx that can chang
## Launching the App
The app is launched by calling the `launch` function with the root component. Internally, this function will create a new web view using [wry](https://docs.rs/wry/latest/wry/) and create a virtual dom with the root component. This guide will not explain the renderer in-depth, but you can read more about it in the [custom renderer](/guide/custom-renderer) section.
The app is launched by calling the `launch` function with the root component. Internally, this function will create a new web view using [wry](https://docs.rs/wry/latest/wry/) and create a virtual dom with the root component (`fn app()` in the readme example). This guide will not explain the renderer in-depth, but you can read more about it in the [custom renderer](/guide/custom-renderer) section.
## The Virtual DOM
Before we dive into the initial render in the virtual dom, we need to discuss what the virtual dom is. The virtual dom is a representation of the dom that is used to diff the current dom from the new dom. This diff is then used to create a list of mutations that need to be applied to the dom.
Before we dive into the initial render in the virtual DOM, we need to discuss what the virtual DOM is. The virtual DOM is a representation of the DOM that is used to diff the current DOM from the new DOM. This diff is then used to create a list of mutations that need to be applied to the DOM to bring it into sync with the virtual DOM.
The Virtual Dom roughly looks like this:
The Virtual DOM roughly looks like this:
```rust, no_run
pub struct VirtualDom {
// All the templates that have been created or set durring hot reloading
// All the templates that have been created or set during hot reloading
pub(crate) templates: FxHashMap<TemplateId, FxHashMap<usize, Template<'static>>>,
// A slab of all the scopes that have been created
@ -63,64 +63,74 @@ pub struct VirtualDom {
```
> What is a [slab](https://docs.rs/slab/latest/slab/)?
>
> A slab acts like a hashmap with integer keys if you don't care about the value of the keys. It is internally backed by a dense vector which makes it more efficient than a hashmap. When you insert a value into a slab, it returns an integer key that you can use to retrieve the value later.
> How does Dioxus use slabs?
> Dioxus uses "synchronized slabs" to communicate between the renderer and the VDOM. When an node is created in the Virtual Dom, a ElementId is passed along with the mutation to the renderer to identify the node. These ids are used by the Virtual Dom to reference that nodes in future mutations like setting an attribute on a node or removing a node.
> When the renderer sends an event to the Virtual Dom, it sends the ElementId of the node that the event was triggered on. The Virtual Dom uses this id to find the node in the slab and then run the necessary event handlers.
>
> Dioxus uses "synchronized slabs" to communicate between the renderer and the VDOM. When a node is created in the Virtual DOM, an (elementId, mutation) pair is passed to the renderer to identify that node, which the renderer will then render in actual DOM. These ids are also used by the Virtual Dom to reference that node in future mutations, like setting an attribute on a node or removing a node. When the renderer sends an event to the Virtual Dom, it sends the ElementId of the node that the event was triggered on. The Virtual DOM uses this id to find that node in the slab and then run the necessary event handlers.
The virtual dom is a tree of scopes. A new scope is created for every component when it is first rendered and recycled when the component is unmounted.
The virtual DOM is a tree of scopes. A new `Scope` is created for every component when it is first rendered and recycled when the component is unmounted.
Scopes serve three main purposes:
1. They store the state of hooks used by the component
2. They store the state for the context API
3. They store the current and previous VNode that was rendered for diffing
2. They store the state for the context API (for example: using
[use_shared_state_provider](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_shared_state_provider.html)).
3. They store the current and previous versions of the `VNode` that was rendered, so they can be
diffed to generate the set of mutations needed to re-render it.
### The Initial Render
The root scope is created and rebuilt:
1. The root component is run
2. The root component returns a VNode
3. Mutations for the VNode are created and added to the mutation list (this may involve creating new child components)
4. The VNode is stored in the root scope
2. The root component returns a `VNode`
3. Mutations for this `VNode` are created and added to the mutation list (this may involve creating new child components)
4. The `VNode` is stored in the root's `Scope`.
After the root scope is built, the mutations are sent to the renderer to be applied to the dom.
After the root's `Scope` is built, all generated mutations are sent to the renderer, which applies them to the DOM.
After the initial render, the root scope looks like this:
After the initial render, the root `Scope` looks like this:
[![](https://mermaid.ink/img/pako:eNqtVE1P4zAQ_SuzPrWikRpWXCLtBRDisItWsOxhCaqM7RKricdyJrQV8N93QtvQNCkfEnOynydv3nxkHoVCbUQipjnOVSYDwc_L1AFbWd3dB-kzuEQkuFLoDUwDFkCZAek9nGDh0RlHK__atA1GkUUHf45f0YbppAqB_aOzIAvz-t7-chN_Y-1bw1WSJKsglIu2w9tktWXxIIuHURT5XCqTYa5NmDguw2R8c5MKq2GcgF46WTB_jafi9rZL0yi5q4jQTSrf9altO4okCn1Ratwyz55Qxuku2ITlTMgs6HCQimsPmb3PvqVi-L5gjXP3QcnxWnL8JZLrwGvR31n0KV-Bx6-r-oVkT_-3G1S-NQLbk9i8rj7udP2cixed2QcDCitHJiQw7ub3EVlNecrPjudG2-6soFO5VbMECmR9T5OnlUY4-AFxfw9aTFst3McU9TK1Otm6NEn_DubBYlX2_dglLXOz48FgwJmJ5lZTlhz6xWgNaFnyDgpymcARHO0W2a9J_l5w2wYXvHuGPcqaQ-rESBQmFNJq3nCPNZoK3l4sUSR81DLMUpG6Z_aTFeHV0imRUKjMSFReSzKnVnKGhUimMi8ZNdoShl-rlfmyOUfCS_cPcePz_B_Wl4pc?type=png)](https://mermaid.live/edit#pako:eNqtVE1P4zAQ_SuzPrWikRpWXCLtBRDisItWsOxhCaqM7RKricdyJrQV8N93QtvQNCkfEnOynydv3nxkHoVCbUQipjnOVSYDwc_L1AFbWd3dB-kzuEQkuFLoDUwDFkCZAek9nGDh0RlHK__atA1GkUUHf45f0YbppAqB_aOzIAvz-t7-chN_Y-1bw1WSJKsglIu2w9tktWXxIIuHURT5XCqTYa5NmDguw2R8c5MKq2GcgF46WTB_jafi9rZL0yi5q4jQTSrf9altO4okCn1Ratwyz55Qxuku2ITlTMgs6HCQimsPmb3PvqVi-L5gjXP3QcnxWnL8JZLrwGvR31n0KV-Bx6-r-oVkT_-3G1S-NQLbk9i8rj7udP2cixed2QcDCitHJiQw7ub3EVlNecrPjudG2-6soFO5VbMECmR9T5OnlUY4-AFxfw9aTFst3McU9TK1Otm6NEn_DubBYlX2_dglLXOz48FgwJmJ5lZTlhz6xWgNaFnyDgpymcARHO0W2a9J_l5w2wYXvHuGPcqaQ-rESBQmFNJq3nCPNZoK3l4sUSR81DLMUpG6Z_aTFeHV0imRUKjMSFReSzKnVnKGhUimMi8ZNdoShl-rlfmyOUfCS_cPcePz_B_Wl4pc)
### Waiting for Events
The Virtual Dom will only ever rerender a scope if it is marked as dirty. Each hook is responsible for marking the scope as dirty if the state has changed. Hooks can mark a scope as dirty by sending a message to the Virtual Dom's channel.
The Virtual DOM will only ever re-render a `Scope` if it is marked as dirty. Each hook is responsible for marking the `Scope` as dirty if the state has changed. Hooks can mark a scope as dirty by sending a message to the Virtual Dom's channel. You can see the [implementations](https://github.com/DioxusLabs/dioxus/tree/master/packages/hooks) for the hooks dioxus includes by default on how this is done. Calling `needs_update()` on a hook will also cause it to mark its scope as dirty.
There are generally two ways a scope is marked as dirty:
1. The renderer triggers an event: This causes an event listener to be called if needed which may mark a component as dirty
2. The renderer calls wait for work: This polls futures which may mark a component as dirty
1. The renderer triggers an event: An event listener on this event may be called, which may mark a
component as dirty, if processing the event resulted in any generated any mutations.
2. The renderer calls
[`wait_for_work`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.VirtualDom.html#method.wait_for_work):
This polls dioxus internal future queue. One of these futures may mark a component as dirty.
Once at least one scope is marked as dirty, the renderer can call `render_with_deadline` to diff the dirty scopes.
Once at least one `Scope` is marked as dirty, the renderer can call [`render_with_deadline`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.VirtualDom.html#method.render_with_deadline) to diff the dirty scopes.
### Diffing Scopes
If the user clicked the "up high" button, the root scope would be marked as dirty by the use_state hook. Once the desktop renderer calls `render_with_deadline`, the root scope would be diffed.
When a user clicks the "up high" button, the root `Scope` will be marked as dirty by the `use_state` hook. The desktop renderer will then call `render_with_deadline`, which will diff the root `Scope`.
To start the diffing process, the component is run. After the root component is run it will look like this:
To start the diffing process, the component function is run. After the root component is run it, the root `Scope` will look like this:
[![](https://mermaid.ink/img/pako:eNrFVlFP2zAQ_iuen0BrpCaIl0i8AEJ72KQJtpcRFBnbJVYTn-U4tBXw33dpG5M2CetoBfdkny_ffb67fPIT5SAkjekkhxnPmHXk-3WiCVpZ3T9YZjJyDeDIDQcjycRCQVwmCTOGXEBhQEvtVvG1CWUldwo0-XX-6vVIF5W1GB9cWVbI1_PNL5v8jW3uPFbpmFOc2HK-GfA2WG1ZeJSFx0EQmJxxmUEupE01liEd394mVAkyjolYaFYgfu1P6N1dF8Yzua-cA51WphtTWzsLc872Zan9CnEGUkktuk6fFm_i5NxFRwn9bUimHrIvCT3-N2EBM70j5XBNOTwI5TrxmvQJkr7ELcHx67Jeggz0v92g8q0RaE-iP1193On6NyxecKUeJeFQaSdtTMLu_Xah5ctT_u94Nty2ZwU0zxWfxqQA5PecPq84kq9nfRw7SK0WDiEFZ4O37d34S_-08lFBVfb92KVb5HIrAp0WpjKYKeGyODLz0dohWIkaZNkiJqfkdLvIH6oRaTSoEmm0n06k0a5K0ZdpL61Io0Yt0nfpxc7UQ0_9cJrhyZ8syX-6brS706Mc489Vjja7fbWj3cxDqIdfJJqOaCFtwZTAV8hT7U0ovjBQRmiMS8HsNKGJfsE4Vjm4WWhOY2crOaKVEczJS8WwgAWNJywv0SuFcmB_rJ41y9fNiBqm_wA0MS9_AUuAiy0?type=png)](https://mermaid.live/edit#pako:eNrFVlFP2zAQ_iuen0BrpCaIl0i8AEJ72KQJtpcRFBnbJVYTn-U4tBXw33dpG5M2CetoBfdkny_ffb67fPIT5SAkjekkhxnPmHXk-3WiCVpZ3T9YZjJyDeDIDQcjycRCQVwmCTOGXEBhQEvtVvG1CWUldwo0-XX-6vVIF5W1GB9cWVbI1_PNL5v8jW3uPFbpmFOc2HK-GfA2WG1ZeJSFx0EQmJxxmUEupE01liEd394mVAkyjolYaFYgfu1P6N1dF8Yzua-cA51WphtTWzsLc872Zan9CnEGUkktuk6fFm_i5NxFRwn9bUimHrIvCT3-N2EBM70j5XBNOTwI5TrxmvQJkr7ELcHx67Jeggz0v92g8q0RaE-iP1193On6NyxecKUeJeFQaSdtTMLu_Xah5ctT_u94Nty2ZwU0zxWfxqQA5PecPq84kq9nfRw7SK0WDiEFZ4O37d34S_-08lFBVfb92KVb5HIrAp0WpjKYKeGyODLz0dohWIkaZNkiJqfkdLvIH6oRaTSoEmm0n06k0a5K0ZdpL61Io0Yt0nfpxc7UQ0_9cJrhyZ8syX-6brS706Mc489Vjja7fbWj3cxDqIdfJJqOaCFtwZTAV8hT7U0ovjBQRmiMS8HsNKGJfsE4Vjm4WWhOY2crOaKVEczJS8WwgAWNJywv0SuFcmB_rJ41y9fNiBqm_wA0MS9_AUuAiy0)
Next, the Virtual Dom will compare the new VNode with the previous VNode and only update the parts of the tree that have changed.
When a component is re-rendered, the Virtual Dom will compare the new VNode with the previous VNode and only update the parts of the tree that have changed.
Next, the Virtual DOM will compare the new VNode with the previous VNode and only update the parts of the tree that have changed. Because of this approach, when a component is re-rendered only the parts of the tree that have changed will be updated in the DOM by the renderer.
The diffing algorithm goes through the list of dynamic attributes and nodes and compares them to the previous VNode. If the attribute or node has changed, a mutation that describes the change is added to the mutation list.
Here is what the diffing algorithm looks like for the root scope (red lines indicate that a mutation was generated, and green lines indicate that no mutation was generated)
Here is what the diffing algorithm looks like for the root `Scope` (red lines indicate that a mutation was generated, and green lines indicate that no mutation was generated)
[![](https://mermaid.ink/img/pako:eNrFlFFPwjAQx7_KpT7Kko2Elya8qCE-aGLAJ5khpe1Yw9Zbug4k4He3OJjbGPig0T5t17tf_nf777aEo5CEkijBNY-ZsfAwDjW4kxfzhWFZDGNECxOOmYTIYAo2lsCyDG4xzVBLbcv8_RHKSG4V6orSIN0Wxrh8b2RYKr_uTyubd1W92GiWKg7aac6bOU3G803HbVk82xfP_Ok0JEqAT-FeLWJvpFYSOBbaSkMhCMnra5MgtfhWFrPWqHlhL2urT6atbU-oa0PNE8WXFFJ0-nazXakRroddGk9IwYEUnCd5w7Pddr5UTT8ZuVJY5F0fM7ebRLYyXNDgUnprJWxM-9lb7xAQLHe-M2xDYQCD9pD_2hez_kVn-P_rjLq6n3qjYv2iO5qz9DyvPdyv1ETp5eTTJ_7BGvQq8v1TVtl5jXUcRRcrqFh-dI4VtFlBN6t_ynLNkh5JpUmZEm5rbvfhkLiN6H4BQt2jYGYZklC_uzxWWJxsNCfUmkL2SJEJZuWdYs4cKaERS3IXlUJZNI_lGv7cxj2SMf2CeMx5_wBcbK19?type=png)](https://mermaid.live/edit#pako:eNrFlFFPwjAQx7_KpT7Kko2Elya8qCE-aGLAJ5khpe1Yw9Zbug4k4He3OJjbGPig0T5t17tf_nf777aEo5CEkijBNY-ZsfAwDjW4kxfzhWFZDGNECxOOmYTIYAo2lsCyDG4xzVBLbcv8_RHKSG4V6orSIN0Wxrh8b2RYKr_uTyubd1W92GiWKg7aac6bOU3G803HbVk82xfP_Ok0JEqAT-FeLWJvpFYSOBbaSkMhCMnra5MgtfhWFrPWqHlhL2urT6atbU-oa0PNE8WXFFJ0-nazXakRroddGk9IwYEUnCd5w7Pddr5UTT8ZuVJY5F0fM7ebRLYyXNDgUnprJWxM-9lb7xAQLHe-M2xDYQCD9pD_2hez_kVn-P_rjLq6n3qjYv2iO5qz9DyvPdyv1ETp5eTTJ_7BGvQq8v1TVtl5jXUcRRcrqFh-dI4VtFlBN6t_ynLNkh5JpUmZEm5rbvfhkLiN6H4BQt2jYGYZklC_uzxWWJxsNCfUmkL2SJEJZuWdYs4cKaERS3IXlUJZNI_lGv7cxj2SMf2CeMx5_wBcbK19)
## Conclusion
This is only a brief overview of how the Virtual Dom works. There are several aspects not yet covered in this guide including how the Virtual Dom handles async-components, keyed diffing, and how it uses [bump allocation](https://github.com/fitzgen/bumpalo) to efficiently allocate VNodes. If need more information about the Virtual Dom, you can read the code of the [core](https://github.com/DioxusLabs/dioxus/tree/master/packages/core) crate or reach out to us on [Discord](https://discord.gg/XgGxMSkvUM).
This is only a brief overview of how the Virtual Dom works. There are several aspects not yet covered in this guide including:
* How the Virtual DOM handles async-components
* Keyed diffing
* Using [bump allocation](https://github.com/fitzgen/bumpalo) to efficiently allocate VNodes.
If you need more information about the Virtual Dom, you can read the code of the [core](https://github.com/DioxusLabs/dioxus/tree/master/packages/core) crate or reach out to us on [Discord](https://discord.gg/XgGxMSkvUM).

View File

@ -76,7 +76,7 @@ Next, we need to modify our `main.rs` to use either hydrate on the client or ren
{{#include ../../../examples/hydration.rs}}
```
Now, build your client-side bundle with `dioxus build --features web` and run your server with `cargo run --features ssr`. You should see the same page as before, but now you can interact with the buttons!
Now, build your client-side bundle with `dx build --features web` and run your server with `cargo run --features ssr`. You should see the same page as before, but now you can interact with the buttons!
## Sycronizing props between the server and client
@ -99,4 +99,4 @@ The only thing we need to change on the client is the props. `dioxus-fullstack`
{{#include ../../../examples/hydration_props.rs}}
```
Now, build your client-side bundle with `dioxus build --features web` and run your server with `cargo run --features ssr`. Navigate to `http://localhost:8080/1` and you should see the counter start at 1. Navigate to `http://localhost:8080/2` and you should see the counter start at 2.
Now, build your client-side bundle with `dx build --features web` and run your server with `cargo run --features ssr`. Navigate to `http://localhost:8080/1` and you should see the counter start at 1. Navigate to `http://localhost:8080/2` and you should see the counter start at 2.

View File

@ -24,7 +24,7 @@ Next, add the server function to your `main.rs`:
{{#include ../../../examples/server_function.rs}}
```
Now, build your client-side bundle with `dioxus build --features web` and run your server with `cargo run --features ssr`. You should see a new button that multiplies the count by 2.
Now, build your client-side bundle with `dx build --features web` and run your server with `cargo run --features ssr`. You should see a new button that multiplies the count by 2.
## Conclusion

View File

@ -18,7 +18,7 @@ Hot reloading is automatically enabled when using the web renderer on debug buil
1. Run:
```bash
dioxus serve --hot-reload
dx serve --hot-reload
```
2. Change some code within a rsx or render macro

View File

@ -59,5 +59,5 @@ Edit your `main.rs`:
And to serve our app:
```bash
dioxus serve
dx serve
```

View File

@ -2,15 +2,16 @@
So far our components have had no state like a normal rust functions. However, in a UI component, it is often useful to have stateful functionality to build user interactions. For example, you might want to track whether the user has opened a drop-down, and render different things accordingly.
Hooks allow us to create state in our components. Hooks are Rust functions that take a reference to `ScopeState` (in a component, you can pass `cx`), and provide you with functionality and state.
Hooks allow us to create state in our components. Hooks are Rust functions that take a reference to [`ScopeState`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.ScopeState.html) (in a component, you can pass `cx`), and provide you with functionality and state.
## `use_state` Hook
[`use_state`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_state.html) is one of the simplest hooks.
- You provide a closure that determines the initial value
- You provide a closure that determines the initial value: `let mut count = use_state(cx, || 0);`
- `use_state` gives you the current value, and a way to update it by setting it to something else
- When the value updates, `use_state` makes the component re-render, and provides you with the new value
- When the value updates, `use_state` makes the component re-render (along with any other component
that references it), and then provides you with the new value.
For example, you might have seen the counter example, in which state (a number) is tracked using the `use_state` hook:
@ -45,10 +46,11 @@ But how can Dioxus differentiate between multiple hooks in the same component? A
This is only possible because the two hooks are always called in the same order, so Dioxus knows which is which. Because the order you call hooks matters, you must follow certain rules when using hooks:
1. Hooks may be only used in components or other hooks (we'll get to that later)
2. On every call to the component function
2. On every call to a component function
1. The same hooks must be called (except in the case of early returns, as explained later in the [Error Handling chapter](../best_practices/error_handling.md))
2. In the same order
3. Hooks name's should start with `use_` so you don't accidentally confuse them with regular functions
3. Hook names should start with `use_` so you don't accidentally confuse them with regular
functions (`use_state()`, `use_ref()`, `use_future()`, etc...)
These rules mean that there are certain things you can't do with hooks:
@ -74,9 +76,12 @@ These rules mean that there are certain things you can't do with hooks:
`use_state` is great for tracking simple values. However, you may notice in the [`UseState` API](https://docs.rs/dioxus/latest/dioxus/hooks/struct.UseState.html) that the only way to modify its value is to replace it with something else (e.g., by calling `set`, or through one of the `+=`, `-=` operators). This works well when it is cheap to construct a value (such as any primitive). But what if you want to maintain more complex data in the components state?
For example, suppose we want to maintain a `Vec` of values. If we stored it with `use_state`, the only way to add a new value to the list would be to create a new `Vec` with the additional value, and put it in the state. This is expensive! We want to modify the existing `Vec` instead.
For example, suppose we want to maintain a `Vec` of values. If we stored it with `use_state`, the
only way to add a new value to the list would be to copy the existing `Vec`, add our value to it,
and then replace the existing `Vec` in the state with it. This is expensive! We want to modify the
existing `Vec` instead.
Thankfully, there is another hook for that, `use_ref`! It is similar to `use_state`, but it lets you get a mutable reference to the contained data.
Thankfully, there is another hook for that, `use_ref`! It **is** similar to `use_state`, but it lets you get a mutable reference to the contained data.
Here's a simple example that keeps a list of events in a `use_ref`. We can acquire write access to the state with `.with_mut()`, and then just `.push` a new value to the state:
@ -84,4 +89,18 @@ Here's a simple example that keeps a list of events in a `use_ref`. We can acqui
{{#include ../../../examples/hooks_use_ref.rs:component}}
```
> The return values of `use_state` and `use_ref` (`UseState` and `UseRef`, respectively) are in some ways similar to [`Cell`](https://doc.rust-lang.org/std/cell/) and [`RefCell`](https://doc.rust-lang.org/std/cell/struct.RefCell.html) they provide interior mutability. However, these Dioxus wrappers also ensure that the component gets re-rendered whenever you change the state.
> The return values of `use_state` and `use_ref` (
> [`UseState`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.UseState.html) and
> [`UseRef`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.UseRef.html), respectively) are in
> some ways similar to [`Cell`](https://doc.rust-lang.org/std/cell/) and
> [`RefCell`](https://doc.rust-lang.org/std/cell/struct.RefCell.html) they provide interior
> mutability. However, these Dioxus wrappers also ensure that the component gets re-rendered
> whenever you change the state.
## Additional Resources
- [**dioxus_hooks** ](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/) rustdoc
- Documents all hook types included with dioxus by default Most of these are also covered in
later chapters of this guide.
- [Hooks Package](https://github.com/DioxusLabs/dioxus/tree/master/packages/hooks)

View File

@ -18,7 +18,7 @@ dioxus = { version = "*", features = ["hot-reload"] }
1. Execute:
```
dioxus serve --hot-reload
dx serve --hot-reload
```
2. alterar algum código dentro de uma macro `rsx`

View File

@ -8,7 +8,7 @@ enum Route {
#[route("/")]
Home {},
// PageNotFound is a catch all route that will match any route and placing the matched segments in the route field
#[route("/:...route")]
#[route("/:..route")]
PageNotFound { route: Vec<String> },
}
// ANCHOR_END: router

View File

@ -7,7 +7,7 @@ use dioxus_router::prelude::*;
#[rustfmt::skip]
enum Route {
// segments that start with :... are catch all segments
#[route("/blog/:...segments")]
#[route("/blog/:..segments")]
BlogPost {
// You must include catch all segment in child variants
segments: Vec<String>,

View File

@ -26,7 +26,7 @@ enum Route {
#[end_layout]
#[end_nest]
#[end_layout]
#[route("/:...route")]
#[route("/:..route")]
PageNotFound {
route: Vec<String>,
},

View File

@ -30,7 +30,7 @@ enum Route {
#[redirect("/", || Route::BlogList {})]
#[redirect("/:name", |name: String| Route::BlogPost { name })]
#[end_nest]
#[route("/:...route")]
#[route("/:..route")]
PageNotFound {
route: Vec<String>,
},

View File

@ -11,7 +11,7 @@ enum Route {
#[route("/")]
Home {},
#[end_layout]
#[route("/:...route")]
#[route("/:..route")]
PageNotFound { route: Vec<String> },
}
// ANCHOR_END: router

View File

@ -7,7 +7,7 @@ use dioxus_router::prelude::*;
enum Route {
#[route("/")]
Home {},
#[route("/:...route")]
#[route("/:..route")]
PageNotFound { route: Vec<String> },
}

View File

@ -11,7 +11,7 @@ enum Route {
#[route("/")]
Home {},
#[end_layout]
#[route("/:...route")]
#[route("/:..route")]
PageNotFound { route: Vec<String> },
}
// ANCHOR_END: router

View File

@ -9,8 +9,8 @@ It is also very much usable as a template for your projects, if you're aiming to
Make sure you have Dioxus CLI installed (if you're unsure, run `cargo install dioxus-cli`).
You can run `dioxus serve` in this directory to start the web server locally, or run
`dioxus build --release` to build the project so you can deploy it on a separate web-server.
You can run `dx serve` in this directory to start the web server locally, or run
`dx build --release` to build the project so you can deploy it on a separate web-server.
## Project Structure
```
@ -41,4 +41,4 @@ For service worker scripting (in JavaScript):
* [Service worker guide from PWABuilder](https://docs.pwabuilder.com/#/home/sw-intro)
* [Service worker examples, also from PWABuilder](https://github.com/pwa-builder/pwabuilder-serviceworkers)
If you want to stay as close to 100% Rust as possible, you can try using [wasi-worker](https://github.com/dunnock/wasi-worker) to replace the JS service worker file. The JSON manifest will still be required though.
If you want to stay as close to 100% Rust as possible, you can try using [wasi-worker](https://github.com/dunnock/wasi-worker) to replace the JS service worker file. The JSON manifest will still be required though.

View File

@ -1,32 +1,47 @@
#![allow(non_snake_case)]
use dioxus::prelude::*;
use tokio::time::sleep;
fn main() {
dioxus_desktop::launch(App);
}
fn App(cx: Scope) -> Element {
let enable_directory_upload = use_state(cx, || false);
let files_uploaded: &UseRef<Vec<String>> = use_ref(cx, Vec::new);
cx.render(rsx! {
label {
input {
r#type: "checkbox",
checked: "{enable_directory_upload}",
oninput: move |evt| {
enable_directory_upload.set(evt.value.parse().unwrap());
},
},
"Enable directory upload"
}
input {
r#type: "file",
accept: ".txt, .rs",
accept: ".txt,.rs",
multiple: true,
directory: **enable_directory_upload,
onchange: |evt| {
to_owned![files_uploaded];
async move {
if let Some(file_engine) = &evt.files {
let files = file_engine.files();
for file_name in &files {
if let Some(file) = file_engine.read_file_to_string(file_name).await{
files_uploaded.write().push(file);
}
for file_name in files {
sleep(std::time::Duration::from_secs(1)).await;
files_uploaded.write().push(file_name);
}
}
}
},
}
},
div { "progress: {files_uploaded.read().len()}" },
ul {
for file in files_uploaded.read().iter() {

View File

@ -30,7 +30,7 @@ enum Route {
#[redirect("/", || Route::BlogList {})]
#[redirect("/:name", |name: String| Route::BlogPost { name })]
#[end_nest]
#[route("/:...route")]
#[route("/:..route")]
PageNotFound {
route: Vec<String>,
},

View File

@ -122,7 +122,7 @@ npx tailwindcss -i ./input.css -o ./public/tailwind.css --watch
- Run the following command in the root of the project to start the dioxus dev server:
```bash
dioxus serve --hot-reload
dx serve --hot-reload
```
- Open the browser to http://localhost:8080

View File

@ -18,7 +18,7 @@ There are plenty Rust Elm-like frameworks in the world - we were not interested
The `RSX` DSL is _barely_ a DSL. Rustaceans will find the DSL very similar to simply assembling nested structs, but without the syntactical overhead of "Default" everywhere or having to jump through hoops with the builder pattern. Between RSX, HTML, the Raw Factory API, and the NodeBuilder syntax, there's plenty of options to choose from.
### What are the build times like? Why on earth would I choose Rust instead of JS/TS/Elm?
Dioxus builds as roughly as fast as a complex WebPack-TypeScript site. Compile times will be slower than an equivalent TypeScript site, but not unbearably slow. The Wasm compiler backend for Rust is very fast. Iterating on small components is basically instant and larger apps takes a few seconds. In practice, the compiler guarantees of Rust balance out the rebuild times.
dx builds as roughly as fast as a complex WebPack-TypeScript site. Compile times will be slower than an equivalent TypeScript site, but not unbearably slow. The Wasm compiler backend for Rust is very fast. Iterating on small components is basically instant and larger apps takes a few seconds. In practice, the compiler guarantees of Rust balance out the rebuild times.
### What about Yew/Seed/Sycamore/Dominator/Dodrio/Percy?
- Yew and Seed use an Elm-like pattern and don't support SSR or any alternate rendering platforms

View File

@ -86,13 +86,6 @@ dioxus-core = { workspace = true, features = ["serialize"] }
default = []
plugin = ["mlua"]
# install path dx and dioxus as the same command
# so, they're not really aliases
# eventually dx will defer to the right version of dioxus
[[bin]]
path = "src/main.rs"
name = "dioxus"
[[bin]]
path = "src/main.rs"
name = "dx"

View File

@ -4,7 +4,7 @@
name = "dioxus-cli"
# default platfrom
# you can also use `dioxus serve/build --platform XXX` to use other platform
# you can also use `dx serve/build --platform XXX` to use other platform
# value: web | desktop
default_platform = "desktop"
@ -42,4 +42,4 @@ script = []
# use binaryen.wasm-opt for output Wasm file
# binaryen just will trigger in `web` platform
binaryen = { wasm_opt = true }
binaryen = { wasm_opt = true }

View File

@ -24,7 +24,7 @@ cargo install --path . --debug
## Get Started
Use `dioxus create project-name` to initialize a new Dioxus project. <br>
Use `dx create project-name` to initialize a new Dioxus project. <br>
It will be cloned from the [dioxus-template](https://github.com/DioxusLabs/dioxus-template) repository.
@ -33,7 +33,7 @@ It will be cloned from the [dioxus-template](https://github.com/DioxusLabs/dioxu
Alternatively, you can specify the template path:
```
dioxus create hello --template gh:dioxuslabs/dioxus-template
dx create hello --template gh:dioxuslabs/dioxus-template
```
## Dioxus Config File

View File

@ -2,14 +2,14 @@
In this chapter we will introduce all `dioxus-cli` commands.
> You can also use `dioxus --help` to get cli help info.
> You can also use `dx --help` to get cli help info.
```
dioxus
dx
Build, bundle, & ship your Dioxus app
USAGE:
dioxus [OPTIONS] <SUBCOMMAND>
dx [OPTIONS] <SUBCOMMAND>
OPTIONS:
-h, --help Print help information
@ -23,4 +23,4 @@ SUBCOMMANDS:
help Print this message or the help of the given subcommand(s)
serve Build, watch & serve the Rust WASM app and all of its assets
translate Translate some html file into a Dioxus component
```
```

View File

@ -1,13 +1,13 @@
# Build
The `dioxus build` command can help you `pack & build` a dioxus project.
The `dx build` command can help you `pack & build` a dioxus project.
```
dioxus-build
dioxus-build
Build the Rust WASM app and all of its assets
USAGE:
dioxus build [OPTIONS]
dx build [OPTIONS]
OPTIONS:
--example <EXAMPLE> [default: ""]
@ -19,7 +19,7 @@ OPTIONS:
You can use this command to build a project:
```
dioxus build --release
dx build --release
```
## Target platform
@ -28,14 +28,14 @@ Use the `platform` option to choose your target platform:
```
# for desktop project
dioxus build --platform desktop
dx build --platform desktop
```
`platform` currently only supports `desktop` & `web`.
```
# for web project
dioxus build --platform web
dx build --platform web
```
## Specify workspace bin
@ -43,7 +43,7 @@ dioxus build --platform web
You can add the `--bin` option to select which crate you want Dioxus to build:
```
dioxus build --bin app
dx build --bin app
```
## Build Example
@ -52,5 +52,5 @@ You can use the `example` option to select a example to build:
```
# build the `test` example
dioxus build --exmaple test
```
dx build --exmaple test
```

View File

@ -1,13 +1,13 @@
# Clean
`dioxus clean` will clear the build artifacts (the out_dir and the cargo cache)
`dx clean` will clear the build artifacts (the out_dir and the cargo cache)
```
dioxus-clean
dioxus-clean
Clean build artifacts
USAGE:
dioxus clean [OPTIONS]
dx clean [OPTIONS]
OPTIONS:
--bin [default: None]
@ -16,12 +16,12 @@ OPTIONS:
# Example
```
dioxus clean
dx clean
```
# Specify workspace bin
You can add the `--bin` option to select which crate you want Dioxus to clean artifacts from:
```
dioxus clean --bin app
```
dx clean --bin app
```

View File

@ -1,13 +1,13 @@
# Serve
The `dioxus serve` can start a dev server with hot-reloading
The `dx serve` can start a dev server with hot-reloading
```
dioxus-serve
dioxus-serve
Build, watch & serve the Rust WASM app and all of its assets
USAGE:
dioxus serve [OPTIONS]
dx serve [OPTIONS]
OPTIONS:
--example <EXAMPLE> [default: ""]
@ -20,7 +20,7 @@ OPTIONS:
You can use this command to build project and start a dev server:
```
dioxus serve
dx serve
```
## Serve Example
@ -29,7 +29,7 @@ You can use the `example` option to serve a example:
```
# serve the `test` example
dioxus serve --exmaple test
dx serve --exmaple test
```
## Specify workspace bin
@ -37,7 +37,7 @@ dioxus serve --exmaple test
You can add the `--bin` option to select which crate you want Dioxus to build and serve:
```
dioxus serve --bin app
dx serve --bin app
```
## Open Browser
@ -45,7 +45,7 @@ dioxus serve --bin app
You can add the `--open` option to open system default browser when server startup:
```
dioxus serve --open
dx serve --open
```
## RSX Hot Reloading
@ -53,7 +53,7 @@ dioxus serve --open
You can add the `--hot-reload` flag to enable [rsx hot reloading](https://dioxuslabs.com/docs/0.3/guide/en/getting_started/hot_reload.html). This will allow you to reload some rsx changes without a full recompile:
```
dioxus serve --open
dx serve --open
```
## Cross Origin Policy
@ -66,5 +66,5 @@ You can add the `cross-origin-policy` option to change cross-origin header to:
```
```
dioxus serve --corss-origin-policy
```
dx serve --corss-origin-policy
```

View File

@ -1,13 +1,13 @@
# Translate
`dioxus translate` can translate some `html` file into a Dioxus compoent
`dx translate` can translate some `html` file into a Dioxus compoent
```
dioxus-translate
dioxus-translate
Translate some source file into a Dioxus component
USAGE:
dioxus translate [OPTIONS] [OUTPUT]
dx translate [OPTIONS] [OUTPUT]
ARGS:
<OUTPUT> Output file, defaults to stdout if not present
@ -22,7 +22,7 @@ OPTIONS:
You can use the `file` option to set path to the `html` file to translate:
```
dioxus transtale --file ./index.html
dx transtale --file ./index.html
```
## Output rsx to a file
@ -30,7 +30,7 @@ dioxus transtale --file ./index.html
You can pass a file to the traslate command to set the path to write the output of the command to:
```
dioxus translate --file ./index.html component.rsx
dx translate --file ./index.html component.rsx
```
## Output rsx to a file
@ -38,7 +38,7 @@ dioxus translate --file ./index.html component.rsx
Setting the `component` option will create a compoent from the HTML:
```
dioxus translate --file ./index.html --component
dx translate --file ./index.html --component
```
## Example
@ -65,4 +65,4 @@ fn component(cx: Scope) -> Element {
}
})
}
```
```

View File

@ -29,8 +29,8 @@ General application confiration:
# default: web
default_platform = "web"
```
if you change this to `desktop`, the `dioxus build` will default building a desktop app
3. ***out_dir*** - The directory to place the build artifacts from `dioxus build` or `dioxus service` into. This is also where the `assets` directory will be copied to
if you change this to `desktop`, the `dx build` will default building a desktop app
3. ***out_dir*** - The directory to place the build artifacts from `dx build` or `dx service` into. This is also where the `assets` directory will be copied to
```
out_dir = "dist"
```

View File

@ -4,10 +4,10 @@ Once you have the Dioxus CLI tool installed, you can use it to create dioxus pro
## Initializing a default project
First, run the `dioxus create` command to create a new project ready to be used with Dioxus and the Dioxus CLI:
First, run the `dx create` command to create a new project ready to be used with Dioxus and the Dioxus CLI:
```
dioxus create hello-dioxus
dx create hello-dioxus
```
> It will clone a default template from github template: [DioxusLabs/dioxus-template](https://github.com/DioxusLabs/dioxus-template)
@ -15,7 +15,7 @@ dioxus create hello-dioxus
>
> You can choose to create your project from a different template by passing the `template` argument:
> ```
> dioxus init hello-dioxus --template=gh:dioxuslabs/dioxus-template
> dx init hello-dioxus --template=gh:dioxuslabs/dioxus-template
> ```
Next, move the current directory into your new project:
@ -33,7 +33,7 @@ cd hello-dioxus
Finally, create serve your project with the Dioxus CLI:
```
dioxus serve
dx serve
```
By default, the CLI serve your site at: [`http://127.0.0.1:8080/`](http://127.0.0.1:8080/)

View File

@ -2,7 +2,7 @@
> For Cli 0.2.0 we will add `plugin-develop` support.
Before the 0.2.0 we use `dioxus tool` to use & install some plugin, but we think that is not good for extend cli program, some people want tailwind support, some people want sass support, we can't add all this thing in to the cli source code and we don't have time to maintain a lot of tools that user request, so maybe user make plugin by themself is a good choice.
Before the 0.2.0 we use `dx tool` to use & install some plugin, but we think that is not good for extend cli program, some people want tailwind support, some people want sass support, we can't add all this thing in to the cli source code and we don't have time to maintain a lot of tools that user request, so maybe user make plugin by themself is a good choice.
### Why Lua ?
@ -76,4 +76,4 @@ end
manager.serve.interval = 1000
return manager
```
```

View File

@ -4,7 +4,7 @@
name = "{{project-name}}"
# default platfrom
# you can also use `dioxus serve/build --platform XXX` to use other platform
# you can also use `dx serve/build --platform XXX` to use other platform
# value: web | desktop
default_platform = "{{default-platform}}"

View File

@ -161,7 +161,7 @@ pub fn build(config: &CrateConfig, quiet: bool) -> Result<BuildResult> {
}
} else {
log::warn!(
"Binaryen tool not found, you can use `dioxus tool add binaryen` to install it."
"Binaryen tool not found, you can use `dx tool add binaryen` to install it."
);
}
}
@ -200,7 +200,7 @@ pub fn build(config: &CrateConfig, quiet: bool) -> Result<BuildResult> {
}
} else {
log::warn!(
"Tailwind tool not found, you can use `dioxus tool add tailwindcss` to install it."
"Tailwind tool not found, you can use `dx tool add tailwindcss` to install it."
);
}
}

View File

@ -136,12 +136,16 @@ async fn autoformat_project(check: bool) -> Result<()> {
}
fn collect_rs_files(folder: &Path, files: &mut Vec<PathBuf>) {
let Ok(folder) = folder.read_dir() else { return };
let Ok(folder) = folder.read_dir() else {
return;
};
// load the gitignore
for entry in folder {
let Ok(entry) = entry else { continue; };
let Ok(entry) = entry else {
continue;
};
let path = entry.path();

View File

@ -31,7 +31,7 @@ impl Plugin {
}
}
Plugin::Add { name: _ } => {
log::info!("You can use `dioxus plugin app-path` to get Installation position");
log::info!("You can use `dx plugin app-path` to get Installation position");
}
}
Ok(())

View File

@ -88,8 +88,7 @@ impl<'b> VirtualDom {
}
// Intialize the root nodes slice
node.root_ids
.intialize(vec![ElementId(0); node.template.get().roots.len()].into_boxed_slice());
*node.root_ids.borrow_mut() = vec![ElementId(0); node.template.get().roots.len()];
// The best renderers will have templates prehydrated and registered
// Just in case, let's create the template using instructions anyways
@ -328,7 +327,7 @@ impl<'b> VirtualDom {
fn load_template_root(&mut self, template: &VNode, root_idx: usize) -> ElementId {
// Get an ID for this root since it's a real root
let this_id = self.next_root(template, root_idx);
template.root_ids.set(root_idx, this_id);
template.root_ids.borrow_mut()[root_idx] = this_id;
self.mutations.push(LoadTemplate {
name: template.template.get().name,

View File

@ -129,12 +129,14 @@ impl<'b> VirtualDom {
});
// Make sure the roots get transferred over while we're here
right_template.root_ids.transfer(&left_template.root_ids);
*right_template.root_ids.borrow_mut() = left_template.root_ids.borrow().clone();
let root_ids = right_template.root_ids.borrow();
// Update the node refs
for i in 0..right_template.root_ids.len() {
if let Some(root_id) = right_template.root_ids.get(i) {
self.update_template(root_id, right_template);
for i in 0..root_ids.len() {
if let Some(root_id) = root_ids.get(i) {
self.update_template(*root_id, right_template);
}
}
}
@ -686,7 +688,7 @@ impl<'b> VirtualDom {
Some(node) => node,
None => {
self.mutations.push(Mutation::PushRoot {
id: node.root_ids.get(idx).unwrap(),
id: node.root_ids.borrow()[idx],
});
return 1;
}
@ -821,7 +823,7 @@ impl<'b> VirtualDom {
if let Some(dy) = node.dynamic_root(idx) {
self.remove_dynamic_node(dy, gen_muts);
} else {
let id = node.root_ids.get(idx).unwrap();
let id = node.root_ids.borrow()[idx];
if gen_muts {
self.mutations.push(Mutation::Remove { id });
}
@ -928,7 +930,7 @@ impl<'b> VirtualDom {
fn find_first_element(&self, node: &'b VNode<'b>) -> ElementId {
match node.dynamic_root(0) {
None => node.root_ids.get(0).unwrap(),
None => node.root_ids.borrow()[0],
Some(Text(t)) => t.id.get().unwrap(),
Some(Fragment(t)) => self.find_first_element(&t[0]),
Some(Placeholder(t)) => t.id.get().unwrap(),
@ -944,7 +946,7 @@ impl<'b> VirtualDom {
fn find_last_element(&self, node: &'b VNode<'b>) -> ElementId {
match node.dynamic_root(node.template.get().roots.len() - 1) {
None => node.root_ids.last().unwrap(),
None => *node.root_ids.borrow().last().unwrap(),
Some(Text(t)) => t.id.get().unwrap(),
Some(Fragment(t)) => self.find_last_element(t.last().unwrap()),
Some(Placeholder(t)) => t.id.get().unwrap(),

View File

@ -5,7 +5,7 @@ use bumpalo::boxed::Box as BumpBox;
use bumpalo::Bump;
use std::{
any::{Any, TypeId},
cell::{Cell, RefCell, UnsafeCell},
cell::{Cell, RefCell},
fmt::{Arguments, Debug},
};
@ -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 actual Dom
pub root_ids: BoxedCellSlice,
pub root_ids: RefCell<Vec<ElementId>>,
/// The dynamic parts of the template
pub dynamic_nodes: &'a [DynamicNode<'a>],
@ -63,112 +63,13 @@ pub struct VNode<'a> {
pub dynamic_attrs: &'a [Attribute<'a>],
}
// Saftey: There is no way to get references to the internal data of this struct so no refrences will be invalidated by mutating the data with a immutable reference (The same principle behind Cell)
#[derive(Debug, Default)]
pub struct BoxedCellSlice(UnsafeCell<Option<Box<[ElementId]>>>);
impl Clone for BoxedCellSlice {
fn clone(&self) -> Self {
Self(UnsafeCell::new(unsafe { (*self.0.get()).clone() }))
}
}
impl BoxedCellSlice {
pub fn last(&self) -> Option<ElementId> {
unsafe {
(*self.0.get())
.as_ref()
.and_then(|inner| inner.as_ref().last().copied())
}
}
pub fn get(&self, idx: usize) -> Option<ElementId> {
unsafe {
(*self.0.get())
.as_ref()
.and_then(|inner| inner.as_ref().get(idx).copied())
}
}
pub unsafe fn get_unchecked(&self, idx: usize) -> Option<ElementId> {
(*self.0.get())
.as_ref()
.and_then(|inner| inner.as_ref().get(idx).copied())
}
pub fn set(&self, idx: usize, new: ElementId) {
unsafe {
if let Some(inner) = &mut *self.0.get() {
inner[idx] = new;
}
}
}
pub fn intialize(&self, contents: Box<[ElementId]>) {
unsafe {
*self.0.get() = Some(contents);
}
}
pub fn transfer(&self, other: &Self) {
unsafe {
*self.0.get() = (*other.0.get()).clone();
}
}
pub fn take_from(&self, other: &Self) {
unsafe {
*self.0.get() = (*other.0.get()).take();
}
}
pub fn len(&self) -> usize {
unsafe {
(*self.0.get())
.as_ref()
.map(|inner| inner.len())
.unwrap_or(0)
}
}
}
impl<'a> IntoIterator for &'a BoxedCellSlice {
type Item = ElementId;
type IntoIter = BoxedCellSliceIter<'a>;
fn into_iter(self) -> Self::IntoIter {
BoxedCellSliceIter {
index: 0,
borrow: self,
}
}
}
pub struct BoxedCellSliceIter<'a> {
index: usize,
borrow: &'a BoxedCellSlice,
}
impl Iterator for BoxedCellSliceIter<'_> {
type Item = ElementId;
fn next(&mut self) -> Option<Self::Item> {
let result = self.borrow.get(self.index);
if result.is_some() {
self.index += 1;
}
result
}
}
impl<'a> VNode<'a> {
/// Create a template with no nodes that will be skipped over during diffing
pub fn empty() -> Element<'a> {
Some(VNode {
key: None,
parent: None,
root_ids: BoxedCellSlice::default(),
root_ids: Default::default(),
dynamic_nodes: &[],
dynamic_attrs: &[],
template: Cell::new(Template {

View File

@ -87,7 +87,7 @@ impl VirtualDom {
if matches!(allocated, RenderReturn::Aborted(_)) {
self.suspended_scopes.insert(scope.id);
}
} else {
} else if !self.suspended_scopes.is_empty() {
_ = self.suspended_scopes.remove(&scope.id);
}

View File

@ -693,13 +693,13 @@ impl<'src> ScopeState {
raw_ref.downcast_mut::<State>()
})
.expect(
r###"
r#"
Unable to retrieve the hook that was initialized at this index.
Consult the `rules of hooks` to understand how to use hooks properly.
You likely used the hook in a conditional. Hooks rely on consistent ordering between renders.
Functions prefixed with "use" should never be called conditionally.
"###,
"#,
)
}
}

View File

@ -571,12 +571,15 @@ impl VirtualDom {
/// The mutations will be thrown out, so it's best to use this method for things like SSR that have async content
pub async fn wait_for_suspense(&mut self) {
loop {
// println!("waiting for suspense {:?}", self.suspended_scopes);
if self.suspended_scopes.is_empty() {
return;
}
// println!("waiting for suspense");
self.wait_for_work().await;
// println!("Rendered immediately");
_ = self.render_immediate();
}
}

View File

@ -1,9 +1,9 @@
//! Verify that tasks get polled by the virtualdom properly, and that we escape wait_for_work safely
use dioxus::prelude::*;
use std::time::Duration;
use std::{sync::atomic::AtomicUsize, time::Duration};
static mut POLL_COUNT: usize = 0;
static POLL_COUNT: AtomicUsize = AtomicUsize::new(0);
#[tokio::test]
async fn it_works() {
@ -18,7 +18,10 @@ async fn it_works() {
// By the time the tasks are finished, we should've accumulated ticks from two tasks
// Be warned that by setting the delay to too short, tokio might not schedule in the tasks
assert_eq!(unsafe { POLL_COUNT }, 135);
assert_eq!(
POLL_COUNT.fetch_add(0, std::sync::atomic::Ordering::Relaxed),
135
);
}
fn app(cx: Scope) -> Element {
@ -26,14 +29,14 @@ fn app(cx: Scope) -> Element {
cx.spawn(async {
for x in 0..10 {
tokio::time::sleep(Duration::from_micros(50)).await;
unsafe { POLL_COUNT += x }
POLL_COUNT.fetch_add(x, std::sync::atomic::Ordering::Relaxed);
}
});
cx.spawn(async {
for x in 0..10 {
tokio::time::sleep(Duration::from_micros(25)).await;
unsafe { POLL_COUNT += x * 2 }
POLL_COUNT.fetch_add(x * 2, std::sync::atomic::Ordering::Relaxed);
}
});
});

View File

@ -8,11 +8,48 @@ pub(crate) struct FileDialogRequest {
#[serde(default)]
accept: Option<String>,
multiple: bool,
directory: bool,
pub event: String,
pub target: usize,
pub bubbles: bool,
}
fn get_file_event_for_folder(request: &FileDialogRequest, dialog: rfd::FileDialog) -> Vec<PathBuf> {
if request.multiple {
dialog.pick_folders().into_iter().flatten().collect()
} else {
dialog.pick_folder().into_iter().collect()
}
}
fn get_file_event_for_file(
request: &FileDialogRequest,
mut dialog: rfd::FileDialog,
) -> Vec<PathBuf> {
let filters: Vec<_> = request
.accept
.as_deref()
.unwrap_or_default()
.split(',')
.filter_map(|s| Filters::from_str(s).ok())
.collect();
let file_extensions: Vec<_> = filters
.iter()
.flat_map(|f| f.as_extensions().into_iter())
.collect();
dialog = dialog.add_filter("name", file_extensions.as_slice());
let files: Vec<_> = if request.multiple {
dialog.pick_files().into_iter().flatten().collect()
} else {
dialog.pick_file().into_iter().collect()
};
files
}
#[cfg(not(any(
target_os = "windows",
target_os = "macos",
@ -36,30 +73,13 @@ pub(crate) fn get_file_event(_request: &FileDialogRequest) -> Vec<PathBuf> {
target_os = "openbsd"
))]
pub(crate) fn get_file_event(request: &FileDialogRequest) -> Vec<PathBuf> {
let mut dialog = rfd::FileDialog::new();
let dialog = rfd::FileDialog::new();
let filters: Vec<_> = request
.accept
.as_deref()
.unwrap_or_default()
.split(',')
.filter_map(|s| Filters::from_str(s).ok())
.collect();
let file_extensions: Vec<_> = filters
.iter()
.flat_map(|f| f.as_extensions().into_iter())
.collect();
dialog = dialog.add_filter("name", file_extensions.as_slice());
let files: Vec<_> = if request.multiple {
dialog.pick_files().into_iter().flatten().collect()
if request.directory {
get_file_event_for_folder(request, dialog)
} else {
dialog.pick_file().into_iter().collect()
};
files
get_file_event_for_file(request, dialog)
}
}
enum Filters {

View File

@ -24,7 +24,7 @@ fn module_loader(root_name: &str) -> String {
let target_id = find_real_id(target);
if (target_id !== null) {
const send = (event_name) => {
const message = serializeIpcMessage("file_diolog", { accept: target.getAttribute("accept"), multiple: target.hasAttribute("multiple"), target: parseInt(target_id), bubbles: event_bubbles(event_name), event: event_name });
const message = serializeIpcMessage("file_diolog", { accept: target.getAttribute("accept"), directory: target.getAttribute("webkitdirectory") === "true", multiple: target.hasAttribute("multiple"), target: parseInt(target_id), bubbles: event_bubbles(event_name), event: event_name });
window.ipc.postMessage(message);
};
send("change&input");

View File

@ -20,7 +20,7 @@ fn app(cx: Scope) -> Element {
background_color: "hsl({hue}, 70%, {brightness}%)",
onmousemove: move |evt| {
if let RenderReturn::Ready(node) = cx.root_node() {
if let Some(id) = node.root_ids.get(0){
if let Some(id) = node.root_ids.borrow().get(0).cloned() {
let node = tui_query.get(mapping.get_node_id(id).unwrap());
let Size{width, height} = node.size().unwrap();
let pos = evt.inner().element_coordinates();

View File

@ -1,7 +1,7 @@
//! Run with:
//!
//! ```sh
//! dioxus build --features web
//! dx build --features web
//! cargo run --features ssr
//! ```

View File

@ -1,7 +1,7 @@
//! Run with:
//!
//! ```sh
//! dioxus build --features web
//! dx build --features web
//! cargo run --features ssr
//! ```

View File

@ -1,7 +1,7 @@
//! Run with:
//!
//! ```sh
//! dioxus build --features web
//! dx build --features web
//! cargo run --features ssr
//! ```

View File

@ -1,7 +1,7 @@
//! Run with:
//!
//! ```sh
//! dioxus build --features web
//! dx build --features web
//! cargo run --features ssr
//! ```

View File

@ -1,7 +1,7 @@
//! Run with:
//!
//! ```sh
//! dioxus build --features web
//! dx build --features web
//! cargo run --features ssr
//! ```

View File

@ -1,7 +1,7 @@
//! Run with:
//!
//! ```sh
//! dioxus build --features web
//! dx build --features web
//! cargo run --features ssr
//! ```

View File

@ -1098,6 +1098,7 @@ builder_constructors! {
autofocus: Bool DEFAULT,
capture: String DEFAULT,
checked: Bool DEFAULT,
directory: Bool "webkitdirectory",
disabled: Bool DEFAULT,
form: Id DEFAULT,
formaction: Uri DEFAULT,

View File

@ -56,10 +56,9 @@ where
{
use serde::Deserialize;
let Ok(file_engine) =
SerializedFileEngine::deserialize(deserializer) else{
return Ok(None);
};
let Ok(file_engine) = SerializedFileEngine::deserialize(deserializer) else {
return Ok(None);
};
let file_engine = std::sync::Arc::new(file_engine);
Ok(Some(file_engine))

View File

@ -25,6 +25,7 @@ const bool_attrs = {
reversed: true,
selected: true,
truespeed: true,
webkitdirectory: true,
};
export function setAttributeInner(node, field, value, ns) {

View File

@ -163,6 +163,7 @@ mod js {
reversed: true,
selected: true,
truespeed: true,
webkitdirectory: true,
};
function truthy(val) {
return val === "true" || val === true;

View File

@ -11,13 +11,14 @@ pub fn axum_socket(ws: WebSocket) -> impl LiveViewSocket {
.sink_map_err(|_| LiveViewError::SendingFailed)
}
fn transform_rx(message: Result<Message, axum::Error>) -> Result<String, LiveViewError> {
fn transform_rx(message: Result<Message, axum::Error>) -> Result<Vec<u8>, LiveViewError> {
message
.map_err(|_| LiveViewError::SendingFailed)?
.into_text()
.map(|s| s.into_bytes())
.map_err(|_| LiveViewError::SendingFailed)
}
async fn transform_tx(message: String) -> Result<Message, axum::Error> {
Ok(Message::Text(message))
async fn transform_tx(message: Vec<u8>) -> Result<Message, axum::Error> {
Ok(Message::Text(String::from_utf8_lossy(&message).to_string()))
}

View File

@ -12,14 +12,12 @@ pub fn salvo_socket(ws: WebSocket) -> impl LiveViewSocket {
.sink_map_err(|_| LiveViewError::SendingFailed)
}
fn transform_rx(message: Result<Message, salvo::Error>) -> Result<String, LiveViewError> {
fn transform_rx(message: Result<Message, salvo::Error>) -> Result<Vec<u8>, LiveViewError> {
let as_bytes = message.map_err(|_| LiveViewError::SendingFailed)?;
let msg = String::from_utf8(as_bytes.into_bytes()).map_err(|_| LiveViewError::SendingFailed)?;
Ok(msg)
Ok(as_bytes.into())
}
async fn transform_tx(message: String) -> Result<Message, salvo::Error> {
Ok(Message::text(message))
async fn transform_tx(message: Vec<u8>) -> Result<Message, salvo::Error> {
Ok(Message::text(String::from_utf8_lossy(&message).to_string()))
}

View File

@ -11,18 +11,15 @@ pub fn warp_socket(ws: WebSocket) -> impl LiveViewSocket {
.sink_map_err(|_| LiveViewError::SendingFailed)
}
fn transform_rx(message: Result<Message, warp::Error>) -> Result<String, LiveViewError> {
fn transform_rx(message: Result<Message, warp::Error>) -> Result<Vec<u8>, LiveViewError> {
// destructure the message into the buffer we got from warp
let msg = message
.map_err(|_| LiveViewError::SendingFailed)?
.into_bytes();
// transform it back into a string, saving us the allocation
let msg = String::from_utf8(msg).map_err(|_| LiveViewError::SendingFailed)?;
Ok(msg)
}
async fn transform_tx(message: String) -> Result<Message, warp::Error> {
Ok(Message::text(message))
async fn transform_tx(message: Vec<u8>) -> Result<Message, warp::Error> {
Ok(Message::text(String::from_utf8_lossy(&message).to_string()))
}

View File

@ -87,16 +87,16 @@ impl LiveViewPool {
/// }
/// ```
pub trait LiveViewSocket:
SinkExt<String, Error = LiveViewError>
+ StreamExt<Item = Result<String, LiveViewError>>
SinkExt<Vec<u8>, Error = LiveViewError>
+ StreamExt<Item = Result<Vec<u8>, LiveViewError>>
+ Send
+ 'static
{
}
impl<S> LiveViewSocket for S where
S: SinkExt<String, Error = LiveViewError>
+ StreamExt<Item = Result<String, LiveViewError>>
S: SinkExt<Vec<u8>, Error = LiveViewError>
+ StreamExt<Item = Result<Vec<u8>, LiveViewError>>
+ Send
+ 'static
{
@ -126,7 +126,7 @@ pub async fn run(mut vdom: VirtualDom, ws: impl LiveViewSocket) -> Result<(), Li
pin_mut!(ws);
// send the initial render to the client
ws.send(edits).await?;
ws.send(edits.into_bytes()).await?;
// Create the a proxy for query engine
let (query_tx, mut query_rx) = tokio::sync::mpsc::unbounded_channel();
@ -156,11 +156,11 @@ pub async fn run(mut vdom: VirtualDom, ws: impl LiveViewSocket) -> Result<(), Li
evt = ws.next() => {
match evt.as_ref().map(|o| o.as_deref()) {
// respond with a pong every ping to keep the websocket alive
Some(Ok("__ping__")) => {
ws.send("__pong__".to_string()).await?;
Some(Ok(b"__ping__")) => {
ws.send(b"__pong__".to_vec()).await?;
}
Some(Ok(evt)) => {
if let Ok(message) = serde_json::from_str::<IpcMessage>(evt) {
if let Ok(message) = serde_json::from_str::<IpcMessage>(&String::from_utf8_lossy(evt)) {
match message {
IpcMessage::Event(evt) => {
// Intercept the mounted event and insert a custom element type
@ -196,7 +196,7 @@ pub async fn run(mut vdom: VirtualDom, ws: impl LiveViewSocket) -> Result<(), Li
// handle any new queries
Some(query) = query_rx.recv() => {
ws.send(serde_json::to_string(&ClientUpdate::Query(query)).unwrap()).await?;
ws.send(serde_json::to_string(&ClientUpdate::Query(query)).unwrap().into_bytes()).await?;
}
Some(msg) = hot_reload_wait => {
@ -218,8 +218,12 @@ pub async fn run(mut vdom: VirtualDom, ws: impl LiveViewSocket) -> Result<(), Li
.render_with_deadline(tokio::time::sleep(Duration::from_millis(10)))
.await;
ws.send(serde_json::to_string(&ClientUpdate::Edits(edits)).unwrap())
.await?;
ws.send(
serde_json::to_string(&ClientUpdate::Edits(edits))
.unwrap()
.into_bytes(),
)
.await?;
}
}

View File

@ -82,7 +82,9 @@ impl Button {
fn write_value(&self, rdom: &mut RealDom) {
if let Some(mut text) = rdom.get_mut(self.text_id) {
let node_type = text.node_type_mut();
let NodeTypeMut::Text(mut text) = node_type else { panic!("input must be an element") };
let NodeTypeMut::Text(mut text) = node_type else {
panic!("input must be an element")
};
*text.text_mut() = self.value.clone();
}
}
@ -111,7 +113,9 @@ impl CustomElement for Button {
fn create(mut root: dioxus_native_core::real_dom::NodeMut) -> Self {
let node_type = root.node_type();
let NodeType::Element(el) = &*node_type else { panic!("input must be an element") };
let NodeType::Element(el) = &*node_type else {
panic!("input must be an element")
};
let value = el
.attributes
@ -146,7 +150,9 @@ impl CustomElement for Button {
AttributeMask::All => {
{
let node_type = root.node_type_mut();
let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") };
let NodeTypeMut::Element(mut el) = node_type else {
panic!("input must be an element")
};
self.update_value_attr(&el);
self.update_size_attr(&mut el);
}
@ -155,7 +161,9 @@ impl CustomElement for Button {
AttributeMask::Some(attrs) => {
{
let node_type = root.node_type_mut();
let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") };
let NodeTypeMut::Element(mut el) = node_type else {
panic!("input must be an element")
};
if attrs.contains("width") || attrs.contains("height") {
self.update_size_attr(&mut el);
}

View File

@ -94,14 +94,18 @@ impl CheckBox {
fn write_value(&self, mut root: NodeMut) {
let single_char = {
let node_type = root.node_type_mut();
let NodeTypeMut::Element( el) = node_type else { panic!("input must be an element") };
let NodeTypeMut::Element(el) = node_type else {
panic!("input must be an element")
};
Self::width(&el) == "1px" || Self::height(&el) == "1px"
};
let rdom = root.real_dom_mut();
if let Some(mut text) = rdom.get_mut(self.text_id) {
let node_type = text.node_type_mut();
let NodeTypeMut::Text(mut text) = node_type else { panic!("input must be an element") };
let NodeTypeMut::Text(mut text) = node_type else {
panic!("input must be an element")
};
let value = if single_char {
if self.checked {
""
@ -156,7 +160,9 @@ impl CustomElement for CheckBox {
fn create(mut root: dioxus_native_core::real_dom::NodeMut) -> Self {
let node_type = root.node_type();
let NodeType::Element(el) = &*node_type else { panic!("input must be an element") };
let NodeType::Element(el) = &*node_type else {
panic!("input must be an element")
};
let value = el
.attributes
@ -197,7 +203,9 @@ impl CustomElement for CheckBox {
AttributeMask::All => {
{
let node_type = root.node_type_mut();
let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") };
let NodeTypeMut::Element(mut el) = node_type else {
panic!("input must be an element")
};
self.update_value_attr(&el);
self.update_size_attr(&mut el);
self.update_checked_attr(&el);
@ -207,7 +215,9 @@ impl CustomElement for CheckBox {
AttributeMask::Some(attrs) => {
{
let node_type = root.node_type_mut();
let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") };
let NodeTypeMut::Element(mut el) = node_type else {
panic!("input must be an element")
};
if attrs.contains("width") || attrs.contains("height") {
self.update_size_attr(&mut el);
}

View File

@ -56,7 +56,9 @@ impl CustomElement for Input {
}
let node_type = root.node_type();
let NodeType::Element(el) = &*node_type else { panic!("input must be an element") };
let NodeType::Element(el) = &*node_type else {
panic!("input must be an element")
};
let input_type = el
.attributes
.get(&OwnedAttributeDiscription {

View File

@ -163,7 +163,9 @@ impl Slider {
if let Some(mut div) = rdom.get_mut(self.pre_cursor_div) {
let node_type = div.node_type_mut();
let NodeTypeMut::Element(mut element) = node_type else { panic!("input must be an element") };
let NodeTypeMut::Element(mut element) = node_type else {
panic!("input must be an element")
};
element.set_attribute(
OwnedAttributeDiscription {
name: "width".to_string(),
@ -175,7 +177,9 @@ impl Slider {
if let Some(mut div) = rdom.get_mut(self.post_cursor_div) {
let node_type = div.node_type_mut();
let NodeTypeMut::Element(mut element) = node_type else { panic!("input must be an element") };
let NodeTypeMut::Element(mut element) = node_type else {
panic!("input must be an element")
};
element.set_attribute(
OwnedAttributeDiscription {
name: "width".to_string(),
@ -259,7 +263,9 @@ impl CustomElement for Slider {
fn create(mut root: dioxus_native_core::real_dom::NodeMut) -> Self {
let node_type = root.node_type();
let NodeType::Element(el) = &*node_type else { panic!("input must be an element") };
let NodeType::Element(el) = &*node_type else {
panic!("input must be an element")
};
let value = el.attributes.get(&OwnedAttributeDiscription {
name: "value".to_string(),
@ -390,7 +396,9 @@ impl CustomElement for Slider {
AttributeMask::All => {
{
let node_type = root.node_type_mut();
let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") };
let NodeTypeMut::Element(mut el) = node_type else {
panic!("input must be an element")
};
self.update_value_attr(&el);
self.update_size_attr(&mut el);
self.update_max_attr(&el);
@ -403,7 +411,9 @@ impl CustomElement for Slider {
AttributeMask::Some(attrs) => {
{
let node_type = root.node_type_mut();
let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") };
let NodeTypeMut::Element(mut el) = node_type else {
panic!("input must be an element")
};
if attrs.contains("width") || attrs.contains("height") {
self.update_size_attr(&mut el);
}

View File

@ -143,19 +143,25 @@ impl<C: TextLikeController> TextLike<C> {
if let Some(mut text) = rdom.get_mut(self.pre_cursor_text) {
let node_type = text.node_type_mut();
let NodeTypeMut::Text(mut text) = node_type else { panic!("input must be an element") };
let NodeTypeMut::Text(mut text) = node_type else {
panic!("input must be an element")
};
*text.text_mut() = self.controller.display_text(text_before_first_cursor);
}
if let Some(mut text) = rdom.get_mut(self.highlighted_text) {
let node_type = text.node_type_mut();
let NodeTypeMut::Text(mut text) = node_type else { panic!("input must be an element") };
let NodeTypeMut::Text(mut text) = node_type else {
panic!("input must be an element")
};
*text.text_mut() = self.controller.display_text(text_highlighted);
}
if let Some(mut text) = rdom.get_mut(self.post_cursor_text) {
let node_type = text.node_type_mut();
let NodeTypeMut::Text(mut text) = node_type else { panic!("input must be an element") };
let NodeTypeMut::Text(mut text) = node_type else {
panic!("input must be an element")
};
*text.text_mut() = self.controller.display_text(text_after_second_cursor);
}
@ -288,7 +294,9 @@ impl<C: TextLikeController + Send + Sync + Default + 'static> CustomElement for
fn create(mut root: dioxus_native_core::real_dom::NodeMut) -> Self {
let node_type = root.node_type();
let NodeType::Element(el) = &*node_type else { panic!("input must be an element") };
let NodeType::Element(el) = &*node_type else {
panic!("input must be an element")
};
let value = el
.attributes
@ -370,7 +378,9 @@ impl<C: TextLikeController + Send + Sync + Default + 'static> CustomElement for
AttributeMask::All => {
{
let node_type = root.node_type_mut();
let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") };
let NodeTypeMut::Element(mut el) = node_type else {
panic!("input must be an element")
};
self.update_value_attr(&el);
self.update_size_attr(&mut el);
self.update_max_width_attr(&el);
@ -381,7 +391,9 @@ impl<C: TextLikeController + Send + Sync + Default + 'static> CustomElement for
AttributeMask::Some(attrs) => {
{
let node_type = root.node_type_mut();
let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") };
let NodeTypeMut::Element(mut el) = node_type else {
panic!("input must be an element")
};
if attrs.contains("width") || attrs.contains("height") {
self.update_size_attr(&mut el);
}

View File

@ -31,11 +31,11 @@ mod segment;
/// Route Segments:
/// 1. Static Segments: "/static"
/// 2. Dynamic Segments: "/:dynamic" (where dynamic has a type that is FromStr in all child Variants)
/// 3. Catch all Segments: "/:...segments" (where segments has a type that is FromSegments in all child Variants)
/// 3. Catch all Segments: "/:..segments" (where segments has a type that is FromSegments in all child Variants)
/// 4. Query Segments: "/?:query" (where query has a type that is FromQuery in all child Variants)
///
/// Routes are matched:
/// 1. By there specificity this order: Query Routes ("/?:query"), Static Routes ("/route"), Dynamic Routes ("/:route"), Catch All Routes ("/:...route")
/// 1. By there specificity this order: Query Routes ("/?:query"), Static Routes ("/route"), Dynamic Routes ("/:route"), Catch All Routes ("/:..route")
/// 2. By the order they are defined in the enum
///
/// All features:
@ -369,13 +369,13 @@ impl RouteEnum {
let (exclude, layout): (bool, Layout) = attr.parse_args_with(parser)?;
if exclude {
let Some(layout_index) =
layouts.iter().position(|l| l.comp == layout.comp) else {
return Err(syn::Error::new(
Span::call_site(),
"Attempted to exclude a layout that does not exist",
));
};
let Some(layout_index) = layouts.iter().position(|l| l.comp == layout.comp)
else {
return Err(syn::Error::new(
Span::call_site(),
"Attempted to exclude a layout that does not exist",
));
};
excluded.push(LayoutId(layout_index));
} else {
let layout_index = layouts.len();

View File

@ -126,7 +126,7 @@ impl Route {
None => {
return Err(syn::Error::new_spanned(
variant.clone(),
"Routable variants with a #[child(...)] attribute must have a field named \"child\" or a field with a #[child] attribute",
"Routable variants with a #[child(..)] attribute must have a field named \"child\" or a field with a #[child] attribute",
));
}
}
@ -134,14 +134,14 @@ impl Route {
_ => {
return Err(syn::Error::new_spanned(
variant.clone(),
"Routable variants with a #[child(...)] attribute must have named fields",
"Routable variants with a #[child(..)] attribute must have named fields",
))
}
}
} else {
return Err(syn::Error::new_spanned(
variant.clone(),
"Routable variants must either have a #[route(...)] attribute or a #[child(...)] attribute",
"Routable variants must either have a #[route(..)] attribute or a #[child(..)] attribute",
));
}
}

View File

@ -150,10 +150,10 @@ pub fn parse_route_segments<'a>(
while let Some(segment) = iterator.next() {
if let Some(segment) = segment.strip_prefix(':') {
let spread = segment.starts_with("...");
let spread = segment.starts_with("..");
let ident = if spread {
segment[3..].to_string()
segment[2..].to_string()
} else {
segment.to_string()
};

View File

@ -10,8 +10,6 @@ mod rt;
use dioxus_core::ScopeState;
pub use rt::*;
use crate::rt::claim_rt;
pub fn use_init_signal_rt(cx: &ScopeState) {
cx.use_hook(|| {
let rt = claim_rt(cx.schedule_update_any());
@ -96,11 +94,7 @@ impl<T: Clone + 'static> std::ops::Deref for Signal<T> {
impl<T> std::clone::Clone for Signal<T> {
fn clone(&self) -> Self {
Self {
t: PhantomData,
id: self.id,
rt: self.rt,
}
*self
}
}

View File

@ -90,7 +90,7 @@ impl WebsysDom {
// make sure we set the root node ids even if the node is not dynamic
set_node(
hydrated,
vnode.root_ids.get(i).ok_or(VNodeNotInitialized)?,
*vnode.root_ids.borrow().get(i).ok_or(VNodeNotInitialized)?,
current_child.clone()?,
);

View File

@ -20,7 +20,7 @@ fn main() {
{
// Start hot reloading
hot_reload_init!(dioxus_hot_reload::Config::new().with_rebuild_callback(|| {
execute::shell("dioxus build --features web")
execute::shell("dx build --features web")
.spawn()
.unwrap()
.wait()

View File

@ -9,23 +9,23 @@
"version": "1.0.0",
"license": "ISC",
"devDependencies": {
"@playwright/test": "^1.34.3"
"@playwright/test": "^1.36.1"
}
},
"node_modules/@playwright/test": {
"version": "1.34.3",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.34.3.tgz",
"integrity": "sha512-zPLef6w9P6T/iT6XDYG3mvGOqOyb6eHaV9XtkunYs0+OzxBtrPAAaHotc0X+PJ00WPPnLfFBTl7mf45Mn8DBmw==",
"version": "1.36.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.36.1.tgz",
"integrity": "sha512-YK7yGWK0N3C2QInPU6iaf/L3N95dlGdbsezLya4n0ZCh3IL7VgPGxC6Gnznh9ApWdOmkJeleT2kMTcWPRZvzqg==",
"dev": true,
"dependencies": {
"@types/node": "*",
"playwright-core": "1.34.3"
"playwright-core": "1.36.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=14"
"node": ">=16"
},
"optionalDependencies": {
"fsevents": "2.3.2"
@ -52,15 +52,15 @@
}
},
"node_modules/playwright-core": {
"version": "1.34.3",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.34.3.tgz",
"integrity": "sha512-2pWd6G7OHKemc5x1r1rp8aQcpvDh7goMBZlJv6Co5vCNLVcQJdhxRL09SGaY6HcyHH9aT4tiynZabMofVasBYw==",
"version": "1.36.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.36.1.tgz",
"integrity": "sha512-7+tmPuMcEW4xeCL9cp9KxmYpQYHKkyjwoXRnoeTowaeNat8PoBMk/HwCYhqkH2fRkshfKEOiVus/IhID2Pg8kg==",
"dev": true,
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=14"
"node": ">=16"
}
}
}

View File

@ -12,6 +12,6 @@
"author": "",
"license": "ISC",
"devDependencies": {
"@playwright/test": "^1.34.3"
"@playwright/test": "^1.36.1"
}
}

File diff suppressed because one or more lines are too long

View File

@ -12,7 +12,7 @@ const path = require("path");
* @see https://playwright.dev/docs/test-configuration
*/
module.exports = defineConfig({
testDir: "./playwright-tests",
testDir: ".",
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
@ -81,16 +81,16 @@ module.exports = defineConfig({
stdout: "pipe",
},
{
cwd: path.join(process.cwd(), "playwright-tests", "web"),
command: "dioxus serve",
cwd: path.join(process.cwd(), "web"),
command: "cargo run --package dioxus-cli -- serve",
port: 8080,
timeout: 10 * 60 * 1000,
reuseExistingServer: !process.env.CI,
stdout: "pipe",
},
{
cwd: path.join(process.cwd(), 'playwright-tests', 'fullstack'),
command: 'dioxus build --features web --release\ncargo run --release --features ssr --no-default-features',
cwd: path.join(process.cwd(), 'fullstack'),
command: 'cargo run --package dioxus-cli -- build --features web --release\ncargo run --release --features ssr',
port: 3333,
timeout: 10 * 60 * 1000,
reuseExistingServer: !process.env.CI,