Bläddra i källkod

Merge branch 'DioxusLabs:master' into fix-text-node-target-events

ealmloff 1 år sedan
förälder
incheckning
d16a706a86
100 ändrade filer med 1983 tillägg och 976 borttagningar
  1. 54 0
      .github/workflows/cli_release.yml
  2. 22 24
      .github/workflows/main.yml
  3. 34 37
      .github/workflows/playwright.yml
  4. 5 2
      Cargo.toml
  5. 3 1
      README.md
  6. 2 1
      docs/guide/examples/readme_expanded.rs
  7. 1 1
      docs/guide/src/en/getting_started/web.md
  8. 2 0
      docs/guide/src/en/interactivity/event_handlers.md
  9. 2 0
      docs/guide/src/pt-br/interactivity/event_handlers.md
  10. 6 5
      examples/PWA-example/README.md
  11. 1 7
      examples/clock.rs
  12. 0 1
      examples/login_form.rs
  13. 1 1
      examples/mobile_demo/src/lib.rs
  14. 12 0
      examples/query_segments_demo/Cargo.toml
  15. 73 0
      examples/query_segments_demo/src/main.rs
  16. 1 1
      examples/signals.rs
  17. 1 1
      examples/tailwind/Dioxus.toml
  18. 1 1
      examples/tailwind/README.md
  19. 4 2
      notes/README/ZH_CN.md
  20. 9 1
      packages/autofmt/src/collect_macros.rs
  21. 9 25
      packages/cli/.github/workflows/main.yml
  22. 10 5
      packages/cli/Cargo.toml
  23. 21 18
      packages/cli/Dioxus.toml
  24. 44 16
      packages/cli/README.md
  25. 1 1
      packages/cli/docs/book.toml
  26. 9 14
      packages/cli/docs/src/SUMMARY.md
  27. 0 30
      packages/cli/docs/src/cmd/README.md
  28. 0 56
      packages/cli/docs/src/cmd/build.md
  29. 0 27
      packages/cli/docs/src/cmd/clean.md
  30. 0 70
      packages/cli/docs/src/cmd/serve.md
  31. 0 68
      packages/cli/docs/src/cmd/translate.md
  32. 85 96
      packages/cli/docs/src/configure.md
  33. 9 11
      packages/cli/docs/src/creating.md
  34. 9 8
      packages/cli/docs/src/installation.md
  35. 12 15
      packages/cli/docs/src/introduction.md
  36. 74 13
      packages/cli/docs/src/plugin/README.md
  37. 4 4
      packages/cli/docs/src/plugin/interface/command.md
  38. 7 12
      packages/cli/docs/src/plugin/interface/dirs.md
  39. 11 11
      packages/cli/docs/src/plugin/interface/log.md
  40. 9 6
      packages/cli/docs/src/plugin/interface/network.md
  41. 3 3
      packages/cli/docs/src/plugin/interface/os.md
  42. 14 11
      packages/cli/docs/src/plugin/interface/path.md
  43. 13 11
      packages/cli/src/builder.rs
  44. 1 1
      packages/cli/src/cli/build.rs
  45. 3 0
      packages/cli/src/config.rs
  46. 1 1
      packages/cli/src/lib.rs
  47. 64 63
      packages/cli/src/server/web/mod.rs
  48. 1 0
      packages/core/Cargo.toml
  49. 32 0
      packages/core/compile_tests/props_safety.rs
  50. 20 0
      packages/core/compile_tests/props_safety.stderr
  51. 24 0
      packages/core/compile_tests/props_safety_temporary_values.rs
  52. 12 0
      packages/core/compile_tests/props_safety_temporary_values.stderr
  53. 13 4
      packages/core/src/arena.rs
  54. 11 7
      packages/core/src/create.rs
  55. 35 10
      packages/core/src/diff.rs
  56. 9 0
      packages/core/src/events.rs
  57. 14 7
      packages/core/src/lib.rs
  58. 9 9
      packages/core/src/nodes.rs
  59. 8 0
      packages/core/src/properties.rs
  60. 108 0
      packages/core/src/runtime.rs
  61. 13 4
      packages/core/src/scheduler/wait.rs
  62. 33 26
      packages/core/src/scope_arena.rs
  63. 335 0
      packages/core/src/scope_context.rs
  64. 61 103
      packages/core/src/scopes.rs
  65. 43 25
      packages/core/src/virtual_dom.rs
  66. 4 4
      packages/core/tests/attr_cleanup.rs
  67. 1 1
      packages/core/tests/bubble_error.rs
  68. 3 3
      packages/core/tests/context_api.rs
  69. 3 3
      packages/core/tests/cycle.rs
  70. 3 3
      packages/core/tests/diff_component.rs
  71. 7 7
      packages/core/tests/diff_element.rs
  72. 12 12
      packages/core/tests/diff_keyed_list.rs
  73. 18 18
      packages/core/tests/diff_unkeyed_list.rs
  74. 2 2
      packages/core/tests/fuzzing.rs
  75. 8 8
      packages/core/tests/lifecycle.rs
  76. 6 6
      packages/core/tests/miri_simple.rs
  77. 8 8
      packages/core/tests/miri_stress.rs
  78. 2 1
      packages/desktop/Cargo.toml
  79. 7 2
      packages/desktop/src/protocol.rs
  80. 7 1
      packages/dioxus-tui/examples/hover.rs
  81. 1 1
      packages/fullstack/src/adapters/mod.rs
  82. 6 5
      packages/fullstack/src/launch.rs
  83. 17 0
      packages/generational-box/Cargo.toml
  84. 34 0
      packages/generational-box/README.md
  85. 359 0
      packages/generational-box/src/lib.rs
  86. 2 0
      packages/hooks/src/computed.rs
  87. 1 2
      packages/hooks/src/lib.rs
  88. 1 1
      packages/hooks/src/use_on_unmount.rs
  89. 14 0
      packages/hooks/src/use_shared_state.rs
  90. 1 1
      packages/html/Cargo.toml
  91. 2 0
      packages/html/src/elements.rs
  92. 1 1
      packages/html/src/eval.rs
  93. 10 1
      packages/html/src/events/form.rs
  94. 6 0
      packages/html/src/native_bind/native_file_engine.rs
  95. 1 1
      packages/native-core/Cargo.toml
  96. 0 1
      packages/native-core/src/real_dom.rs
  97. 2 2
      packages/native-core/src/utils/persistant_iterator.rs
  98. 3 2
      packages/native-core/tests/fuzzing.rs
  99. 2 1
      packages/router-macro/src/lib.rs
  100. 1 1
      packages/router-macro/src/query.rs

+ 54 - 0
.github/workflows/cli_release.yml

@@ -0,0 +1,54 @@
+name: Build CLI for Release
+
+# Will run automatically on every new release
+on:
+  release:
+    types: [published]
+
+jobs:
+  build-and-upload:
+    permissions:
+      contents: write
+    runs-on: ${{ matrix.platform.os }}
+    strategy:
+      matrix:
+        platform:
+          - {
+              target: x86_64-pc-windows-msvc,
+              os: windows-latest,
+              toolchain: "1.70.0",
+            }
+          - {
+              target: x86_64-apple-darwin,
+              os: macos-latest,
+              toolchain: "1.70.0",
+            }
+          - {
+              target: x86_64-unknown-linux-gnu,
+              os: ubuntu-latest,
+              toolchain: "1.70.0",
+            }
+    steps:
+      - uses: actions/checkout@v3
+      - name: Install stable
+        uses: dtolnay/rust-toolchain@master
+        with:
+          toolchain: ${{ matrix.platform.toolchain }}
+          targets: ${{ matrix.platform.target }}
+
+      # Setup the Github Actions Cache for the CLI package
+      - name: Setup cache
+        uses: Swatinem/rust-cache@v2
+        with:
+          workspaces: packages/cli -> ../../target
+
+      # This neat action can build and upload the binary in one go!
+      - name: Build and upload binary
+        uses: taiki-e/upload-rust-binary-action@v1
+        with:
+          token: ${{ secrets.GITHUB_TOKEN }}
+          target: ${{ matrix.platform.target }}
+          bin: dx
+          archive: dx-${{ matrix.platform.target }}
+          checksum: sha256
+          manifest_path: packages/cli/Cargo.toml

+ 22 - 24
.github/workflows/main.yml

@@ -85,52 +85,56 @@ jobs:
 
 
   matrix_test:
   matrix_test:
     runs-on: ${{ matrix.platform.os }}
     runs-on: ${{ matrix.platform.os }}
+    env:
+      RUST_CARGO_COMMAND: ${{ matrix.platform.cross == true && 'cross' || 'cargo' }}
     strategy:
     strategy:
       matrix:
       matrix:
         platform:
         platform:
           - {
           - {
               target: x86_64-pc-windows-msvc,
               target: x86_64-pc-windows-msvc,
               os: windows-latest,
               os: windows-latest,
-              toolchain: '1.70.0',
+              toolchain: "1.70.0",
               cross: false,
               cross: false,
-              command: 'test',
-              args: '--all --tests'
+              command: "test",
+              args: "--all --tests",
             }
             }
           - {
           - {
               target: x86_64-apple-darwin,
               target: x86_64-apple-darwin,
               os: macos-latest,
               os: macos-latest,
-              toolchain: '1.70.0',
+              toolchain: "1.70.0",
               cross: false,
               cross: false,
-              command: 'test',
-              args: '--all --tests'
+              command: "test",
+              args: "--all --tests",
             }
             }
           - {
           - {
               target: aarch64-apple-ios,
               target: aarch64-apple-ios,
               os: macos-latest,
               os: macos-latest,
-              toolchain: '1.70.0',
+              toolchain: "1.70.0",
               cross: false,
               cross: false,
-              command: 'build',
-              args: '--package dioxus-mobile'
+              command: "build",
+              args: "--package dioxus-mobile",
             }
             }
           - {
           - {
               target: aarch64-linux-android,
               target: aarch64-linux-android,
               os: ubuntu-latest,
               os: ubuntu-latest,
-              toolchain: '1.70.0',
+              toolchain: "1.70.0",
               cross: true,
               cross: true,
-              command: 'build',
-              args: '--package dioxus-mobile'
+              command: "build",
+              args: "--package dioxus-mobile",
             }
             }
 
 
     steps:
     steps:
       - uses: actions/checkout@v3
       - uses: actions/checkout@v3
 
 
       - name: install stable
       - name: install stable
-        uses: actions-rs/toolchain@v1
+        uses: dtolnay/rust-toolchain@master
         with:
         with:
           toolchain: ${{ matrix.platform.toolchain }}
           toolchain: ${{ matrix.platform.toolchain }}
-          target: ${{ matrix.platform.target }}
-          override: true
-          default: true
+          targets: ${{ matrix.platform.target }}
+
+      - name: Install cross
+        if: ${{ matrix.platform.cross == true }}
+        uses: taiki-e/install-action@cross
 
 
       - uses: Swatinem/rust-cache@v2
       - uses: Swatinem/rust-cache@v2
         with:
         with:
@@ -138,13 +142,8 @@ jobs:
           save-if: ${{ matrix.features.key == 'all' }}
           save-if: ${{ matrix.features.key == 'all' }}
 
 
       - name: test
       - name: test
-        uses: actions-rs/cargo@v1
-        with:
-          use-cross: ${{ matrix.platform.cross }}
-          command: ${{ matrix.platform.command }}
-          args: --target ${{ matrix.platform.target }} ${{ matrix.platform.args }}
-
-
+        run: |
+          ${{ env.RUST_CARGO_COMMAND }} ${{ matrix.platform.command }} ${{ matrix.platform.args }} --target ${{ matrix.platform.target }}
 
 
   # Coverage is disabled until we can fix it
   # Coverage is disabled until we can fix it
   # coverage:
   # coverage:
@@ -166,4 +165,3 @@ jobs:
   #       uses: codecov/codecov-action@v2
   #       uses: codecov/codecov-action@v2
   #       with:
   #       with:
   #         fail_ci_if_error: false
   #         fail_ci_if_error: false
-

+ 34 - 37
.github/workflows/playwright.yml

@@ -1,9 +1,9 @@
 name: Playwright Tests
 name: Playwright Tests
 on:
 on:
   push:
   push:
-    branches: [ main, master ]
+    branches: [main, master]
   pull_request:
   pull_request:
-    branches: [ main, master ]
+    branches: [main, master]
 defaults:
 defaults:
   run:
   run:
     working-directory: ./playwright-tests
     working-directory: ./playwright-tests
@@ -16,39 +16,36 @@ jobs:
   test:
   test:
     if: github.event.pull_request.draft == false
     if: github.event.pull_request.draft == false
     timeout-minutes: 60
     timeout-minutes: 60
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-latest
     steps:
     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 Rust
-      uses: actions-rs/toolchain@v1
-      with:
-        profile: minimal
-        toolchain: stable
-        override: true
-    - uses: Swatinem/rust-cache@v2
-    - name: Install WASM toolchain
-      run: rustup target add wasm32-unknown-unknown
-    - 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
-      if: always()
-      with:
-        name: playwright-report
-        path: playwright-report/
-        retention-days: 30
+      # 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 Rust
+        uses: dtolnay/rust-toolchain@master
+        with:
+          toolchain: stable
+          targets: x86_64-unknown-linux-gnu,wasm32-unknown-unknown
+      - uses: Swatinem/rust-cache@v2
+      - 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
+        if: always()
+        with:
+          name: playwright-report
+          path: playwright-report/
+          retention-days: 30

+ 5 - 2
Cargo.toml

@@ -25,6 +25,7 @@ members = [
     "packages/native-core",
     "packages/native-core",
     "packages/native-core-macro",
     "packages/native-core-macro",
     "packages/rsx-rosetta",
     "packages/rsx-rosetta",
+    "packages/generational-box",
     "packages/signals",
     "packages/signals",
     "packages/hot-reload",
     "packages/hot-reload",
     "packages/fullstack",
     "packages/fullstack",
@@ -41,6 +42,7 @@ members = [
     # Full project examples
     # Full project examples
     "examples/tailwind",
     "examples/tailwind",
     "examples/PWA-example",
     "examples/PWA-example",
+    "examples/query_segments_demo",
     # Playwright tests
     # Playwright tests
     "playwright-tests/liveview",
     "playwright-tests/liveview",
     "playwright-tests/web",
     "playwright-tests/web",
@@ -49,12 +51,12 @@ members = [
 exclude = ["examples/mobile_demo"]
 exclude = ["examples/mobile_demo"]
 
 
 [workspace.package]
 [workspace.package]
-version = "0.4.1"
+version = "0.4.2"
 
 
 # dependencies that are shared across packages
 # dependencies that are shared across packages
 [workspace.dependencies]
 [workspace.dependencies]
 dioxus = { path = "packages/dioxus", version = "0.4.0" }
 dioxus = { path = "packages/dioxus", version = "0.4.0" }
-dioxus-core = { path = "packages/core", version = "0.4.0" }
+dioxus-core = { path = "packages/core", version = "0.4.2" }
 dioxus-core-macro = { path = "packages/core-macro", version = "0.4.0"  }
 dioxus-core-macro = { path = "packages/core-macro", version = "0.4.0"  }
 dioxus-router = { path = "packages/router", version = "0.4.1"  }
 dioxus-router = { path = "packages/router", version = "0.4.1"  }
 dioxus-router-macro = { path = "packages/router-macro", version = "0.4.1" }
 dioxus-router-macro = { path = "packages/router-macro", version = "0.4.1" }
@@ -76,6 +78,7 @@ dioxus-native-core = { path = "packages/native-core", version = "0.4.0" }
 dioxus-native-core-macro = { path = "packages/native-core-macro", version = "0.4.0" }
 dioxus-native-core-macro = { path = "packages/native-core-macro", version = "0.4.0" }
 rsx-rosetta = { path = "packages/rsx-rosetta", version = "0.4.0" }
 rsx-rosetta = { path = "packages/rsx-rosetta", version = "0.4.0" }
 dioxus-signals = { path = "packages/signals" }
 dioxus-signals = { path = "packages/signals" }
+generational-box = { path = "packages/generational-box" }
 dioxus-hot-reload = { path = "packages/hot-reload", version = "0.4.0" }
 dioxus-hot-reload = { path = "packages/hot-reload", version = "0.4.0" }
 dioxus-fullstack = { path = "packages/fullstack", version = "0.4.1"  }
 dioxus-fullstack = { path = "packages/fullstack", version = "0.4.1"  }
 dioxus_server_macro = { path = "packages/server-macro", version = "0.4.1" }
 dioxus_server_macro = { path = "packages/server-macro", version = "0.4.1" }

+ 3 - 1
README.md

@@ -40,11 +40,13 @@
     <span> | </span>
     <span> | </span>
     <a href="https://github.com/DioxusLabs/example-projects"> Examples </a>
     <a href="https://github.com/DioxusLabs/example-projects"> Examples </a>
     <span> | </span>
     <span> | </span>
-    <a href="https://dioxuslabs.com/docs/0.3/guide/en/"> Guide </a>
+    <a href="https://dioxuslabs.com/learn/0.4/guide"> Guide </a>
     <span> | </span>
     <span> | </span>
     <a href="https://github.com/DioxusLabs/dioxus/blob/master/notes/README/ZH_CN.md"> 中文 </a>
     <a href="https://github.com/DioxusLabs/dioxus/blob/master/notes/README/ZH_CN.md"> 中文 </a>
     <span> | </span>
     <span> | </span>
     <a href="https://github.com/DioxusLabs/dioxus/blob/master/translations/pt-br/README.md"> PT-BR </a>
     <a href="https://github.com/DioxusLabs/dioxus/blob/master/translations/pt-br/README.md"> PT-BR </a>
+    <span> | </span>
+    <a href="https://github.com/DioxusLabs/dioxus/blob/master/translations/ja-jp/README.md"> 日本語 </a>
   </h3>
   </h3>
 </div>
 </div>
 
 

+ 2 - 1
docs/guide/examples/readme_expanded.rs

@@ -89,7 +89,8 @@ fn app(cx: Scope) -> Element {
                     key: None,
                     key: None,
                     // The static template this node will use. The template is stored in a Cell so it can be replaced with a new template when hot rsx reloading is enabled
                     // The static template this node will use. The template is stored in a Cell so it can be replaced with a new template when hot rsx reloading is enabled
                     template: std::cell::Cell::new(TEMPLATE),
                     template: std::cell::Cell::new(TEMPLATE),
-                    root_ids: Default::default(),
+                    root_ids: dioxus::core::exports::bumpalo::collections::Vec::new_in(__cx.bump())
+                        .into(),
                     dynamic_nodes: __cx.bump().alloc([
                     dynamic_nodes: __cx.bump().alloc([
                         // The dynamic count text node (dynamic node id 0)
                         // The dynamic count text node (dynamic node id 0)
                         __cx.text_node(format_args!("High-Five counter: {0}", count)),
                         __cx.text_node(format_args!("High-Five counter: {0}", count)),

+ 1 - 1
docs/guide/src/en/getting_started/web.md

@@ -25,7 +25,7 @@ The Web is the best-supported target platform for Dioxus.
 To develop your Dioxus app for the web, you'll need a tool to build and serve your assets. We recommend using [dioxus-cli](https://github.com/DioxusLabs/dioxus/tree/master/packages/cli) which includes a build system, Wasm optimization, a dev server, and support hot reloading:
 To develop your Dioxus app for the web, you'll need a tool to build and serve your assets. We recommend using [dioxus-cli](https://github.com/DioxusLabs/dioxus/tree/master/packages/cli) which includes a build system, Wasm optimization, a dev server, and support hot reloading:
 
 
 ```shell
 ```shell
-cargo install dioxus-cli
+cargo install dioxus-cli --locked
 ```
 ```
 
 
 Make sure the `wasm32-unknown-unknown` target for rust is installed:
 Make sure the `wasm32-unknown-unknown` target for rust is installed:

+ 2 - 0
docs/guide/src/en/interactivity/event_handlers.md

@@ -51,6 +51,8 @@ Any event handlers will still be called.
 
 
 > Normally, in React or JavaScript, you'd call "preventDefault" on the event in the callback. Dioxus does _not_ currently support this behavior. Note: this means you cannot conditionally prevent default behavior based on the data in the event.
 > Normally, in React or JavaScript, you'd call "preventDefault" on the event in the callback. Dioxus does _not_ currently support this behavior. Note: this means you cannot conditionally prevent default behavior based on the data in the event.
 
 
+> Note about forms: if an event handler is attached to the `onsubmit` event of a form, default behavior is to **not submit it**, meaning having `prevent_default: "onsubmit"` will submit it in this case.
+
 ## Handler Props
 ## Handler Props
 
 
 Sometimes, you might want to make a component that accepts an event handler. A simple example would be a `FancyButton` component, which accepts an `on_click` handler:
 Sometimes, you might want to make a component that accepts an event handler. A simple example would be a `FancyButton` component, which accepts an `on_click` handler:

+ 2 - 0
docs/guide/src/pt-br/interactivity/event_handlers.md

@@ -64,3 +64,5 @@ Então, você pode usá-lo como qualquer outro manipulador:
 > Nota: assim como qualquer outro atributo, você pode nomear os manipuladores como quiser! Embora eles devam começar com `on`, para que o prop seja automaticamente transformado em um `EventHandler` no local da chamada.
 > Nota: assim como qualquer outro atributo, você pode nomear os manipuladores como quiser! Embora eles devam começar com `on`, para que o prop seja automaticamente transformado em um `EventHandler` no local da chamada.
 >
 >
 > Você também pode colocar dados personalizados no evento, em vez de, por exemplo, `MouseData`
 > Você também pode colocar dados personalizados no evento, em vez de, por exemplo, `MouseData`
+
+> Nota sobre formulários: se um manipulador de evento está anexado ao evento `onsubmit` em um formulário, o comportamento padrão é de **não submetê-lo**. Portanto, especificar `prevent_default: "onsubmit"` irá submetê-lo.

+ 6 - 5
examples/PWA-example/README.md

@@ -7,12 +7,13 @@ It is also very much usable as a template for your projects, if you're aiming to
 
 
 ## Try the example
 ## Try the example
 
 
-Make sure you have Dioxus CLI installed (if you're unsure, run `cargo install dioxus-cli`).
+Make sure you have Dioxus CLI installed (if you're unsure, run `cargo install dioxus-cli --locked`).
 
 
 You can run `dx serve` in this directory to start the web server locally, or run
 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.
 `dx build --release` to build the project so you can deploy it on a separate web-server.
 
 
 ## Project Structure
 ## Project Structure
+
 ```
 ```
 ├── Cargo.toml
 ├── Cargo.toml
 ├── Dioxus.toml
 ├── Dioxus.toml
@@ -33,12 +34,12 @@ You can run `dx serve` in this directory to start the web server locally, or run
 
 
 If you're just getting started with PWAs, here are some useful resources:
 If you're just getting started with PWAs, here are some useful resources:
 
 
-* [PWABuilder docs](https://docs.pwabuilder.com/#/)
-* [MDN article on PWAs](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps)
+- [PWABuilder docs](https://docs.pwabuilder.com/#/)
+- [MDN article on PWAs](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps)
 
 
 For service worker scripting (in JavaScript):
 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)
+- [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.

+ 1 - 7
examples/clock.rs

@@ -1,17 +1,11 @@
-//! Example: README.md showcase
-//!
-//! The example from the README.md.
-
 use dioxus::prelude::*;
 use dioxus::prelude::*;
-use dioxus_signals::{use_init_signal_rt, use_signal};
+use dioxus_signals::use_signal;
 
 
 fn main() {
 fn main() {
     dioxus_desktop::launch(app);
     dioxus_desktop::launch(app);
 }
 }
 
 
 fn app(cx: Scope) -> Element {
 fn app(cx: Scope) -> Element {
-    use_init_signal_rt(cx);
-
     let mut count = use_signal(cx, || 0);
     let mut count = use_signal(cx, || 0);
 
 
     use_future!(cx, || async move {
     use_future!(cx, || async move {

+ 0 - 1
examples/login_form.rs

@@ -35,7 +35,6 @@ fn app(cx: Scope) -> Element {
         h1 { "Login" }
         h1 { "Login" }
         form {
         form {
             onsubmit: onsubmit,
             onsubmit: onsubmit,
-            prevent_default: "onsubmit", // Prevent the default behavior of <form> to post
             input { r#type: "text", id: "username", name: "username" }
             input { r#type: "text", id: "username", name: "username" }
             label { "Username" }
             label { "Username" }
             br {}
             br {}

+ 1 - 1
examples/mobile_demo/src/lib.rs

@@ -72,7 +72,7 @@ fn app(cx: Scope) -> Element {
                     onclick: move|_| {
                     onclick: move|_| {
                         println!("Clicked!");
                         println!("Clicked!");
                         items.push(items.len());
                         items.push(items.len());
-                        cx.needs_update_any(ScopeId(0));
+                        cx.needs_update_any(ScopeId::ROOT);
                         println!("Requested update");
                         println!("Requested update");
                     },
                     },
                     "Add item"
                     "Add item"

+ 12 - 0
examples/query_segments_demo/Cargo.toml

@@ -0,0 +1,12 @@
+[package]
+name = "query_segments_demo"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+dioxus = { path = "../../packages/dioxus", version = "*" }
+dioxus-router = { path = "../../packages/router", version = "*" }
+dioxus-web = { path = "../../packages/web", version = "*" }
+form_urlencoded = "1.2.0"

+ 73 - 0
examples/query_segments_demo/src/main.rs

@@ -0,0 +1,73 @@
+#![allow(non_snake_case, unused)]
+//! Example: Url query segments usage
+//! ------------------------------------
+//!
+//! This example shows how to access and use multiple query segments present in an url on the web.
+//!
+//! Run `dx serve` and navigate to `http://localhost:8080/blog?name=John&surname=Doe`
+use std::fmt::Display;
+
+use dioxus::prelude::*;
+use dioxus_router::prelude::*;
+
+// ANCHOR: route
+#[derive(Routable, Clone)]
+#[rustfmt::skip]
+enum Route {
+    // segments that start with ?: are query segments
+    #[route("/blog?:query_params")]
+    BlogPost {
+        // You must include query segments in child variants
+        query_params: BlogQuerySegments,
+    },
+}
+
+#[derive(Debug, Clone, PartialEq)]
+struct BlogQuerySegments {
+    name: String,
+    surname: String,
+}
+
+/// The display impl needs to display the query in a way that can be parsed:
+impl Display for BlogQuerySegments {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "name={}&surname={}", self.name, self.surname)
+    }
+}
+
+/// The query segment is anything that implements https://docs.rs/dioxus-router/latest/dioxus_router/routable/trait.FromQuery.html. You can implement that trait for a struct if you want to parse multiple query parameters.
+impl FromQuery for BlogQuerySegments {
+    fn from_query(query: &str) -> Self {
+        let mut name = None;
+        let mut surname = None;
+        let pairs = form_urlencoded::parse(query.as_bytes());
+        pairs.for_each(|(key, value)| {
+            if key == "name" {
+                name = Some(value.clone().into());
+            }
+            if key == "surname" {
+                surname = Some(value.clone().into());
+            }
+        });
+        Self {
+            name: name.unwrap(),
+            surname: surname.unwrap(),
+        }
+    }
+}
+
+#[inline_props]
+fn BlogPost(cx: Scope, query_params: BlogQuerySegments) -> Element {
+    render! {
+        div{"This is your blogpost with a query segment:"}
+        div{format!("{:?}", query_params)}
+    }
+}
+
+fn App(cx: Scope) -> Element {
+    render! { Router::<Route>{} }
+}
+
+fn main() {
+    dioxus_web::launch(App);
+}

+ 1 - 1
examples/signals.rs

@@ -20,7 +20,7 @@ fn app(cx: Scope) -> Element {
         button { onclick: move |_| count += 1, "Up high!" }
         button { onclick: move |_| count += 1, "Up high!" }
         button { onclick: move |_| count -= 1, "Down low!" }
         button { onclick: move |_| count -= 1, "Down low!" }
 
 
-        if count() > 5 {
+        if count.value() > 5 {
             rsx!{ h2 { "High five!" } }
             rsx!{ h2 { "High five!" } }
         }
         }
     })
     })

+ 1 - 1
examples/tailwind/Dioxus.toml

@@ -30,7 +30,7 @@ watch_path = ["src", "public"]
 [web.resource]
 [web.resource]
 
 
 # CSS style file
 # CSS style file
-style = ["tailwind.css"]
+style = ["/tailwind.css"]
 
 
 # Javascript code file
 # Javascript code file
 script = []
 script = []

+ 1 - 1
examples/tailwind/README.md

@@ -81,7 +81,7 @@ watch_path = ["src", "public"]
 [web.resource]
 [web.resource]
 
 
 # CSS style file
 # CSS style file
-style = ["tailwind.css"]
+style = ["/tailwind.css"]
 
 
 # Javascript code file
 # Javascript code file
 script = []
 script = []

+ 4 - 2
notes/README/ZH_CN.md

@@ -25,7 +25,7 @@
   </a>
   </a>
 
 
   <!--Awesome -->
   <!--Awesome -->
-  <a href="https://github.com/dioxuslabs/awesome-dioxus">
+  <a href="https://dioxuslabs.com/awesome">
     <img src="https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg" alt="Awesome Page" />
     <img src="https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg" alt="Awesome Page" />
   </a>
   </a>
   <!-- Discord -->
   <!-- Discord -->
@@ -40,11 +40,13 @@
     <span> | </span>
     <span> | </span>
     <a href="https://github.com/DioxusLabs/example-projects"> 代码示例 </a>
     <a href="https://github.com/DioxusLabs/example-projects"> 代码示例 </a>
     <span> | </span>
     <span> | </span>
-    <a href="https://dioxuslabs.com/guide/en"> 开发指南 </a>
+    <a href="https://dioxuslabs.com/learn/0.4/guide"> 开发指南 </a>
     <span> | </span>
     <span> | </span>
     <a href="https://github.com/DioxusLabs/dioxus/blob/master/README.md"> English </a>
     <a href="https://github.com/DioxusLabs/dioxus/blob/master/README.md"> English </a>
     <span> | </span>
     <span> | </span>
     <a href="https://github.com/DioxusLabs/dioxus/blob/master/translations/pt-br/README.md"> PT-BR </a>
     <a href="https://github.com/DioxusLabs/dioxus/blob/master/translations/pt-br/README.md"> PT-BR </a>
+    <span> | </span>
+    <a href="https://github.com/DioxusLabs/dioxus/blob/master/translations/ja-jp/README.md"> 日本語 </a>
   </h3>
   </h3>
 </div>
 </div>
 
 

+ 9 - 1
packages/autofmt/src/collect_macros.rs

@@ -17,7 +17,15 @@ struct MacroCollector<'a, 'b> {
 
 
 impl<'a, 'b> Visit<'b> for MacroCollector<'a, 'b> {
 impl<'a, 'b> Visit<'b> for MacroCollector<'a, 'b> {
     fn visit_macro(&mut self, i: &'b Macro) {
     fn visit_macro(&mut self, i: &'b Macro) {
-        self.macros.push(i);
+        if let Some("rsx" | "render") = i
+            .path
+            .segments
+            .last()
+            .map(|i| i.ident.to_string())
+            .as_deref()
+        {
+            self.macros.push(i)
+        }
     }
     }
 }
 }
 
 

+ 9 - 25
packages/cli/.github/workflows/main.yml

@@ -8,47 +8,31 @@ jobs:
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest
     steps:
     steps:
       - uses: actions/checkout@v2
       - uses: actions/checkout@v2
-      - uses: actions-rs/toolchain@v1
-        with:
-          profile: minimal
-          toolchain: stable
-          override: true
+      - name: Install Rust
+        uses: dtolnay/rust-toolchain@stable
       - uses: Swatinem/rust-cache@v1
       - uses: Swatinem/rust-cache@v1
-      - uses: actions-rs/cargo@v1
-        with:
-          command: check
+      - run: cargo check
 
 
   test:
   test:
     name: Test Suite
     name: Test Suite
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest
     steps:
     steps:
       - uses: actions/checkout@v2
       - uses: actions/checkout@v2
-      - uses: actions-rs/toolchain@v1
-        with:
-          profile: minimal
-          toolchain: stable
-          override: true
+      - name: Install Rust
+        uses: dtolnay/rust-toolchain@stable
       - uses: Swatinem/rust-cache@v1
       - uses: Swatinem/rust-cache@v1
-      - uses: actions-rs/cargo@v1
-        with:
-          command: test
+      - run: cargo test
 
 
   fmt:
   fmt:
     name: Rustfmt
     name: Rustfmt
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest
     steps:
     steps:
       - uses: actions/checkout@v2
       - uses: actions/checkout@v2
-      - uses: actions-rs/toolchain@v1
-        with:
-          profile: minimal
-          toolchain: stable
-          override: true
+      - name: Install Rust
+        uses: dtolnay/rust-toolchain@stable
       - uses: Swatinem/rust-cache@v1
       - uses: Swatinem/rust-cache@v1
       - run: rustup component add rustfmt
       - run: rustup component add rustfmt
-      - uses: actions-rs/cargo@v1
-        with:
-          command: fmt
-          args: --all -- --check
+      - run: cargo fmt --all -- --check
 
 
   # clippy:
   # clippy:
   #  name: Clippy
   #  name: Clippy

+ 10 - 5
packages/cli/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 [package]
 name = "dioxus-cli"
 name = "dioxus-cli"
-version = { workspace = true }
+version = "0.4.1"
 authors = ["Jonathan Kelley"]
 authors = ["Jonathan Kelley"]
 edition = "2021"
 edition = "2021"
 description = "CLI tool for developing, testing, and publishing Dioxus apps"
 description = "CLI tool for developing, testing, and publishing Dioxus apps"
@@ -36,7 +36,7 @@ chrono = "0.4.19"
 anyhow = "1.0.53"
 anyhow = "1.0.53"
 hyper = "0.14.17"
 hyper = "0.14.17"
 hyper-rustls = "0.23.2"
 hyper-rustls = "0.23.2"
-indicatif = "0.17.0-rc.11"
+indicatif = "0.17.5"
 subprocess = "0.2.9"
 subprocess = "0.2.9"
 
 
 axum = { version = "0.5.1", features = ["ws", "headers"] }
 axum = { version = "0.5.1", features = ["ws", "headers"] }
@@ -75,11 +75,10 @@ gitignore = "1.0.7"
 open = "4.1.0"
 open = "4.1.0"
 cargo-generate = "0.18"
 cargo-generate = "0.18"
 toml_edit = "0.19.11"
 toml_edit = "0.19.11"
-# dioxus-rsx = "0.0.1"
 
 
 # bundling
 # bundling
-tauri-bundler = { version = "1.2", features = ["native-tls-vendored"] }
-tauri-utils = "1.3"
+tauri-bundler = { version = "=1.3.0", features = ["native-tls-vendored"] }
+tauri-utils = "=1.4.*"
 
 
 dioxus-autofmt = { workspace = true }
 dioxus-autofmt = { workspace = true }
 dioxus-check = { workspace = true }
 dioxus-check = { workspace = true }
@@ -100,3 +99,9 @@ name = "dx"
 
 
 [dev-dependencies]
 [dev-dependencies]
 tempfile = "3.3"
 tempfile = "3.3"
+
+[package.metadata.binstall]
+pkg-url = "{ repo }/releases/download/v{ version }/dx-{ target }{ archive-suffix }"
+
+[package.metadata.binstall.overrides.x86_64-pc-windows-msvc]
+pkg-fmt = "zip"

+ 21 - 18
packages/cli/Dioxus.toml

@@ -1,29 +1,31 @@
 [application]
 [application]
 
 
-# dioxus project name
-name = "dioxus-cli"
+# App name
+name = "project_name"
 
 
-# default platfrom
-# you can also use `dx serve/build --platform XXX` to use other platform
-# value: web | desktop
-default_platform = "desktop"
+# The Dioxus platform to default to
+default_platform = "web"
 
 
-# Web `build` & `serve` dist path
+# `build` & `serve` output path
 out_dir = "dist"
 out_dir = "dist"
 
 
-# resource (static) file folder
+# The static resource path
 asset_dir = "public"
 asset_dir = "public"
 
 
 [web.app]
 [web.app]
 
 
 # HTML title tag content
 # HTML title tag content
-title = "dioxus | ⛺"
+title = "project_name"
 
 
 [web.watcher]
 [web.watcher]
 
 
-watch_path = ["src"]
+# When watcher is triggered, regenerate the `index.html`
+reload_html = true
 
 
-# include `assets` in web platform
+# Which files or dirs will be monitored
+watch_path = ["src", "public"]
+
+# Include style or script assets
 [web.resource]
 [web.resource]
 
 
 # CSS style file
 # CSS style file
@@ -34,12 +36,13 @@ script = []
 
 
 [web.resource.dev]
 [web.resource.dev]
 
 
-# Javascript code file
-# serve: [dev-server] only
-script = []
+# Same as [web.resource], but for development servers
 
 
-[application.tools]
+# CSS style file
+style = []
+
+# JavaScript files
+script = []
 
 
-# use binaryen.wasm-opt for output Wasm file
-# binaryen just will trigger in `web` platform
-binaryen = { wasm_opt = true }
+[[web.proxy]]
+backend = "http://localhost:8000/api/"

+ 44 - 16
packages/cli/README.md

@@ -1,43 +1,71 @@
-<div align="center">
-  <h1>📦✨ Dioxus CLI </h1>
+<div style="text-align: center">
+  <h1>📦✨ Dioxus CLI</h1>
   <p><strong>Tooling to supercharge Dioxus projects</strong></p>
   <p><strong>Tooling to supercharge Dioxus projects</strong></p>
 </div>
 </div>
-**dioxus-cli** (inspired by wasm-pack and webpack) is a tool for getting Dioxus projects up and running.
-It handles all build, bundling, development and publishing to simplify web development.
 
 
+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.
 
 
 ## Installation
 ## Installation
 
 
-### Install stable version
+### Install the stable version (recommended)
+
 ```
 ```
-cargo install dioxus-cli
+cargo install dioxus-cli --locked
 ```
 ```
-### Install from git repository
+
+### Install the latest development build through git
+
+To get the latest bug fixes and features, you can install the development version from git.
+However, this is not fully tested.
+That means you're probably going to have more bugs despite having the latest bug fixes.
+
 ```
 ```
 cargo install --git https://github.com/DioxusLabs/dioxus dioxus-cli
 cargo install --git https://github.com/DioxusLabs/dioxus dioxus-cli
 ```
 ```
+
+This will download the CLI from the master branch,
+and install it in Cargo's global binary directory (`~/.cargo/bin/` by default).
+
 ### Install from local folder
 ### Install from local folder
+
 ```
 ```
 cargo install --path . --debug
 cargo install --path . --debug
 ```
 ```
 
 
+## Get started
 
 
-## Get Started
-
-Use `dx create project-name` to initialize a new Dioxus project. <br>
-
+Use `dx create project-name` to initialize a new Dioxus project.
 It will be cloned from the [dioxus-template](https://github.com/DioxusLabs/dioxus-template) repository.
 It will be cloned from the [dioxus-template](https://github.com/DioxusLabs/dioxus-template) repository.
 
 
-<br>
-
 Alternatively, you can specify the template path:
 Alternatively, you can specify the template path:
 
 
 ```
 ```
 dx create hello --template gh:dioxuslabs/dioxus-template
 dx create hello --template gh:dioxuslabs/dioxus-template
 ```
 ```
 
 
-## Dioxus Config File
+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.
+
+## Dioxus config file
+
+You can use the `Dioxus.toml` file for further configuration.
+Some fields are mandatory, but the CLI tool will tell you which ones are missing.
+You can create a `Dioxus.toml` with all fields already set using `dx config init project-name`,
+or you can use this bare-bones template (only mandatory fields) to get started:
 
 
-Dioxus CLI will use `Dioxus.toml` file to Identify some project info and switch some cli feature.
+```toml
+[application]
+name = "project-name"
+# Currently supported platforms: web, desktop
+default_platform = "web"
+
+[web.app]
+title = "Hello"
+
+[web.watcher]
+
+[web.resource.dev]
+```
 
 
-You can get more configure information from [Dioxus CLI Document](https://dioxuslabs.com/cli/configure.html).
+The full anatomy of `Dioxus.toml` is shown on the [Dioxus website](https://dioxuslabs.com/learn/0.4/CLI/configure).

+ 1 - 1
packages/cli/docs/book.toml

@@ -3,4 +3,4 @@ authors = ["YuKun Liu"]
 language = "en"
 language = "en"
 multilingual = false
 multilingual = false
 src = "src"
 src = "src"
-title = "Dioxus Cli"
+title = "Dioxus CLI"

+ 9 - 14
packages/cli/docs/src/SUMMARY.md

@@ -2,17 +2,12 @@
 
 
 - [Introduction](./introduction.md)
 - [Introduction](./introduction.md)
 - [Installation](./installation.md)
 - [Installation](./installation.md)
-- [Create a Project](./creating.md)
-- [Configure Project](./configure.md)
-- [Commands](./cmd/README.md)
-  - [Build](./cmd/build.md)
-  - [Serve](./cmd/serve.md)
-  - [Clean](./cmd/clean.md)
-  - [Translate](./cmd/translate.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)
+- [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)

+ 0 - 30
packages/cli/docs/src/cmd/README.md

@@ -1,30 +0,0 @@
-# Commands
-
-In this chapter we will introduce all `dioxus-cli` commands.
-
-> You can also use `dx --help` to get cli help info.
-
-```
-Build, Bundle & Ship Dioxus Apps
-
-Usage: dx [OPTIONS] <COMMAND>
-
-Commands:
-  build      Build the Rust WASM app and all of its assets
-  translate  Translate some source file into Dioxus code
-  serve      Build, watch & serve the Rust WASM app and all of its assets
-  create     Init a new project for Dioxus
-  clean      Clean output artifacts
-  bundle     Bundle the Rust desktop app and all of its assets
-  version    Print the version of this extension
-  fmt        Format some rsx
-  check      Check the Rust files in the project for issues
-  config     Dioxus config file controls
-  help       Print this message or the help of the given subcommand(s)
-
-Options:
-  -v               Enable verbose logging
-      --bin <BIN>  Specify bin target
-  -h, --help       Print help
-  -V, --version    Print version
-```

+ 0 - 56
packages/cli/docs/src/cmd/build.md

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

+ 0 - 27
packages/cli/docs/src/cmd/clean.md

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

+ 0 - 70
packages/cli/docs/src/cmd/serve.md

@@ -1,70 +0,0 @@
-# Serve
-
-The `dx serve` can start a dev server with hot-reloading
-
-```
-dioxus-serve
-Build, watch & serve the Rust WASM app and all of its assets
-
-USAGE:
-    dx serve [OPTIONS]
-
-OPTIONS:
-        --example <EXAMPLE>      [default: ""]
-        --platform <PLATFORM>    [default: "default_platform"]
-        --release                [default: false]
-        --hot-reload             [default: false]
-        --bin                    [default: None]
-```
-
-You can use this command to build project and start a dev server:
-
-```
-dx serve
-```
-
-## Serve Example
-
-You can use the `example` option to serve a example:
-
-```
-# serve the `test` example
-dx serve --exmaple test
-```
-
-## Specify workspace bin
-
-You can add the `--bin` option to select which crate you want Dioxus to build and serve:
-
-```
-dx serve --bin app
-```
-
-## Open Browser
-
-You can add the `--open` option to open system default browser when server startup:
-
-```
-dx serve --open
-```
-
-## RSX Hot Reloading
-
-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:
-
-```
-dx serve --open
-```
-
-## Cross Origin Policy
-
-You can add the `cross-origin-policy` option to change cross-origin header to:
-
-```
-  Cross-Origin-Opener-Policy: same-origin
-  Cross-Origin-Embedder-Policy: require-corp
-```
-
-```
-dx serve --corss-origin-policy
-```

+ 0 - 68
packages/cli/docs/src/cmd/translate.md

@@ -1,68 +0,0 @@
-# Translate
-
-`dx translate` can translate some `html` file into a Dioxus compoent
-
-```
-dioxus-translate
-Translate some source file into a Dioxus component
-
-USAGE:
-    dx translate [OPTIONS] [OUTPUT]
-
-ARGS:
-    <OUTPUT>    Output file, defaults to stdout if not present
-
-OPTIONS:
-    -c, --component      Activate debug mode
-    -f, --file <FILE>    Input file
-```
-
-## Translate HTML to stdout
-
-You can use the `file` option to set path to the `html` file to translate:
-
-```
-dx transtale --file ./index.html
-```
-
-## Output rsx to a file
-
-You can pass a file to the traslate command to set the path to write the output of the command to:
-
-```
-dx translate --file ./index.html component.rsx
-```
-
-## Output rsx to a file
-
-Setting the `component` option will create a compoent from the HTML:
-
-```
-dx translate --file ./index.html --component
-```
-
-## Example
-
-This HTML:
-```html
-<div>
-    <h1> Hello World </h1>
-    <a href="https://dioxuslabs.com/">Link</a>
-</div>
-```
-
-Translates into this Dioxus component:
-
-```rust
-fn component(cx: Scope) -> Element {
-    cx.render(rsx! {
-        div {
-            h1 { "Hello World" },
-            a {
-                href: "https://dioxuslabs.com/",
-                "Link"
-            }
-        }
-    })
-}
-```

+ 85 - 96
packages/cli/docs/src/configure.md

@@ -1,166 +1,155 @@
 # Configure Project
 # 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.
 
 
-This chapter will introduce you to how to configure the CLI with your `Dioxus.toml` file
-
-Be aware that if the config file is present in the folder, some fields must be filled out, or the CLI tool will abort. The mandatory [table headers](https://toml.io/en/v1.0.0#table) and keys will have a '✍' sign beside it.
+"🔒" 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
 ## Structure
 
 
-The CLI uses a `Dioxus.toml` file in the root of your crate to define some configuration for your `dioxus` project.
-
-### Application ✍
+Each header has it's TOML form directly under it.
 
 
-General application confiration:
+### Application 🔒
 
 
-```
+```toml
 [application]
 [application]
-# configuration
 ```
 ```
-1. ***name*** ✍ - project name & title
-2. ***default_platform*** ✍ - which platform target for this project.
 
 
+Application-wide configuration. Applies to both web and desktop.
+
+1. **name** 🔒 - Project name & title.
+   ```toml
+   name = "my_project"
    ```
    ```
-   name = "my-project"
-   ```
-2. ***default_platform*** - The platform this project targets
-   ```ß
-   # current supported platforms: web, desktop
-   # default: web
+
+2. **default_platform** 🔒 - The platform this project targets
+   ```toml
+   # Currently supported platforms: web, desktop
    default_platform = "web"
    default_platform = "web"
    ```
    ```
-   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
-    ```
+
+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"
     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.
-   ```
+
+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"
    asset_dir = "public"
    ```
    ```
-5. ***sub_package*** - The sub package in the workspace to build by default
-   ```
+
+5. **sub_package** - The sub package in the workspace to build by default.
+   ```toml
    sub_package = "my-crate"
    sub_package = "my-crate"
    ```
    ```
 
 
-### Web.App ✍
-
-Configeration specific to web applications:
+### Web.App 🔒
 
 
-```
+```toml
 [web.app]
 [web.app]
-# configuration
 ```
 ```
 
 
-1. ***title*** - The title of the web page
-   ```
+Web-specific configuration.
+
+1. **title** - The title of the web page.
+   ```toml
    # HTML title tag content
    # HTML title tag content
-   title = "dioxus app | ⛺"
-   ```
-2. ***base_path*** - The base path to build the appliation 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.
+   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
    # 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"
    base_path = "my_application"
    ```
    ```
 
 
 ### Web.Watcher ✍
 ### Web.Watcher ✍
 
 
-Configeration related to the development server:
-
-```
+```toml
 [web.watcher]
 [web.watcher]
-# configuration
 ```
 ```
 
 
-1. ***reload_html*** - If this is true, the cli will rebuild the index.html file every time the application is rebuilt
-   ```
+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
    reload_html = true
    ```
    ```
-2. ***watch_path*** - The files & directories to moniter for changes
-   ```
+
+2. **watch_path** - The files & directories to monitor for changes
+   ```toml
    watch_path = ["src", "public"]
    watch_path = ["src", "public"]
    ```
    ```
-3. ***index_on_404*** - If enabled, Dioxus CLI will serve the root page when a route is not found. *This is needed when serving an application that uses the router*
-   ```
+
+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
    index_on_404 = true
    ```
    ```
 
 
-### Web.Resource ✍
+### Web.Resource 🔒
 
 
-Configeration related to static resources your application uses:
-```
+```toml
 [web.resource]
 [web.resource]
-# configuration
 ```
 ```
 
 
-1. ***style*** - The styles (`.css` files) to include in your application
-   ```
+Static resource configuration.
+
+1. **style** - CSS files to include in your application.
+   ```toml
    style = [
    style = [
-      # include from public_dir.
+      # Include from public_dir.
       "./assets/style.css",
       "./assets/style.css",
-      # or some asset from online cdn.
+      # Or some asset from online cdn.
       "https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.css"
       "https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.css"
    ]
    ]
    ```
    ```
-2. ***script*** - The additional scripts (`.js` files) to include in your application
-    ```
-    style = [
-        # include from public_dir.
-        "./assets/index.js",
-        # or some asset from online cdn.
+
+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"
         "https://cdn.jsdelivr.net/npm/bootstrap/dist/js/bootstrap.js"
     ]
     ]
    ```
    ```
 
 
-### Web.Resource.Dev 
+### Web.Resource.Dev 🔒
 
 
-Configeration related to static resources your application uses in development:
-```
+```toml
 [web.resource.dev]
 [web.resource.dev]
-# configuration
 ```
 ```
 
 
-1. ***style*** - The styles (`.css` files) to include in your application
-   ```
-   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*** - The additional scripts (`.js` files) to include in your application
-    ```
-    style = [
-        # include from public_dir.
-        "./assets/index.js",
-        # or some asset from online cdn.
-        "https://cdn.jsdelivr.net/npm/bootstrap/dist/js/bootstrap.js"
-    ]
-   ```
+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
 ### Web.Proxy
 
 
-Configeration related to any proxies your application requires durring development. Proxies will forward requests to a new service
-
-```
+```toml
 [[web.proxy]]
 [[web.proxy]]
-# configuration
 ```
 ```
 
 
-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
-   ```
+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/"
    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 not currently supported).
+   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
 ## Config example
 
 
+This includes all fields, mandatory or not.
+
 ```toml
 ```toml
 [application]
 [application]
 
 
-# App (Project) Name
-name = "{{project-name}}"
+# App name
+name = "project_name"
 
 
 # The Dioxus platform to default to
 # The Dioxus platform to default to
 default_platform = "web"
 default_platform = "web"
@@ -168,23 +157,23 @@ default_platform = "web"
 # `build` & `serve` output path
 # `build` & `serve` output path
 out_dir = "dist"
 out_dir = "dist"
 
 
-# the static resource path
+# The static resource path
 asset_dir = "public"
 asset_dir = "public"
 
 
 [web.app]
 [web.app]
 
 
 # HTML title tag content
 # HTML title tag content
-title = "dioxus | ⛺"
+title = "project_name"
 
 
 [web.watcher]
 [web.watcher]
 
 
-# when watcher is triggered, regenerate the `index.html`
+# When watcher is triggered, regenerate the `index.html`
 reload_html = true
 reload_html = true
 
 
-# which files or dirs will be monitored
+# Which files or dirs will be monitored
 watch_path = ["src", "public"]
 watch_path = ["src", "public"]
 
 
-# include `assets` in web platform
+# Include style or script assets
 [web.resource]
 [web.resource]
 
 
 # CSS style file
 # CSS style file
@@ -195,12 +184,12 @@ script = []
 
 
 [web.resource.dev]
 [web.resource.dev]
 
 
-# serve: [dev-server] only
+# Same as [web.resource], but for development servers
 
 
 # CSS style file
 # CSS style file
 style = []
 style = []
 
 
-# Javascript code file
+# JavaScript files
 script = []
 script = []
 
 
 [[web.proxy]]
 [[web.proxy]]

+ 9 - 11
packages/cli/docs/src/creating.md

@@ -1,39 +1,37 @@
 # Create a Project
 # Create a Project
 
 
-Once you have the Dioxus CLI tool installed, you can use it to create dioxus project.
+Once you have the Dioxus CLI installed, you can use it to create your own project!
 
 
 ## Initializing a default project
 ## Initializing a default project
 
 
-First, run the `dx 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:
 ```
 ```
 dx 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)
-> This default template is use for `web` platform application.
+> 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:
 > 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
 > dx init hello-dioxus --template=gh:dioxuslabs/dioxus-template
 > ```
 > ```
 
 
-Next, move the current directory into your new project:
+Next, navigate into your new project:
 
 
 ```
 ```
 cd hello-dioxus
 cd hello-dioxus
 ```
 ```
 
 
-> Make sure `wasm32 target` is installed before running the Web project.
-> You can install the wasm target for rust using rustup:
+> 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
 > rustup target add wasm32-unknown-unknown
 > ```
 > ```
 
 
-Finally, create serve your project with the Dioxus CLI:
-
+Finally, serve your project:
 ```
 ```
 dx serve
 dx serve
 ```
 ```
 
 
-By default, the CLI serve your site at: [`http://127.0.0.1:8080/`](http://127.0.0.1:8080/)
+By default, the CLI serves your website at [`http://127.0.0.1:8080/`](http://127.0.0.1:8080/).

+ 9 - 8
packages/cli/docs/src/installation.md

@@ -1,22 +1,23 @@
 # Installation
 # Installation
 
 
-Choose any one of the methods below to install the Dioxus CLI:
+## Install the latest development build through git
 
 
-## Install from latest git version
-
-To get the most up to date bug fixes and features of the Dioxus CLI, you can install the development version from 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
 cargo install --git https://github.com/Dioxuslabs/cli
 ```
 ```
 
 
-This will automatically download `Dioxus-CLI` source from github master branch,
+This will download `Dioxus-CLI` source from GitHub master branch,
 and install it in Cargo's global binary directory (`~/.cargo/bin/` by default).
 and install it in Cargo's global binary directory (`~/.cargo/bin/` by default).
 
 
-## Install from `crates.io` version
+## Install stable through `crates.io`
 
 
-The published version of the Dioxus CLI is updated less often, but should be more stable than the git version of the Dioxus CLI.
+The published version of the Dioxus CLI is updated less often, but is more stable than the git version.
 
 
 ```
 ```
-cargo install dioxus-cli
+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.

+ 12 - 15
packages/cli/docs/src/introduction.md

@@ -1,21 +1,18 @@
 # Introduction
 # Introduction
 
 
-📦✨ **Dioxus-Cli** is a tool to help get dioxus projects off the ground.
-
-![dioxus-logo](https://dioxuslabs.com/guide/images/dioxuslogo_full.png)
-
-It includes `dev server`, `hot reload` and some `quick command` to help you use dioxus.
+The 📦✨ **Dioxus CLI** is a tool to help get Dioxus projects off the ground.
 
 
 ## Features
 ## 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] `html` to `rsx` conversion tool
-- [x] hot reload for `web` platform
-- [x] create dioxus project from `git` repo
-- [x] build & pack dioxus project
-- [ ] autoformat dioxus `rsx` code
-
-## Contributors
-
-Contributors to this guide:
-
-- [mrxiaozhuox](https://github.com/mrxiaozhuox)
+- [x] Hot Reload for `web` platform
+- [x] Create a Dioxus project from `git` repo
+- [x] Build & pack Dioxus project
+- [ ] Automatically format Dioxus `rsx` code
+-->

+ 74 - 13
packages/cli/docs/src/plugin/README.md

@@ -1,24 +1,85 @@
-# CLI Plugin Development
+# CLI Plugin development
 
 
-> For Cli 0.2.0 we will add `plugin-develop` support.
+**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.**
 
 
-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.
+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.
 
 
-### Why Lua ?
+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.
 
 
-We choose `Lua: 5.4` to be the plugin develop language, because cli plugin is not complex, just like a workflow, and user & developer can write some easy code for their plugin. We have **vendored** lua in cli program, and user don't need install lua runtime in their computer, and the lua parser & runtime doesn't take up much disk memory.
+### Why Lua?
 
 
-### Event Management
+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.
 
 
-The plugin library have pre-define some important event you can control:
+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!
 
 
-- `build.on_start`
-- `build.on_finished`
-- `serve.on_start`
-- `serve.on_rebuild`
-- `serve.on_shutdown`
 
 
-### Plugin Template
+## 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
 ```lua
 package.path = library_dir .. "/?.lua"
 package.path = library_dir .. "/?.lua"

+ 4 - 4
packages/cli/docs/src/plugin/interface/command.md

@@ -1,15 +1,15 @@
 # Command Functions
 # Command Functions
 
 
-> you can use command functions to execute some code & script
+You can use command functions to execute code and scripts.
 
 
-Type Define:
+Type definition:
 ```
 ```
 Stdio: "Inherit" | "Piped" | "Null"
 Stdio: "Inherit" | "Piped" | "Null"
 ```
 ```
 
 
 ### `exec(commands: [string], stdout: Stdio, stderr: Stdio)`
 ### `exec(commands: [string], stdout: Stdio, stderr: Stdio)`
 
 
-you can use this function to run some command on the current system.
+You can use this function to run some commands on the current system.
 
 
 ```lua
 ```lua
 local cmd = plugin.command
 local cmd = plugin.command
@@ -18,4 +18,4 @@ manager.test = function ()
     cmd.exec({"git", "clone", "https://github.com/DioxusLabs/cli-plugin-library"})
     cmd.exec({"git", "clone", "https://github.com/DioxusLabs/cli-plugin-library"})
 end
 end
 ```
 ```
-> Warning: This function don't have exception catch.
+> Warning: This function doesn't catch exceptions.

+ 7 - 12
packages/cli/docs/src/plugin/interface/dirs.md

@@ -1,33 +1,28 @@
 # Dirs Functions
 # Dirs Functions
 
 
-> you can use Dirs functions to get some directory path
+Dirs functions are for getting various directory paths. Not to be confused with `plugin.path`.
 
 
+### `plugin_dir() -> string`
 
 
-### plugin_dir() -> string
-
-You can get current plugin **root** directory path
+Get the plugin's root directory path.
 
 
 ```lua
 ```lua
 local path = plugin.dirs.plugin_dir()
 local path = plugin.dirs.plugin_dir()
 -- example: ~/Development/DioxusCli/plugin/test-plugin/
 -- example: ~/Development/DioxusCli/plugin/test-plugin/
 ```
 ```
 
 
-### bin_dir() -> string
-
-You can get plugin **bin** direcotry path
+### `bin_dir() -> string`
 
 
-Sometime you need install some binary file like `tailwind-cli` & `sass-cli` to help your plugin work, then you should put binary file in this directory.
+Get the plugin's binary directory path. Put binary files like `tailwind-cli` or `sass-cli` in this directory.
 
 
 ```lua
 ```lua
 local path = plugin.dirs.bin_dir()
 local path = plugin.dirs.bin_dir()
 -- example: ~/Development/DioxusCli/plugin/test-plugin/bin/
 -- example: ~/Development/DioxusCli/plugin/test-plugin/bin/
 ```
 ```
 
 
-### temp_dir() -> string
-
-You can get plugin **temp** direcotry path
+### `temp_dir() -> string`
 
 
-Just put some temporary file in this directory.
+Get the plugin's temporary directory path. Put any temporary files here.
 
 
 ```lua
 ```lua
 local path = plugin.dirs.bin_dir()
 local path = plugin.dirs.bin_dir()

+ 11 - 11
packages/cli/docs/src/plugin/interface/log.md

@@ -1,46 +1,46 @@
 # Log Functions
 # Log Functions
 
 
-> You can use log function to print some useful log info
+You can use log functions to print various logging information.
 
 
-### Trace(info: string)
+### `trace(info: string)`
 
 
-Print trace log info
+Print trace log info.
 
 
 ```lua
 ```lua
 local log = plugin.log
 local log = plugin.log
 log.trace("trace information")
 log.trace("trace information")
 ```
 ```
 
 
-### Debug(info: string)
+### `debug(info: string)`
 
 
-Print debug log info
+Print debug log info.
 
 
 ```lua
 ```lua
 local log = plugin.log
 local log = plugin.log
 log.debug("debug information")
 log.debug("debug information")
 ```
 ```
 
 
-### Info(info: string)
+### `info(info: string)`
 
 
-Print info log info
+Print info log info.
 
 
 ```lua
 ```lua
 local log = plugin.log
 local log = plugin.log
 log.info("info information")
 log.info("info information")
 ```
 ```
 
 
-### Warn(info: string)
+### `warn(info: string)`
 
 
-Print warning log info
+Print warning log info.
 
 
 ```lua
 ```lua
 local log = plugin.log
 local log = plugin.log
 log.warn("warn information")
 log.warn("warn information")
 ```
 ```
 
 
-### Error(info: string)
+### `error(info: string)`
 
 
-Print error log info
+Print error log info.
 
 
 ```lua
 ```lua
 local log = plugin.log
 local log = plugin.log

+ 9 - 6
packages/cli/docs/src/plugin/interface/network.md

@@ -1,12 +1,13 @@
 # Network Functions
 # Network Functions
 
 
-> you can use Network functions to download & read some data from internet
+You can use Network functions to download & read some data from the internet.
 
 
-### download_file(url: string, path: string) -> boolean
+### `download_file(url: string, path: string) -> boolean`
 
 
-This function can help you download some file from url, and it will return a *boolean* value to check the download status. (true: success | false: fail)
+Downloads a file from the specified URL,
+and returns a `boolean` that represents the download status (true: success, false: failure).
 
 
-You need pass a target url and a local path (where you want to save this file)
+You need to pass a target URL and a local path (where you want to save this file).
 
 
 ```lua
 ```lua
 -- this file will download to plugin temp directory
 -- this file will download to plugin temp directory
@@ -19,9 +20,11 @@ if status != true then
 end
 end
 ```
 ```
 
 
-### clone_repo(url: string, path: string) -> boolean
+### `clone_repo(url: string, path: string) -> boolean`
 
 
-This function can help you use `git clone` command (this system must have been installed git)
+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
 ```lua
 local status = plugin.network.clone_repo(
 local status = plugin.network.clone_repo(

+ 3 - 3
packages/cli/docs/src/plugin/interface/os.md

@@ -1,10 +1,10 @@
 # OS Functions
 # OS Functions
 
 
-> you can use OS functions to get some system information
+OS functions are for getting system information.
 
 
-### current_platform() -> string ("windows" | "macos" | "linux")
+### `current_platform() -> string ("windows" | "macos" | "linux")`
 
 
-This function can help you get system & platform type:
+Get the current OS platform.
 
 
 ```lua
 ```lua
 local platform = plugin.os.current_platform()
 local platform = plugin.os.current_platform()

+ 14 - 11
packages/cli/docs/src/plugin/interface/path.md

@@ -1,10 +1,13 @@
 # Path Functions
 # Path Functions
 
 
-> you can use path functions to operate valid path string
+You can use path functions to perform operations on valid path strings.
 
 
-### join(path: string, extra: string) -> string
+### `join(path: string, extra: string) -> string`
 
 
-This function can help you extend a path, you can extend any path, dirname or filename.
+<!-- 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
 ```lua
 local current_path = "~/hello/dioxus"
 local current_path = "~/hello/dioxus"
@@ -12,9 +15,9 @@ local new_path = plugin.path.join(current_path, "world")
 -- new_path = "~/hello/dioxus/world"
 -- new_path = "~/hello/dioxus/world"
 ```
 ```
 
 
-### parent(path: string) -> string
+### `parent(path: string) -> string`
 
 
-This function will return `path` parent-path string, back to the parent.
+Return the parent path of the specified path. The parent path is always a directory.
 
 
 ```lua
 ```lua
 local current_path = "~/hello/dioxus"
 local current_path = "~/hello/dioxus"
@@ -22,14 +25,14 @@ local new_path = plugin.path.parent(current_path)
 -- new_path = "~/hello/"
 -- new_path = "~/hello/"
 ```
 ```
 
 
-### exists(path: string) -> boolean
+### `exists(path: string) -> boolean`
 
 
-This function can check some path (dir & file) is exists.
+Check if the specified path exists, as either a file or a directory.
 
 
-### is_file(path: string) -> boolean
+### `is_file(path: string) -> boolean`
 
 
-This function can check some path is a exist file.
+Check if the specified path is a file.
 
 
-### is_dir(path: string) -> boolean
+### `is_dir(path: string) -> boolean`
 
 
-This function can check some path is a exist dir.
+Check if the specified path is a directory.

+ 13 - 11
packages/cli/src/builder.rs

@@ -22,6 +22,7 @@ pub struct BuildResult {
     pub elapsed_time: u128,
     pub elapsed_time: u128,
 }
 }
 
 
+#[allow(unused)]
 pub fn build(config: &CrateConfig, quiet: bool) -> Result<BuildResult> {
 pub fn build(config: &CrateConfig, quiet: bool) -> Result<BuildResult> {
     // [1] Build the project with cargo, generating a wasm32-unknown-unknown target (is there a more specific, better target to leverage?)
     // [1] Build the project with cargo, generating a wasm32-unknown-unknown target (is there a more specific, better target to leverage?)
     // [2] Generate the appropriate build folders
     // [2] Generate the appropriate build folders
@@ -53,7 +54,8 @@ pub fn build(config: &CrateConfig, quiet: bool) -> Result<BuildResult> {
         .arg("build")
         .arg("build")
         .arg("--target")
         .arg("--target")
         .arg("wasm32-unknown-unknown")
         .arg("wasm32-unknown-unknown")
-        .arg("--message-format=json");
+        .arg("--message-format=json")
+        .arg("--quiet");
 
 
     let cmd = if config.release {
     let cmd = if config.release {
         cmd.arg("--release")
         cmd.arg("--release")
@@ -66,8 +68,6 @@ pub fn build(config: &CrateConfig, quiet: bool) -> Result<BuildResult> {
         cmd
         cmd
     };
     };
 
 
-    let cmd = if quiet { cmd.arg("--quiet") } else { cmd };
-
     let cmd = if config.custom_profile.is_some() {
     let cmd = if config.custom_profile.is_some() {
         let custom_profile = config.custom_profile.as_ref().unwrap();
         let custom_profile = config.custom_profile.as_ref().unwrap();
         cmd.arg("--profile").arg(custom_profile)
         cmd.arg("--profile").arg(custom_profile)
@@ -93,18 +93,21 @@ pub fn build(config: &CrateConfig, quiet: bool) -> Result<BuildResult> {
     // [2] Establish the output directory structure
     // [2] Establish the output directory structure
     let bindgen_outdir = out_dir.join("assets").join("dioxus");
     let bindgen_outdir = out_dir.join("assets").join("dioxus");
 
 
-    let release_type = match config.release {
-        true => "release",
-        false => "debug",
+    let build_profile = if config.custom_profile.is_some() {
+        config.custom_profile.as_ref().unwrap()
+    } else if config.release {
+        "release"
+    } else {
+        "debug"
     };
     };
 
 
     let input_path = match executable {
     let input_path = match executable {
         ExecutableType::Binary(name) | ExecutableType::Lib(name) => target_dir
         ExecutableType::Binary(name) | ExecutableType::Lib(name) => target_dir
-            .join(format!("wasm32-unknown-unknown/{}", release_type))
+            .join(format!("wasm32-unknown-unknown/{}", build_profile))
             .join(format!("{}.wasm", name)),
             .join(format!("{}.wasm", name)),
 
 
         ExecutableType::Example(name) => target_dir
         ExecutableType::Example(name) => target_dir
-            .join(format!("wasm32-unknown-unknown/{}/examples", release_type))
+            .join(format!("wasm32-unknown-unknown/{}/examples", build_profile))
             .join(format!("{}.wasm", name)),
             .join(format!("{}.wasm", name)),
     };
     };
 
 
@@ -383,10 +386,9 @@ fn prettier_build(cmd: subprocess::Exec) -> anyhow::Result<Vec<Diagnostic>> {
         }
         }
     }
     }
 
 
-    StopSpinOnDrop(pb.clone());
-
     let stdout = cmd.detached().stream_stdout()?;
     let stdout = cmd.detached().stream_stdout()?;
     let reader = std::io::BufReader::new(stdout);
     let reader = std::io::BufReader::new(stdout);
+
     for message in cargo_metadata::Message::parse_stream(reader) {
     for message in cargo_metadata::Message::parse_stream(reader) {
         match message.unwrap() {
         match message.unwrap() {
             Message::CompilerMessage(msg) => {
             Message::CompilerMessage(msg) => {
@@ -406,7 +408,7 @@ fn prettier_build(cmd: subprocess::Exec) -> anyhow::Result<Vec<Diagnostic>> {
                 }
                 }
             }
             }
             Message::CompilerArtifact(artifact) => {
             Message::CompilerArtifact(artifact) => {
-                pb.set_message(format!("Compiling {} ", artifact.package_id));
+                pb.set_message(format!("⚙️ Compiling {} ", artifact.package_id));
                 pb.tick();
                 pb.tick();
             }
             }
             Message::BuildScriptExecuted(script) => {
             Message::BuildScriptExecuted(script) => {

+ 1 - 1
packages/cli/src/cli/build.rs

@@ -42,7 +42,7 @@ impl Build {
 
 
         match platform {
         match platform {
             Platform::Web => {
             Platform::Web => {
-                crate::builder::build(&crate_config, false)?;
+                crate::builder::build(&crate_config, true)?;
             }
             }
             Platform::Desktop => {
             Platform::Desktop => {
                 crate::builder::build_desktop(&crate_config, false)?;
                 crate::builder::build_desktop(&crate_config, false)?;

+ 3 - 0
packages/cli/src/config.rs

@@ -381,6 +381,7 @@ impl From<DebianSettings> for tauri_bundler::DebianSettings {
         tauri_bundler::DebianSettings {
         tauri_bundler::DebianSettings {
             depends: val.depends,
             depends: val.depends,
             files: val.files,
             files: val.files,
+            desktop_template: None,
         }
         }
     }
     }
 }
 }
@@ -522,6 +523,8 @@ impl From<NsisSettings> for tauri_bundler::NsisSettings {
             install_mode: val.install_mode.into(),
             install_mode: val.install_mode.into(),
             languages: val.languages,
             languages: val.languages,
             display_language_selector: val.display_language_selector,
             display_language_selector: val.display_language_selector,
+            custom_language_files: None,
+            template: None,
         }
         }
     }
     }
 }
 }

+ 1 - 1
packages/cli/src/lib.rs

@@ -1,4 +1,4 @@
-pub const DIOXUS_CLI_VERSION: &str = "0.1.5";
+pub const DIOXUS_CLI_VERSION: &str = "0.4.1";
 
 
 pub mod builder;
 pub mod builder;
 pub mod server;
 pub mod server;

+ 64 - 63
packages/cli/src/server/web/mod.rs

@@ -5,7 +5,7 @@ use crate::{
         output::{print_console_info, PrettierOptions, WebServerInfo},
         output::{print_console_info, PrettierOptions, WebServerInfo},
         setup_file_watcher, setup_file_watcher_hot_reload,
         setup_file_watcher, setup_file_watcher_hot_reload,
     },
     },
-    BuildResult, CrateConfig, Result,
+    BuildResult, CrateConfig, Result, WebHttpsConfig,
 };
 };
 use axum::{
 use axum::{
     body::{Full, HttpBody},
     body::{Full, HttpBody},
@@ -73,7 +73,7 @@ pub async fn serve_default(
     config: CrateConfig,
     config: CrateConfig,
     start_browser: bool,
     start_browser: bool,
 ) -> Result<()> {
 ) -> Result<()> {
-    let first_build_result = crate::builder::build(&config, false)?;
+    let first_build_result = crate::builder::build(&config, true)?;
 
 
     log::info!("🚀 Starting development server...");
     log::info!("🚀 Starting development server...");
 
 
@@ -134,7 +134,7 @@ pub async fn serve_hot_reload(
     config: CrateConfig,
     config: CrateConfig,
     start_browser: bool,
     start_browser: bool,
 ) -> Result<()> {
 ) -> Result<()> {
-    let first_build_result = crate::builder::build(&config, false)?;
+    let first_build_result = crate::builder::build(&config, true)?;
 
 
     log::info!("🚀 Starting development server...");
     log::info!("🚀 Starting development server...");
 
 
@@ -218,67 +218,12 @@ async fn get_rustls(config: &CrateConfig) -> Result<Option<RustlsConfig>> {
         return Ok(None);
         return Ok(None);
     }
     }
 
 
-    let (cert_path, key_path) = match web_config.mkcert {
+    let (cert_path, key_path) = if let Some(true) = web_config.mkcert {
         // mkcert, use it
         // mkcert, use it
-        Some(true) => {
-            // Get paths to store certs, otherwise use ssl/item.pem
-            let key_path = web_config
-                .key_path
-                .clone()
-                .unwrap_or(DEFAULT_KEY_PATH.to_string());
-
-            let cert_path = web_config
-                .cert_path
-                .clone()
-                .unwrap_or(DEFAULT_CERT_PATH.to_string());
-
-            // Create ssl directory if using defaults
-            if key_path == DEFAULT_KEY_PATH && cert_path == DEFAULT_CERT_PATH {
-                _ = fs::create_dir("ssl");
-            }
-
-            let cmd = Command::new("mkcert")
-                .args([
-                    "-install",
-                    "-key-file",
-                    &key_path,
-                    "-cert-file",
-                    &cert_path,
-                    "localhost",
-                    "::1",
-                    "127.0.0.1",
-                ])
-                .spawn();
-
-            match cmd {
-                Err(e) => {
-                    match e.kind() {
-                        io::ErrorKind::NotFound => log::error!("mkcert is not installed. See https://github.com/FiloSottile/mkcert#installation for installation instructions."),
-                        e => log::error!("an error occured while generating mkcert certificates: {}", e.to_string()),
-                    };
-                    return Err("failed to generate mkcert certificates".into());
-                }
-                Ok(mut cmd) => {
-                    cmd.wait()?;
-                }
-            }
-
-            (cert_path, key_path)
-        }
-        // not mkcert
-        Some(false) => {
-            // get paths to cert & key
-            if let (Some(key), Some(cert)) =
-                (web_config.key_path.clone(), web_config.cert_path.clone())
-            {
-                (cert, key)
-            } else {
-                // missing cert or key
-                return Err("https is enabled but cert or key path is missing".into());
-            }
-        }
-        // other
-        _ => return Ok(None),
+        get_rustls_with_mkcert(web_config)?
+    } else {
+        // if mkcert not specified or false, don't use it
+        get_rustls_without_mkcert(web_config)?
     };
     };
 
 
     Ok(Some(
     Ok(Some(
@@ -286,6 +231,62 @@ async fn get_rustls(config: &CrateConfig) -> Result<Option<RustlsConfig>> {
     ))
     ))
 }
 }
 
 
+fn get_rustls_with_mkcert(web_config: &WebHttpsConfig) -> Result<(String, String)> {
+    // Get paths to store certs, otherwise use ssl/item.pem
+    let key_path = web_config
+        .key_path
+        .clone()
+        .unwrap_or(DEFAULT_KEY_PATH.to_string());
+
+    let cert_path = web_config
+        .cert_path
+        .clone()
+        .unwrap_or(DEFAULT_CERT_PATH.to_string());
+
+    // Create ssl directory if using defaults
+    if key_path == DEFAULT_KEY_PATH && cert_path == DEFAULT_CERT_PATH {
+        _ = fs::create_dir("ssl");
+    }
+
+    let cmd = Command::new("mkcert")
+        .args([
+            "-install",
+            "-key-file",
+            &key_path,
+            "-cert-file",
+            &cert_path,
+            "localhost",
+            "::1",
+            "127.0.0.1",
+        ])
+        .spawn();
+
+    match cmd {
+        Err(e) => {
+            match e.kind() {
+                io::ErrorKind::NotFound => log::error!("mkcert is not installed. See https://github.com/FiloSottile/mkcert#installation for installation instructions."),
+                e => log::error!("an error occured while generating mkcert certificates: {}", e.to_string()),
+            };
+            return Err("failed to generate mkcert certificates".into());
+        }
+        Ok(mut cmd) => {
+            cmd.wait()?;
+        }
+    }
+
+    Ok((cert_path, key_path))
+}
+
+fn get_rustls_without_mkcert(web_config: &WebHttpsConfig) -> Result<(String, String)> {
+    // get paths to cert & key
+    if let (Some(key), Some(cert)) = (web_config.key_path.clone(), web_config.cert_path.clone()) {
+        Ok((cert, key))
+    } else {
+        // missing cert or key
+        Err("https is enabled but cert or key path is missing".into())
+    }
+}
+
 /// Sets up and returns a router
 /// Sets up and returns a router
 async fn setup_router(
 async fn setup_router(
     config: CrateConfig,
     config: CrateConfig,

+ 1 - 0
packages/core/Cargo.toml

@@ -39,6 +39,7 @@ dioxus = { workspace = true }
 pretty_assertions = "1.3.0"
 pretty_assertions = "1.3.0"
 rand = "0.8.5"
 rand = "0.8.5"
 dioxus-ssr = { workspace = true }
 dioxus-ssr = { workspace = true }
+trybuild = "1.0"
 
 
 [features]
 [features]
 default = []
 default = []

+ 32 - 0
packages/core/compile_tests/props_safety.rs

@@ -0,0 +1,32 @@
+use dioxus::prelude::*;
+
+fn main() {}
+
+fn app(cx: Scope) -> Element {
+    let count: &RefCell<Vec<Element>> = cx.use_hook(|| RefCell::new(Vec::new()));
+
+    render! {
+        unsafe_child_component {
+            borrowed: count
+        }
+    }
+}
+
+#[derive(Props)]
+struct Testing<'a> {
+    borrowed: &'a RefCell<Vec<Element<'a>>>,
+}
+
+fn unsafe_child_component<'a>(cx: Scope<'a, Testing<'a>>) -> Element<'a> {
+    let Testing { borrowed } = cx.props;
+    let borrowed_temporary_data =
+        cx.use_hook(|| String::from("This data is only valid for the lifetime of the child"));
+
+    borrowed
+        .borrow_mut()
+        .push(render! {"{borrowed_temporary_data}"});
+
+    cx.render(rsx! {
+        div { "Hello, world!" }
+    })
+}

+ 20 - 0
packages/core/compile_tests/props_safety.stderr

@@ -0,0 +1,20 @@
+error[E0521]: borrowed data escapes outside of function
+  --> compile_tests/props_safety.rs:8:5
+   |
+5  |   fn app(cx: Scope) -> Element {
+   |          --
+   |          |
+   |          `cx` is a reference that is only valid in the function body
+   |          has type `&'1 Scoped<'1>`
+...
+8  | /     render! {
+9  | |         unsafe_child_component {
+10 | |             borrowed: count
+11 | |         }
+12 | |     }
+   | |     ^
+   | |     |
+   | |_____`cx` escapes the function body here
+   |       argument requires that `'1` must outlive `'static`
+   |
+   = note: this error originates in the macro `render` (in Nightly builds, run with -Z macro-backtrace for more info)

+ 24 - 0
packages/core/compile_tests/props_safety_temporary_values.rs

@@ -0,0 +1,24 @@
+use dioxus::prelude::*;
+
+fn main() {}
+
+fn app(cx: Scope) -> Element {
+    let count = vec![1, 2, 3];
+
+    render! {
+        unsafe_child_component {
+            borrowed: &count
+        }
+    }
+}
+
+#[derive(Props)]
+struct Testing<'a> {
+    borrowed: &'a Vec<u32>,
+}
+
+fn unsafe_child_component<'a>(cx: Scope<'a, Testing<'a>>) -> Element<'a> {
+    cx.render(rsx! {
+        div { "{cx.props.borrowed:?}" }
+    })
+}

+ 12 - 0
packages/core/compile_tests/props_safety_temporary_values.stderr

@@ -0,0 +1,12 @@
+error[E0515]: cannot return value referencing local variable `count`
+  --> compile_tests/props_safety_temporary_values.rs:8:5
+   |
+8  | /     render! {
+9  | |         unsafe_child_component {
+10 | |             borrowed: &count
+   | |                       ------ `count` is borrowed here
+11 | |         }
+12 | |     }
+   | |_____^ returns a value referencing data owned by the current function
+   |
+   = note: this error originates in the macro `render` (in Nightly builds, run with -Z macro-backtrace for more info)

+ 13 - 4
packages/core/src/arena.rs

@@ -19,6 +19,9 @@ pub(crate) struct ElementRef {
 
 
     // The actual template
     // The actual template
     pub template: Option<NonNull<VNode<'static>>>,
     pub template: Option<NonNull<VNode<'static>>>,
+
+    // The scope the element belongs to
+    pub scope: ScopeId,
 }
 }
 
 
 #[derive(Clone, Copy, Debug)]
 #[derive(Clone, Copy, Debug)]
@@ -32,6 +35,7 @@ impl ElementRef {
         Self {
         Self {
             template: None,
             template: None,
             path: ElementPath::Root(0),
             path: ElementPath::Root(0),
+            scope: ScopeId::ROOT,
         }
         }
     }
     }
 }
 }
@@ -56,11 +60,13 @@ impl VirtualDom {
     fn next_reference(&mut self, template: &VNode, path: ElementPath) -> ElementId {
     fn next_reference(&mut self, template: &VNode, path: ElementPath) -> ElementId {
         let entry = self.elements.vacant_entry();
         let entry = self.elements.vacant_entry();
         let id = entry.key();
         let id = entry.key();
+        let scope = self.runtime.current_scope_id().unwrap_or(ScopeId::ROOT);
 
 
         entry.insert(ElementRef {
         entry.insert(ElementRef {
             // We know this is non-null because it comes from a reference
             // We know this is non-null because it comes from a reference
             template: Some(unsafe { NonNull::new_unchecked(template as *const _ as *mut _) }),
             template: Some(unsafe { NonNull::new_unchecked(template as *const _ as *mut _) }),
             path,
             path,
+            scope,
         });
         });
         ElementId(id)
         ElementId(id)
     }
     }
@@ -91,7 +97,7 @@ impl VirtualDom {
     // Note: This will not remove any ids from the arena
     // Note: This will not remove any ids from the arena
     pub(crate) fn drop_scope(&mut self, id: ScopeId, recursive: bool) {
     pub(crate) fn drop_scope(&mut self, id: ScopeId, recursive: bool) {
         self.dirty_scopes.remove(&DirtyScope {
         self.dirty_scopes.remove(&DirtyScope {
-            height: self.scopes[id.0].height,
+            height: self.scopes[id.0].height(),
             id,
             id,
         });
         });
 
 
@@ -110,10 +116,13 @@ impl VirtualDom {
         // Drop all the hooks once the children are dropped
         // Drop all the hooks once the children are dropped
         // this means we'll drop hooks bottom-up
         // this means we'll drop hooks bottom-up
         scope.hooks.get_mut().clear();
         scope.hooks.get_mut().clear();
+        {
+            let context = scope.context();
 
 
-        // Drop all the futures once the hooks are dropped
-        for task_id in scope.spawned_tasks.borrow_mut().drain() {
-            scope.tasks.remove(task_id);
+            // Drop all the futures once the hooks are dropped
+            for task_id in context.spawned_tasks.borrow_mut().drain() {
+                context.tasks.remove(task_id);
+            }
         }
         }
 
 
         self.scopes.remove(id.0);
         self.scopes.remove(id.0);

+ 11 - 7
packages/core/src/create.rs

@@ -66,10 +66,10 @@ impl<'b> VirtualDom {
     ///
     ///
     /// This method pushes the ScopeID to the internal scopestack and returns the number of nodes created.
     /// This method pushes the ScopeID to the internal scopestack and returns the number of nodes created.
     pub(crate) fn create_scope(&mut self, scope: ScopeId, template: &'b VNode<'b>) -> usize {
     pub(crate) fn create_scope(&mut self, scope: ScopeId, template: &'b VNode<'b>) -> usize {
-        self.scope_stack.push(scope);
-        let out = self.create(template);
-        self.scope_stack.pop();
-        out
+        self.runtime.scope_stack.borrow_mut().push(scope);
+        let nodes = self.create(template);
+        self.runtime.scope_stack.borrow_mut().pop();
+        nodes
     }
     }
 
 
     /// Create this template and write its mutations
     /// Create this template and write its mutations
@@ -87,8 +87,12 @@ impl<'b> VirtualDom {
             }
             }
         }
         }
 
 
-        // Intialize the root nodes slice
-        *node.root_ids.borrow_mut() = vec![ElementId(0); node.template.get().roots.len()];
+        // Initialize the root nodes slice
+        {
+            let mut nodes_mut = node.root_ids.borrow_mut();
+            let len = node.template.get().roots.len();
+            nodes_mut.resize(len, ElementId::default());
+        };
 
 
         // The best renderers will have templates prehydrated and registered
         // The best renderers will have templates prehydrated and registered
         // Just in case, let's create the template using instructions anyways
         // Just in case, let's create the template using instructions anyways
@@ -522,7 +526,7 @@ impl<'b> VirtualDom {
             .take()
             .take()
             .map(|props| {
             .map(|props| {
                 let unbounded_props: Box<dyn AnyProps> = unsafe { std::mem::transmute(props) };
                 let unbounded_props: Box<dyn AnyProps> = unsafe { std::mem::transmute(props) };
-                self.new_scope(unbounded_props, component.name).id
+                self.new_scope(unbounded_props, component.name).context().id
             })
             })
             .unwrap_or_else(|| component.scope.get().unwrap())
             .unwrap_or_else(|| component.scope.get().unwrap())
     }
     }

+ 35 - 10
packages/core/src/diff.rs

@@ -15,9 +15,8 @@ use DynamicNode::*;
 
 
 impl<'b> VirtualDom {
 impl<'b> VirtualDom {
     pub(super) fn diff_scope(&mut self, scope: ScopeId) {
     pub(super) fn diff_scope(&mut self, scope: ScopeId) {
-        let scope_state = &mut self.scopes[scope.0];
-
-        self.scope_stack.push(scope);
+        self.runtime.scope_stack.borrow_mut().push(scope);
+        let scope_state = &mut self.get_scope(scope).unwrap();
         unsafe {
         unsafe {
             // Load the old and new bump arenas
             // Load the old and new bump arenas
             let old = scope_state
             let old = scope_state
@@ -47,7 +46,7 @@ impl<'b> VirtualDom {
                 (Aborted(l), Ready(r)) => self.replace_placeholder(l, [r]),
                 (Aborted(l), Ready(r)) => self.replace_placeholder(l, [r]),
             };
             };
         }
         }
-        self.scope_stack.pop();
+        self.runtime.scope_stack.borrow_mut().pop();
     }
     }
 
 
     fn diff_ok_to_err(&mut self, l: &'b VNode<'b>, p: &'b VPlaceholder) {
     fn diff_ok_to_err(&mut self, l: &'b VNode<'b>, p: &'b VPlaceholder) {
@@ -129,7 +128,13 @@ impl<'b> VirtualDom {
             });
             });
 
 
         // Make sure the roots get transferred over while we're here
         // Make sure the roots get transferred over while we're here
-        *right_template.root_ids.borrow_mut() = left_template.root_ids.borrow().clone();
+        {
+            let mut right = right_template.root_ids.borrow_mut();
+            right.clear();
+            for &element in left_template.root_ids.borrow().iter() {
+                right.push(element);
+            }
+        }
 
 
         let root_ids = right_template.root_ids.borrow();
         let root_ids = right_template.root_ids.borrow();
 
 
@@ -210,7 +215,7 @@ impl<'b> VirtualDom {
         self.diff_scope(scope_id);
         self.diff_scope(scope_id);
 
 
         self.dirty_scopes.remove(&DirtyScope {
         self.dirty_scopes.remove(&DirtyScope {
-            height: self.scopes[scope_id.0].height,
+            height: self.runtime.get_context(scope_id).unwrap().height,
             id: scope_id,
             id: scope_id,
         });
         });
     }
     }
@@ -714,7 +719,12 @@ impl<'b> VirtualDom {
 
 
                     Component(comp) => {
                     Component(comp) => {
                         let scope = comp.scope.get().unwrap();
                         let scope = comp.scope.get().unwrap();
-                        match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
+                        match unsafe {
+                            self.get_scope(scope)
+                                .unwrap()
+                                .root_node()
+                                .extend_lifetime_ref()
+                        } {
                             RenderReturn::Ready(node) => self.push_all_real_nodes(node),
                             RenderReturn::Ready(node) => self.push_all_real_nodes(node),
                             RenderReturn::Aborted(_node) => todo!(),
                             RenderReturn::Aborted(_node) => todo!(),
                         }
                         }
@@ -915,7 +925,12 @@ impl<'b> VirtualDom {
             .expect("VComponents to always have a scope");
             .expect("VComponents to always have a scope");
 
 
         // Remove the component from the dom
         // Remove the component from the dom
-        match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
+        match unsafe {
+            self.get_scope(scope)
+                .unwrap()
+                .root_node()
+                .extend_lifetime_ref()
+        } {
             RenderReturn::Ready(t) => self.remove_node(t, gen_muts),
             RenderReturn::Ready(t) => self.remove_node(t, gen_muts),
             RenderReturn::Aborted(placeholder) => self.remove_placeholder(placeholder, gen_muts),
             RenderReturn::Aborted(placeholder) => self.remove_placeholder(placeholder, gen_muts),
         };
         };
@@ -936,7 +951,12 @@ impl<'b> VirtualDom {
             Some(Placeholder(t)) => t.id.get().unwrap(),
             Some(Placeholder(t)) => t.id.get().unwrap(),
             Some(Component(comp)) => {
             Some(Component(comp)) => {
                 let scope = comp.scope.get().unwrap();
                 let scope = comp.scope.get().unwrap();
-                match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
+                match unsafe {
+                    self.get_scope(scope)
+                        .unwrap()
+                        .root_node()
+                        .extend_lifetime_ref()
+                } {
                     RenderReturn::Ready(t) => self.find_first_element(t),
                     RenderReturn::Ready(t) => self.find_first_element(t),
                     _ => todo!("cannot handle nonstandard nodes"),
                     _ => todo!("cannot handle nonstandard nodes"),
                 }
                 }
@@ -952,7 +972,12 @@ impl<'b> VirtualDom {
             Some(Placeholder(t)) => t.id.get().unwrap(),
             Some(Placeholder(t)) => t.id.get().unwrap(),
             Some(Component(comp)) => {
             Some(Component(comp)) => {
                 let scope = comp.scope.get().unwrap();
                 let scope = comp.scope.get().unwrap();
-                match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
+                match unsafe {
+                    self.get_scope(scope)
+                        .unwrap()
+                        .root_node()
+                        .extend_lifetime_ref()
+                } {
                     RenderReturn::Ready(t) => self.find_last_element(t),
                     RenderReturn::Ready(t) => self.find_last_element(t),
                     _ => todo!("cannot handle nonstandard nodes"),
                     _ => todo!("cannot handle nonstandard nodes"),
                 }
                 }

+ 9 - 0
packages/core/src/events.rs

@@ -1,3 +1,4 @@
+use crate::{runtime::with_runtime, ScopeId};
 use std::{
 use std::{
     cell::{Cell, RefCell},
     cell::{Cell, RefCell},
     rc::Rc,
     rc::Rc,
@@ -135,12 +136,14 @@ impl<T: std::fmt::Debug> std::fmt::Debug for Event<T> {
 ///
 ///
 /// ```
 /// ```
 pub struct EventHandler<'bump, T = ()> {
 pub struct EventHandler<'bump, T = ()> {
+    pub(crate) origin: ScopeId,
     pub(super) callback: RefCell<Option<ExternalListenerCallback<'bump, T>>>,
     pub(super) callback: RefCell<Option<ExternalListenerCallback<'bump, T>>>,
 }
 }
 
 
 impl<T> Default for EventHandler<'_, T> {
 impl<T> Default for EventHandler<'_, T> {
     fn default() -> Self {
     fn default() -> Self {
         Self {
         Self {
+            origin: ScopeId::ROOT,
             callback: Default::default(),
             callback: Default::default(),
         }
         }
     }
     }
@@ -154,7 +157,13 @@ impl<T> EventHandler<'_, T> {
     /// This borrows the event using a RefCell. Recursively calling a listener will cause a panic.
     /// This borrows the event using a RefCell. Recursively calling a listener will cause a panic.
     pub fn call(&self, event: T) {
     pub fn call(&self, event: T) {
         if let Some(callback) = self.callback.borrow_mut().as_mut() {
         if let Some(callback) = self.callback.borrow_mut().as_mut() {
+            with_runtime(|rt| {
+                rt.scope_stack.borrow_mut().push(self.origin);
+            });
             callback(event);
             callback(event);
+            with_runtime(|rt| {
+                rt.scope_stack.borrow_mut().pop();
+            });
         }
         }
     }
     }
 
 

+ 14 - 7
packages/core/src/lib.rs

@@ -14,8 +14,10 @@ mod lazynodes;
 mod mutations;
 mod mutations;
 mod nodes;
 mod nodes;
 mod properties;
 mod properties;
+mod runtime;
 mod scheduler;
 mod scheduler;
 mod scope_arena;
 mod scope_arena;
+mod scope_context;
 mod scopes;
 mod scopes;
 mod virtual_dom;
 mod virtual_dom;
 
 
@@ -31,6 +33,7 @@ pub(crate) mod innerlude {
     pub use crate::nodes::*;
     pub use crate::nodes::*;
     pub use crate::properties::*;
     pub use crate::properties::*;
     pub use crate::scheduler::*;
     pub use crate::scheduler::*;
+    pub use crate::scope_context::*;
     pub use crate::scopes::*;
     pub use crate::scopes::*;
     pub use crate::virtual_dom::*;
     pub use crate::virtual_dom::*;
 
 
@@ -70,10 +73,11 @@ pub(crate) mod innerlude {
 }
 }
 
 
 pub use crate::innerlude::{
 pub use crate::innerlude::{
-    fc_to_builder, AnyValue, Attribute, AttributeValue, BorrowedAttributeValue, CapturedError,
-    Component, DynamicNode, Element, ElementId, Event, Fragment, IntoDynNode, LazyNodes, Mutation,
-    Mutations, Properties, RenderReturn, Scope, ScopeId, ScopeState, Scoped, TaskId, Template,
-    TemplateAttribute, TemplateNode, VComponent, VNode, VPlaceholder, VText, VirtualDom,
+    fc_to_builder, vdom_is_rendering, AnyValue, Attribute, AttributeValue, BorrowedAttributeValue,
+    CapturedError, Component, DynamicNode, Element, ElementId, Event, Fragment, IntoDynNode,
+    LazyNodes, Mutation, Mutations, Properties, RenderReturn, Scope, ScopeId, ScopeState, Scoped,
+    TaskId, Template, TemplateAttribute, TemplateNode, VComponent, VNode, VPlaceholder, VText,
+    VirtualDom,
 };
 };
 
 
 /// The purpose of this module is to alleviate imports of many common types
 /// The purpose of this module is to alleviate imports of many common types
@@ -81,9 +85,12 @@ pub use crate::innerlude::{
 /// This includes types like [`Scope`], [`Element`], and [`Component`].
 /// This includes types like [`Scope`], [`Element`], and [`Component`].
 pub mod prelude {
 pub mod prelude {
     pub use crate::innerlude::{
     pub use crate::innerlude::{
-        fc_to_builder, AnyValue, Component, Element, Event, EventHandler, Fragment,
-        IntoAttributeValue, LazyNodes, Properties, Scope, ScopeId, ScopeState, Scoped, TaskId,
-        Template, TemplateAttribute, TemplateNode, Throw, VNode, VirtualDom,
+        consume_context, consume_context_from_scope, current_scope_id, fc_to_builder, has_context,
+        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, Scope, ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute,
+        TemplateNode, Throw, VNode, VirtualDom,
     };
     };
 }
 }
 
 

+ 9 - 9
packages/core/src/nodes.rs

@@ -54,7 +54,7 @@ pub struct VNode<'a> {
 
 
     /// The IDs for the roots of this template - to be used when moving the template around and removing it from
     /// The IDs for the roots of this template - to be used when moving the template around and removing it from
     /// the actual Dom
     /// the actual Dom
-    pub root_ids: RefCell<Vec<ElementId>>,
+    pub root_ids: RefCell<bumpalo::collections::Vec<'a, ElementId>>,
 
 
     /// The dynamic parts of the template
     /// The dynamic parts of the template
     pub dynamic_nodes: &'a [DynamicNode<'a>],
     pub dynamic_nodes: &'a [DynamicNode<'a>],
@@ -65,11 +65,11 @@ pub struct VNode<'a> {
 
 
 impl<'a> VNode<'a> {
 impl<'a> VNode<'a> {
     /// Create a template with no nodes that will be skipped over during diffing
     /// Create a template with no nodes that will be skipped over during diffing
-    pub fn empty() -> Element<'a> {
+    pub fn empty(cx: &'a ScopeState) -> Element<'a> {
         Some(VNode {
         Some(VNode {
             key: None,
             key: None,
             parent: None,
             parent: None,
-            root_ids: Default::default(),
+            root_ids: RefCell::new(bumpalo::collections::Vec::new_in(cx.bump())),
             dynamic_nodes: &[],
             dynamic_nodes: &[],
             dynamic_attrs: &[],
             dynamic_attrs: &[],
             template: Cell::new(Template {
             template: Cell::new(Template {
@@ -691,14 +691,14 @@ impl<'a> IntoDynNode<'a> for &Element<'a> {
 
 
 impl<'a, 'b> IntoDynNode<'a> for LazyNodes<'a, 'b> {
 impl<'a, 'b> IntoDynNode<'a> for LazyNodes<'a, 'b> {
     fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> {
     fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> {
-        DynamicNode::Fragment(cx.bump().alloc([self.call(cx)]))
+        DynamicNode::Fragment(cx.bump().alloc([cx.render(self).unwrap()]))
     }
     }
 }
 }
 
 
 impl<'a, 'b> IntoDynNode<'b> for &'a str {
 impl<'a, 'b> IntoDynNode<'b> for &'a str {
     fn into_vnode(self, cx: &'b ScopeState) -> DynamicNode<'b> {
     fn into_vnode(self, cx: &'b ScopeState) -> DynamicNode<'b> {
         DynamicNode::Text(VText {
         DynamicNode::Text(VText {
-            value: bumpalo::collections::String::from_str_in(self, cx.bump()).into_bump_str(),
+            value: cx.bump().alloc_str(self),
             id: Default::default(),
             id: Default::default(),
         })
         })
     }
     }
@@ -741,16 +741,16 @@ impl<'a> IntoTemplate<'a> for VNode<'a> {
     }
     }
 }
 }
 impl<'a> IntoTemplate<'a> for Element<'a> {
 impl<'a> IntoTemplate<'a> for Element<'a> {
-    fn into_template(self, _cx: &'a ScopeState) -> VNode<'a> {
+    fn into_template(self, cx: &'a ScopeState) -> VNode<'a> {
         match self {
         match self {
-            Some(val) => val.into_template(_cx),
-            _ => VNode::empty().unwrap(),
+            Some(val) => val.into_template(cx),
+            _ => VNode::empty(cx).unwrap(),
         }
         }
     }
     }
 }
 }
 impl<'a, 'b> IntoTemplate<'a> for LazyNodes<'a, 'b> {
 impl<'a, 'b> IntoTemplate<'a> for LazyNodes<'a, 'b> {
     fn into_template(self, cx: &'a ScopeState) -> VNode<'a> {
     fn into_template(self, cx: &'a ScopeState) -> VNode<'a> {
-        self.call(cx)
+        cx.render(self).unwrap()
     }
     }
 }
 }
 
 

+ 8 - 0
packages/core/src/properties.rs

@@ -73,3 +73,11 @@ impl EmptyBuilder {
 pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element<'a>) -> T::Builder {
 pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element<'a>) -> T::Builder {
     T::builder()
     T::builder()
 }
 }
+
+#[cfg(not(miri))]
+#[test]
+fn unsafe_props_fail() {
+    let t = trybuild::TestCases::new();
+    t.compile_fail("compile_tests/props_safety.rs");
+    t.compile_fail("compile_tests/props_safety_temporary_values.rs");
+}

+ 108 - 0
packages/core/src/runtime.rs

@@ -0,0 +1,108 @@
+use std::cell::{Cell, Ref, RefCell};
+
+use crate::{innerlude::Scheduler, scope_context::ScopeContext, scopes::ScopeId};
+use std::rc::Rc;
+
+thread_local! {
+    static RUNTIMES: RefCell<Vec<Rc<Runtime>>> = RefCell::new(vec![]);
+}
+
+/// Pushes a new scope onto the stack
+pub(crate) fn push_runtime(runtime: Rc<Runtime>) {
+    RUNTIMES.with(|stack| stack.borrow_mut().push(runtime));
+}
+
+/// Pops a scope off the stack
+pub(crate) fn pop_runtime() {
+    RUNTIMES.with(|stack| stack.borrow_mut().pop());
+}
+
+/// Runs a function with the current runtime
+pub(crate) fn with_runtime<F, R>(f: F) -> Option<R>
+where
+    F: FnOnce(&Runtime) -> R,
+{
+    RUNTIMES.with(|stack| {
+        let stack = stack.borrow();
+        stack.last().map(|r| f(r))
+    })
+}
+
+/// Runs a function with the current scope
+pub(crate) fn with_current_scope<F, R>(f: F) -> Option<R>
+where
+    F: FnOnce(&ScopeContext) -> R,
+{
+    with_runtime(|runtime| {
+        runtime
+            .current_scope_id()
+            .and_then(|scope| runtime.get_context(scope).map(|sc| f(&sc)))
+    })
+    .flatten()
+}
+
+pub(crate) struct Runtime {
+    pub(crate) scope_contexts: RefCell<Vec<Option<ScopeContext>>>,
+    pub(crate) scheduler: Rc<Scheduler>,
+
+    // We use this to track the current scope
+    pub(crate) scope_stack: RefCell<Vec<ScopeId>>,
+    pub(crate) rendering: Cell<bool>,
+}
+
+impl Runtime {
+    pub(crate) fn new(scheduler: Rc<Scheduler>) -> Rc<Self> {
+        Rc::new(Self {
+            scheduler,
+
+            scope_contexts: Default::default(),
+
+            scope_stack: Default::default(),
+
+            rendering: Cell::new(true),
+        })
+    }
+
+    /// Create a scope context. This slab is synchronized with the scope slab.
+    pub(crate) fn create_context_at(&self, id: ScopeId, context: ScopeContext) {
+        let mut contexts = self.scope_contexts.borrow_mut();
+        if contexts.len() <= id.0 {
+            contexts.resize_with(id.0 + 1, Default::default);
+        }
+        contexts[id.0] = Some(context);
+    }
+
+    pub(crate) fn remove_context(&self, id: ScopeId) {
+        self.scope_contexts.borrow_mut()[id.0] = None;
+    }
+
+    /// Get the current scope id
+    pub fn current_scope_id(&self) -> Option<ScopeId> {
+        self.scope_stack.borrow().last().copied()
+    }
+
+    /// Get the context for any scope given its ID
+    ///
+    /// This is useful for inserting or removing contexts from a scope, or rendering out its root node
+    pub fn get_context(&self, id: ScopeId) -> Option<Ref<'_, ScopeContext>> {
+        Ref::filter_map(self.scope_contexts.borrow(), |contexts| {
+            contexts.get(id.0).and_then(|f| f.as_ref())
+        })
+        .ok()
+    }
+}
+
+pub(crate) struct RuntimeGuard(Rc<Runtime>);
+
+impl RuntimeGuard {
+    pub(crate) fn new(runtime: Rc<Runtime>) -> Self {
+        push_runtime(runtime.clone());
+        Self(runtime)
+    }
+}
+
+impl Drop for RuntimeGuard {
+    fn drop(&mut self) {
+        pop_runtime();
+    }
+}

+ 13 - 4
packages/core/src/scheduler/wait.rs

@@ -1,4 +1,4 @@
-use crate::{TaskId, VirtualDom};
+use crate::{runtime::RuntimeGuard, TaskId, VirtualDom};
 use std::task::Context;
 use std::task::Context;
 
 
 impl VirtualDom {
 impl VirtualDom {
@@ -7,7 +7,8 @@ impl VirtualDom {
     /// This is precise, meaning we won't poll every task, just tasks that have woken up as notified to use by the
     /// This is precise, meaning we won't poll every task, just tasks that have woken up as notified to use by the
     /// queue
     /// queue
     pub(crate) fn handle_task_wakeup(&mut self, id: TaskId) {
     pub(crate) fn handle_task_wakeup(&mut self, id: TaskId) {
-        let mut tasks = self.scheduler.tasks.borrow_mut();
+        let _runtime = RuntimeGuard::new(self.runtime.clone());
+        let mut tasks = self.runtime.scheduler.tasks.borrow_mut();
 
 
         let task = match tasks.get(id.0) {
         let task = match tasks.get(id.0) {
             Some(task) => task,
             Some(task) => task,
@@ -17,14 +18,22 @@ impl VirtualDom {
 
 
         let mut cx = Context::from_waker(&task.waker);
         let mut cx = Context::from_waker(&task.waker);
 
 
+        // update the scope stack
+        self.runtime.scope_stack.borrow_mut().push(task.scope);
+        self.runtime.rendering.set(false);
+
         // If the task completes...
         // If the task completes...
         if task.task.borrow_mut().as_mut().poll(&mut cx).is_ready() {
         if task.task.borrow_mut().as_mut().poll(&mut cx).is_ready() {
             // Remove it from the scope so we dont try to double drop it when the scope dropes
             // Remove it from the scope so we dont try to double drop it when the scope dropes
-            let scope = &self.scopes[task.scope.0];
-            scope.spawned_tasks.borrow_mut().remove(&id);
+            let scope = &self.get_scope(task.scope).unwrap();
+            scope.context().spawned_tasks.borrow_mut().remove(&id);
 
 
             // Remove it from the scheduler
             // Remove it from the scheduler
             tasks.try_remove(id.0);
             tasks.try_remove(id.0);
         }
         }
+
+        // Remove the scope from the stack
+        self.runtime.scope_stack.borrow_mut().pop();
+        self.runtime.rendering.set(true);
     }
     }
 }
 }

+ 33 - 26
packages/core/src/scope_arena.rs

@@ -3,6 +3,7 @@ use crate::{
     bump_frame::BumpFrame,
     bump_frame::BumpFrame,
     innerlude::DirtyScope,
     innerlude::DirtyScope,
     nodes::RenderReturn,
     nodes::RenderReturn,
+    scope_context::ScopeContext,
     scopes::{ScopeId, ScopeState},
     scopes::{ScopeId, ScopeState},
     virtual_dom::VirtualDom,
     virtual_dom::VirtualDom,
 };
 };
@@ -13,48 +14,49 @@ impl VirtualDom {
         props: Box<dyn AnyProps<'static>>,
         props: Box<dyn AnyProps<'static>>,
         name: &'static str,
         name: &'static str,
     ) -> &ScopeState {
     ) -> &ScopeState {
-        let parent = self.acquire_current_scope_raw();
+        let parent_id = self.runtime.current_scope_id();
+        let height = parent_id
+            .and_then(|parent_id| self.get_scope(parent_id).map(|f| f.context().height + 1))
+            .unwrap_or(0);
         let entry = self.scopes.vacant_entry();
         let entry = self.scopes.vacant_entry();
-        let height = unsafe { parent.map(|f| (*f).height + 1).unwrap_or(0) };
         let id = ScopeId(entry.key());
         let id = ScopeId(entry.key());
 
 
-        entry.insert(Box::new(ScopeState {
-            parent,
-            id,
-            height,
-            name,
+        let scope = entry.insert(Box::new(ScopeState {
+            runtime: self.runtime.clone(),
+            context_id: id,
+
             props: Some(props),
             props: Some(props),
-            tasks: self.scheduler.clone(),
+
             node_arena_1: BumpFrame::new(0),
             node_arena_1: BumpFrame::new(0),
             node_arena_2: BumpFrame::new(0),
             node_arena_2: BumpFrame::new(0),
-            spawned_tasks: Default::default(),
-            suspended: Default::default(),
+
             render_cnt: Default::default(),
             render_cnt: Default::default(),
             hooks: Default::default(),
             hooks: Default::default(),
             hook_idx: Default::default(),
             hook_idx: Default::default(),
-            shared_contexts: Default::default(),
+
             borrowed_props: Default::default(),
             borrowed_props: Default::default(),
             attributes_to_drop: Default::default(),
             attributes_to_drop: Default::default(),
-        }))
-    }
+        }));
 
 
-    fn acquire_current_scope_raw(&self) -> Option<*const ScopeState> {
-        let id = self.scope_stack.last().copied()?;
-        let scope = self.scopes.get(id.0)?;
-        Some(scope.as_ref())
+        let context =
+            ScopeContext::new(name, id, parent_id, height, self.runtime.scheduler.clone());
+        self.runtime.create_context_at(id, context);
+
+        scope
     }
     }
 
 
     pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn {
     pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn {
+        self.runtime.scope_stack.borrow_mut().push(scope_id);
         // Cycle to the next frame and then reset it
         // Cycle to the next frame and then reset it
         // This breaks any latent references, invalidating every pointer referencing into it.
         // This breaks any latent references, invalidating every pointer referencing into it.
         // Remove all the outdated listeners
         // Remove all the outdated listeners
         self.ensure_drop_safety(scope_id);
         self.ensure_drop_safety(scope_id);
 
 
         let new_nodes = unsafe {
         let new_nodes = unsafe {
-            self.scopes[scope_id.0].previous_frame().bump_mut().reset();
-
             let scope = &self.scopes[scope_id.0];
             let scope = &self.scopes[scope_id.0];
-            scope.suspended.set(false);
+            scope.previous_frame().bump_mut().reset();
+
+            scope.context().suspended.set(false);
 
 
             scope.hook_idx.set(0);
             scope.hook_idx.set(0);
 
 
@@ -77,21 +79,26 @@ impl VirtualDom {
         // And move the render generation forward by one
         // And move the render generation forward by one
         scope.render_cnt.set(scope.render_cnt.get() + 1);
         scope.render_cnt.set(scope.render_cnt.get() + 1);
 
 
+        let context = scope.context();
         // remove this scope from dirty scopes
         // remove this scope from dirty scopes
         self.dirty_scopes.remove(&DirtyScope {
         self.dirty_scopes.remove(&DirtyScope {
-            height: scope.height,
-            id: scope.id,
+            height: context.height,
+            id: context.id,
         });
         });
 
 
-        if scope.suspended.get() {
+        if context.suspended.get() {
             if matches!(allocated, RenderReturn::Aborted(_)) {
             if matches!(allocated, RenderReturn::Aborted(_)) {
-                self.suspended_scopes.insert(scope.id);
+                self.suspended_scopes.insert(context.id);
             }
             }
         } else if !self.suspended_scopes.is_empty() {
         } else if !self.suspended_scopes.is_empty() {
-            _ = self.suspended_scopes.remove(&scope.id);
+            _ = self.suspended_scopes.remove(&context.id);
         }
         }
 
 
         // rebind the lifetime now that its stored internally
         // rebind the lifetime now that its stored internally
-        unsafe { allocated.extend_lifetime_ref() }
+        let result = unsafe { allocated.extend_lifetime_ref() };
+
+        self.runtime.scope_stack.borrow_mut().pop();
+
+        result
     }
     }
 }
 }

+ 335 - 0
packages/core/src/scope_context.rs

@@ -0,0 +1,335 @@
+use crate::{
+    innerlude::{ErrorBoundary, Scheduler, SchedulerMsg},
+    runtime::{with_current_scope, with_runtime},
+    Element, ScopeId, TaskId,
+};
+use rustc_hash::FxHashSet;
+use std::{
+    any::Any,
+    cell::{Cell, RefCell},
+    fmt::Debug,
+    future::Future,
+    rc::Rc,
+    sync::Arc,
+};
+
+/// A component's state separate from its props.
+///
+/// This struct exists to provide a common interface for all scopes without relying on generics.
+pub(crate) struct ScopeContext {
+    pub(crate) name: &'static str,
+
+    pub(crate) id: ScopeId,
+    pub(crate) parent_id: Option<ScopeId>,
+
+    pub(crate) height: u32,
+    pub(crate) suspended: Cell<bool>,
+
+    pub(crate) shared_contexts: RefCell<Vec<Box<dyn Any>>>,
+
+    pub(crate) tasks: Rc<Scheduler>,
+    pub(crate) spawned_tasks: RefCell<FxHashSet<TaskId>>,
+}
+
+impl ScopeContext {
+    pub(crate) fn new(
+        name: &'static str,
+        id: ScopeId,
+        parent_id: Option<ScopeId>,
+        height: u32,
+        tasks: Rc<Scheduler>,
+    ) -> Self {
+        Self {
+            name,
+            id,
+            parent_id,
+            height,
+            suspended: Cell::new(false),
+            shared_contexts: RefCell::new(vec![]),
+            tasks,
+            spawned_tasks: RefCell::new(FxHashSet::default()),
+        }
+    }
+
+    pub fn parent_id(&self) -> Option<ScopeId> {
+        self.parent_id
+    }
+
+    pub fn scope_id(&self) -> ScopeId {
+        self.id
+    }
+
+    /// Create a subscription that schedules a future render for the reference component
+    ///
+    /// ## Notice: you should prefer using [`Self::schedule_update_any`] and [`Self::scope_id`]
+    pub fn schedule_update(&self) -> Arc<dyn Fn() + Send + Sync + 'static> {
+        let (chan, id) = (self.tasks.sender.clone(), self.scope_id());
+        Arc::new(move || drop(chan.unbounded_send(SchedulerMsg::Immediate(id))))
+    }
+
+    /// Schedule an update for any component given its [`ScopeId`].
+    ///
+    /// A component's [`ScopeId`] can be obtained from `use_hook` or the [`ScopeState::scope_id`] method.
+    ///
+    /// This method should be used when you want to schedule an update for a component
+    pub fn schedule_update_any(&self) -> Arc<dyn Fn(ScopeId) + Send + Sync> {
+        let chan = self.tasks.sender.clone();
+        Arc::new(move |id| {
+            chan.unbounded_send(SchedulerMsg::Immediate(id)).unwrap();
+        })
+    }
+
+    /// Mark this scope as dirty, and schedule a render for it.
+    pub fn needs_update(&self) {
+        self.needs_update_any(self.scope_id());
+    }
+
+    /// Get the [`ScopeId`] of a mounted component.
+    ///
+    /// `ScopeId` is not unique for the lifetime of the [`crate::VirtualDom`] - a [`ScopeId`] will be reused if a component is unmounted.
+    pub fn needs_update_any(&self, id: ScopeId) {
+        self.tasks
+            .sender
+            .unbounded_send(SchedulerMsg::Immediate(id))
+            .expect("Scheduler to exist if scope exists");
+    }
+
+    /// Return any context of type T if it exists on this scope
+    pub fn has_context<T: 'static + Clone>(&self) -> Option<T> {
+        self.shared_contexts
+            .borrow()
+            .iter()
+            .find_map(|any| any.downcast_ref::<T>())
+            .cloned()
+    }
+
+    /// Try to retrieve a shared state with type `T` from any parent scope.
+    ///
+    /// Clones the state if it exists.
+    pub fn consume_context<T: 'static + Clone>(&self) -> Option<T> {
+        if let Some(this_ctx) = self.has_context() {
+            return Some(this_ctx);
+        }
+
+        let mut search_parent = self.parent_id;
+        with_runtime(|runtime| {
+            while let Some(parent_id) = search_parent {
+                let parent = runtime.get_context(parent_id).unwrap();
+                if let Some(shared) = parent
+                    .shared_contexts
+                    .borrow()
+                    .iter()
+                    .find_map(|any| any.downcast_ref::<T>())
+                {
+                    return Some(shared.clone());
+                }
+                search_parent = parent.parent_id;
+            }
+            None
+        })
+        .flatten()
+    }
+
+    /// Expose state to children further down the [`crate::VirtualDom`] Tree. Requires `Clone` on the context to allow getting values down the tree.
+    ///
+    /// This is a "fundamental" operation and should only be called during initialization of a hook.
+    ///
+    /// For a hook that provides the same functionality, use `use_provide_context` and `use_context` instead.
+    ///
+    /// # Example
+    ///
+    /// ```rust, ignore
+    /// struct SharedState(&'static str);
+    ///
+    /// static App: Component = |cx| {
+    ///     cx.use_hook(|| cx.provide_context(SharedState("world")));
+    ///     render!(Child {})
+    /// }
+    ///
+    /// static Child: Component = |cx| {
+    ///     let state = cx.consume_state::<SharedState>();
+    ///     render!(div { "hello {state.0}" })
+    /// }
+    /// ```
+    pub fn provide_context<T: 'static + Clone>(&self, value: T) -> T {
+        let mut contexts = self.shared_contexts.borrow_mut();
+
+        // If the context exists, swap it out for the new value
+        for ctx in contexts.iter_mut() {
+            // Swap the ptr directly
+            if let Some(ctx) = ctx.downcast_mut::<T>() {
+                std::mem::swap(ctx, &mut value.clone());
+                return value;
+            }
+        }
+
+        // Else, just push it
+        contexts.push(Box::new(value.clone()));
+
+        value
+    }
+
+    /// Provide a context to the root and then consume it
+    ///
+    /// This is intended for "global" state management solutions that would rather be implicit for the entire app.
+    /// Things like signal runtimes and routers are examples of "singletons" that would benefit from lazy initialization.
+    ///
+    /// Note that you should be checking if the context existed before trying to provide a new one. Providing a context
+    /// when a context already exists will swap the context out for the new one, which may not be what you want.
+    pub fn provide_root_context<T: 'static + Clone>(&self, context: T) -> T {
+        with_runtime(|runtime| {
+            runtime
+                .get_context(ScopeId::ROOT)
+                .unwrap()
+                .provide_context(context)
+        })
+        .expect("Runtime to exist")
+    }
+
+    /// Pushes the future onto the poll queue to be polled after the component renders.
+    pub fn push_future(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
+        let id = self.tasks.spawn(self.id, fut);
+        self.spawned_tasks.borrow_mut().insert(id);
+        id
+    }
+
+    /// Spawns the future but does not return the [`TaskId`]
+    pub fn spawn(&self, fut: impl Future<Output = ()> + 'static) {
+        self.push_future(fut);
+    }
+
+    /// Spawn a future that Dioxus won't clean up when this component is unmounted
+    ///
+    /// This is good for tasks that need to be run after the component has been dropped.
+    pub fn spawn_forever(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
+        // The root scope will never be unmounted so we can just add the task at the top of the app
+        let id = self.tasks.spawn(ScopeId::ROOT, fut);
+
+        // wake up the scheduler if it is sleeping
+        self.tasks
+            .sender
+            .unbounded_send(SchedulerMsg::TaskNotified(id))
+            .expect("Scheduler should exist");
+
+        self.spawned_tasks.borrow_mut().insert(id);
+
+        id
+    }
+
+    /// Informs the scheduler that this task is no longer needed and should be removed.
+    ///
+    /// This drops the task immediately.
+    pub fn remove_future(&self, id: TaskId) {
+        self.tasks.remove(id);
+    }
+
+    /// Inject an error into the nearest error boundary and quit rendering
+    ///
+    /// The error doesn't need to implement Error or any specific traits since the boundary
+    /// itself will downcast the error into a trait object.
+    pub fn throw(&self, error: impl Debug + 'static) -> Option<()> {
+        if let Some(cx) = self.consume_context::<Rc<ErrorBoundary>>() {
+            cx.insert_error(self.scope_id(), Box::new(error));
+        }
+
+        // Always return none during a throw
+        None
+    }
+
+    /// Mark this component as suspended and then return None
+    pub fn suspend(&self) -> Option<Element> {
+        self.suspended.set(true);
+        None
+    }
+}
+
+/// Schedule an update for any component given its [`ScopeId`].
+///
+/// A component's [`ScopeId`] can be obtained from `use_hook` or the [`crate::scopes::ScopeState::scope_id`] method.
+///
+/// This method should be used when you want to schedule an update for a component
+pub fn schedule_update_any() -> Option<Arc<dyn Fn(ScopeId) + Send + Sync>> {
+    with_current_scope(|cx| cx.schedule_update_any())
+}
+
+/// Get the current scope id
+pub fn current_scope_id() -> Option<ScopeId> {
+    with_runtime(|rt| rt.current_scope_id()).flatten()
+}
+
+#[doc(hidden)]
+/// Check if the virtual dom is currently inside of the body of a component
+pub fn vdom_is_rendering() -> bool {
+    with_runtime(|rt| rt.rendering.get()).unwrap_or_default()
+}
+
+/// Consume context from the current scope
+pub fn consume_context<T: 'static + Clone>() -> Option<T> {
+    with_current_scope(|cx| cx.consume_context::<T>()).flatten()
+}
+
+/// Consume context from the current scope
+pub fn consume_context_from_scope<T: 'static + Clone>(scope_id: ScopeId) -> Option<T> {
+    with_runtime(|rt| {
+        rt.get_context(scope_id)
+            .and_then(|cx| cx.consume_context::<T>())
+    })
+    .flatten()
+}
+
+/// Check if the current scope has a context
+pub fn has_context<T: 'static + Clone>() -> Option<T> {
+    with_current_scope(|cx| cx.has_context::<T>()).flatten()
+}
+
+/// Provide context to the current scope
+pub fn provide_context<T: 'static + Clone>(value: T) -> Option<T> {
+    with_current_scope(|cx| cx.provide_context(value))
+}
+
+/// Provide context to the the given scope
+pub fn provide_context_to_scope<T: 'static + Clone>(scope_id: ScopeId, value: T) -> Option<T> {
+    with_runtime(|rt| rt.get_context(scope_id).map(|cx| cx.provide_context(value))).flatten()
+}
+
+/// Provide a context to the root scope
+pub fn provide_root_context<T: 'static + Clone>(value: T) -> Option<T> {
+    with_current_scope(|cx| cx.provide_root_context(value))
+}
+
+/// Suspends the current component
+pub fn suspend() -> Option<Element<'static>> {
+    with_current_scope(|cx| {
+        cx.suspend();
+    });
+    None
+}
+
+/// Throw an error into the nearest error boundary
+pub fn throw(error: impl Debug + 'static) -> Option<()> {
+    with_current_scope(|cx| cx.throw(error)).flatten()
+}
+
+/// Pushes the future onto the poll queue to be polled after the component renders.
+pub fn push_future(fut: impl Future<Output = ()> + 'static) -> Option<TaskId> {
+    with_current_scope(|cx| cx.push_future(fut))
+}
+
+/// Spawns the future but does not return the [`TaskId`]
+pub fn spawn(fut: impl Future<Output = ()> + 'static) {
+    with_current_scope(|cx| cx.spawn(fut));
+}
+
+/// Spawn a future that Dioxus won't clean up when this component is unmounted
+///
+/// This is good for tasks that need to be run after the component has been dropped.
+pub fn spawn_forever(fut: impl Future<Output = ()> + 'static) -> Option<TaskId> {
+    with_current_scope(|cx| cx.spawn_forever(fut))
+}
+
+/// Informs the scheduler that this task is no longer needed and should be removed.
+///
+/// This drops the task immediately.
+pub fn remove_future(id: TaskId) {
+    with_current_scope(|cx| cx.remove_future(id));
+}

+ 61 - 103
packages/core/src/scopes.rs

@@ -2,17 +2,18 @@ use crate::{
     any_props::AnyProps,
     any_props::AnyProps,
     any_props::VProps,
     any_props::VProps,
     bump_frame::BumpFrame,
     bump_frame::BumpFrame,
+    innerlude::ErrorBoundary,
     innerlude::{DynamicNode, EventHandler, VComponent, VText},
     innerlude::{DynamicNode, EventHandler, VComponent, VText},
-    innerlude::{ErrorBoundary, Scheduler, SchedulerMsg},
     lazynodes::LazyNodes,
     lazynodes::LazyNodes,
     nodes::{IntoAttributeValue, IntoDynNode, RenderReturn},
     nodes::{IntoAttributeValue, IntoDynNode, RenderReturn},
+    runtime::Runtime,
+    scope_context::ScopeContext,
     AnyValue, Attribute, AttributeValue, Element, Event, Properties, TaskId,
     AnyValue, Attribute, AttributeValue, Element, Event, Properties, TaskId,
 };
 };
 use bumpalo::{boxed::Box as BumpBox, Bump};
 use bumpalo::{boxed::Box as BumpBox, Bump};
-use rustc_hash::FxHashSet;
 use std::{
 use std::{
-    any::{Any, TypeId},
-    cell::{Cell, RefCell, UnsafeCell},
+    any::Any,
+    cell::{Cell, Ref, RefCell, UnsafeCell},
     fmt::{Arguments, Debug},
     fmt::{Arguments, Debug},
     future::Future,
     future::Future,
     rc::Rc,
     rc::Rc,
@@ -62,37 +63,53 @@ impl<'a, T> std::ops::Deref for Scoped<'a, T> {
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
 pub struct ScopeId(pub usize);
 pub struct ScopeId(pub usize);
 
 
+impl ScopeId {
+    /// The root ScopeId.
+    ///
+    /// This scope will last for the entire duration of your app, making it convenient for long-lived state
+    /// that is created dynamically somewhere down the component tree.
+    ///
+    /// # Example
+    ///
+    /// ```rust, ignore
+    /// use dioxus_signals::*;
+    /// let my_persistent_state = Signal::new_in_scope(ScopeId::ROOT, String::new());
+    /// ```
+    pub const ROOT: ScopeId = ScopeId(0);
+}
+
 /// A component's state separate from its props.
 /// A component's state separate from its props.
 ///
 ///
 /// This struct exists to provide a common interface for all scopes without relying on generics.
 /// This struct exists to provide a common interface for all scopes without relying on generics.
 pub struct ScopeState {
 pub struct ScopeState {
+    pub(crate) runtime: Rc<Runtime>,
+    pub(crate) context_id: ScopeId,
+
     pub(crate) render_cnt: Cell<usize>,
     pub(crate) render_cnt: Cell<usize>,
-    pub(crate) name: &'static str,
 
 
     pub(crate) node_arena_1: BumpFrame,
     pub(crate) node_arena_1: BumpFrame,
     pub(crate) node_arena_2: BumpFrame,
     pub(crate) node_arena_2: BumpFrame,
 
 
-    pub(crate) parent: Option<*const ScopeState>,
-    pub(crate) id: ScopeId,
-
-    pub(crate) height: u32,
-    pub(crate) suspended: Cell<bool>,
-
     pub(crate) hooks: RefCell<Vec<Box<UnsafeCell<dyn Any>>>>,
     pub(crate) hooks: RefCell<Vec<Box<UnsafeCell<dyn Any>>>>,
     pub(crate) hook_idx: Cell<usize>,
     pub(crate) hook_idx: Cell<usize>,
 
 
-    pub(crate) shared_contexts: RefCell<Vec<(TypeId, Box<dyn Any>)>>,
-
-    pub(crate) tasks: Rc<Scheduler>,
-    pub(crate) spawned_tasks: RefCell<FxHashSet<TaskId>>,
-
     pub(crate) borrowed_props: RefCell<Vec<*const VComponent<'static>>>,
     pub(crate) borrowed_props: RefCell<Vec<*const VComponent<'static>>>,
     pub(crate) attributes_to_drop: RefCell<Vec<*const Attribute<'static>>>,
     pub(crate) attributes_to_drop: RefCell<Vec<*const Attribute<'static>>>,
 
 
     pub(crate) props: Option<Box<dyn AnyProps<'static>>>,
     pub(crate) props: Option<Box<dyn AnyProps<'static>>>,
 }
 }
 
 
+impl Drop for ScopeState {
+    fn drop(&mut self) {
+        self.runtime.remove_context(self.context_id);
+    }
+}
+
 impl<'src> ScopeState {
 impl<'src> ScopeState {
+    pub(crate) fn context(&self) -> Ref<'_, ScopeContext> {
+        self.runtime.get_context(self.context_id).unwrap()
+    }
+
     pub(crate) fn current_frame(&self) -> &BumpFrame {
     pub(crate) fn current_frame(&self) -> &BumpFrame {
         match self.render_cnt.get() % 2 {
         match self.render_cnt.get() % 2 {
             0 => &self.node_arena_1,
             0 => &self.node_arena_1,
@@ -111,7 +128,7 @@ impl<'src> ScopeState {
 
 
     /// Get the name of this component
     /// Get the name of this component
     pub fn name(&self) -> &str {
     pub fn name(&self) -> &str {
-        self.name
+        self.context().name
     }
     }
 
 
     /// Get the current render since the inception of this component
     /// Get the current render since the inception of this component
@@ -174,7 +191,7 @@ impl<'src> ScopeState {
     /// assert_eq!(base.height(), 0);
     /// assert_eq!(base.height(), 0);
     /// ```
     /// ```
     pub fn height(&self) -> u32 {
     pub fn height(&self) -> u32 {
-        self.height
+        self.context().height
     }
     }
 
 
     /// Get the Parent of this [`Scope`] within this Dioxus [`crate::VirtualDom`].
     /// Get the Parent of this [`Scope`] within this Dioxus [`crate::VirtualDom`].
@@ -195,7 +212,7 @@ impl<'src> ScopeState {
     /// ```
     /// ```
     pub fn parent(&self) -> Option<ScopeId> {
     pub fn parent(&self) -> Option<ScopeId> {
         // safety: the pointer to our parent is *always* valid thanks to the bump arena
         // safety: the pointer to our parent is *always* valid thanks to the bump arena
-        self.parent.map(|p| unsafe { &*p }.id)
+        self.context().parent_id()
     }
     }
 
 
     /// Get the ID of this Scope within this Dioxus [`crate::VirtualDom`].
     /// Get the ID of this Scope within this Dioxus [`crate::VirtualDom`].
@@ -212,15 +229,14 @@ impl<'src> ScopeState {
     /// assert_eq!(base.scope_id(), 0);
     /// assert_eq!(base.scope_id(), 0);
     /// ```
     /// ```
     pub fn scope_id(&self) -> ScopeId {
     pub fn scope_id(&self) -> ScopeId {
-        self.id
+        self.context().scope_id()
     }
     }
 
 
     /// Create a subscription that schedules a future render for the reference component
     /// Create a subscription that schedules a future render for the reference component
     ///
     ///
     /// ## Notice: you should prefer using [`Self::schedule_update_any`] and [`Self::scope_id`]
     /// ## Notice: you should prefer using [`Self::schedule_update_any`] and [`Self::scope_id`]
     pub fn schedule_update(&self) -> Arc<dyn Fn() + Send + Sync + 'static> {
     pub fn schedule_update(&self) -> Arc<dyn Fn() + Send + Sync + 'static> {
-        let (chan, id) = (self.tasks.sender.clone(), self.scope_id());
-        Arc::new(move || drop(chan.unbounded_send(SchedulerMsg::Immediate(id))))
+        self.context().schedule_update()
     }
     }
 
 
     /// Schedule an update for any component given its [`ScopeId`].
     /// Schedule an update for any component given its [`ScopeId`].
@@ -229,61 +245,31 @@ impl<'src> ScopeState {
     ///
     ///
     /// This method should be used when you want to schedule an update for a component
     /// This method should be used when you want to schedule an update for a component
     pub fn schedule_update_any(&self) -> Arc<dyn Fn(ScopeId) + Send + Sync> {
     pub fn schedule_update_any(&self) -> Arc<dyn Fn(ScopeId) + Send + Sync> {
-        let chan = self.tasks.sender.clone();
-        Arc::new(move |id| {
-            chan.unbounded_send(SchedulerMsg::Immediate(id)).unwrap();
-        })
+        self.context().schedule_update_any()
     }
     }
 
 
     /// Mark this scope as dirty, and schedule a render for it.
     /// Mark this scope as dirty, and schedule a render for it.
     pub fn needs_update(&self) {
     pub fn needs_update(&self) {
-        self.needs_update_any(self.scope_id());
+        self.context().needs_update()
     }
     }
 
 
     /// Get the [`ScopeId`] of a mounted component.
     /// Get the [`ScopeId`] of a mounted component.
     ///
     ///
     /// `ScopeId` is not unique for the lifetime of the [`crate::VirtualDom`] - a [`ScopeId`] will be reused if a component is unmounted.
     /// `ScopeId` is not unique for the lifetime of the [`crate::VirtualDom`] - a [`ScopeId`] will be reused if a component is unmounted.
     pub fn needs_update_any(&self, id: ScopeId) {
     pub fn needs_update_any(&self, id: ScopeId) {
-        self.tasks
-            .sender
-            .unbounded_send(SchedulerMsg::Immediate(id))
-            .expect("Scheduler to exist if scope exists");
+        self.context().needs_update_any(id)
     }
     }
 
 
     /// Return any context of type T if it exists on this scope
     /// Return any context of type T if it exists on this scope
     pub fn has_context<T: 'static + Clone>(&self) -> Option<T> {
     pub fn has_context<T: 'static + Clone>(&self) -> Option<T> {
-        self.shared_contexts
-            .borrow()
-            .iter()
-            .find(|(k, _)| *k == TypeId::of::<T>())
-            .map(|(_, v)| v)?
-            .downcast_ref::<T>()
-            .cloned()
+        self.context().has_context()
     }
     }
 
 
     /// Try to retrieve a shared state with type `T` from any parent scope.
     /// Try to retrieve a shared state with type `T` from any parent scope.
     ///
     ///
     /// Clones the state if it exists.
     /// Clones the state if it exists.
     pub fn consume_context<T: 'static + Clone>(&self) -> Option<T> {
     pub fn consume_context<T: 'static + Clone>(&self) -> Option<T> {
-        if let Some(this_ctx) = self.has_context() {
-            return Some(this_ctx);
-        }
-
-        let mut search_parent = self.parent;
-        while let Some(parent_ptr) = search_parent {
-            // safety: all parent pointers are valid thanks to the bump arena
-            let parent = unsafe { &*parent_ptr };
-            if let Some(shared) = parent
-                .shared_contexts
-                .borrow()
-                .iter()
-                .find(|(k, _)| *k == TypeId::of::<T>())
-            {
-                return shared.1.downcast_ref::<T>().cloned();
-            }
-            search_parent = parent.parent;
-        }
-        None
+        self.context().consume_context()
     }
     }
 
 
     /// Expose state to children further down the [`crate::VirtualDom`] Tree. Requires `Clone` on the context to allow getting values down the tree.
     /// Expose state to children further down the [`crate::VirtualDom`] Tree. Requires `Clone` on the context to allow getting values down the tree.
@@ -308,21 +294,7 @@ impl<'src> ScopeState {
     /// }
     /// }
     /// ```
     /// ```
     pub fn provide_context<T: 'static + Clone>(&self, value: T) -> T {
     pub fn provide_context<T: 'static + Clone>(&self, value: T) -> T {
-        let mut contexts = self.shared_contexts.borrow_mut();
-
-        // If the context exists, swap it out for the new value
-        for ctx in contexts.iter_mut() {
-            // Swap the ptr directly
-            if let Some(ctx) = ctx.1.downcast_mut::<T>() {
-                std::mem::swap(ctx, &mut value.clone());
-                return value;
-            }
-        }
-
-        // Else, just push it
-        contexts.push((TypeId::of::<T>(), Box::new(value.clone())));
-
-        value
+        self.context().provide_context(value)
     }
     }
 
 
     /// Provide a context to the root and then consume it
     /// Provide a context to the root and then consume it
@@ -333,52 +305,31 @@ impl<'src> ScopeState {
     /// Note that you should be checking if the context existed before trying to provide a new one. Providing a context
     /// Note that you should be checking if the context existed before trying to provide a new one. Providing a context
     /// when a context already exists will swap the context out for the new one, which may not be what you want.
     /// when a context already exists will swap the context out for the new one, which may not be what you want.
     pub fn provide_root_context<T: 'static + Clone>(&self, context: T) -> T {
     pub fn provide_root_context<T: 'static + Clone>(&self, context: T) -> T {
-        let mut parent = self;
-
-        // Walk upwards until there is no more parent - and tada we have the root
-        while let Some(next_parent) = parent.parent {
-            parent = unsafe { &*next_parent };
-            debug_assert_eq!(parent.scope_id(), ScopeId(0));
-        }
-
-        parent.provide_context(context)
+        self.context().provide_root_context(context)
     }
     }
 
 
     /// Pushes the future onto the poll queue to be polled after the component renders.
     /// Pushes the future onto the poll queue to be polled after the component renders.
     pub fn push_future(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
     pub fn push_future(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
-        let id = self.tasks.spawn(self.id, fut);
-        self.spawned_tasks.borrow_mut().insert(id);
-        id
+        self.context().push_future(fut)
     }
     }
 
 
     /// Spawns the future but does not return the [`TaskId`]
     /// Spawns the future but does not return the [`TaskId`]
     pub fn spawn(&self, fut: impl Future<Output = ()> + 'static) {
     pub fn spawn(&self, fut: impl Future<Output = ()> + 'static) {
-        self.push_future(fut);
+        self.context().spawn(fut);
     }
     }
 
 
     /// Spawn a future that Dioxus won't clean up when this component is unmounted
     /// Spawn a future that Dioxus won't clean up when this component is unmounted
     ///
     ///
     /// This is good for tasks that need to be run after the component has been dropped.
     /// This is good for tasks that need to be run after the component has been dropped.
     pub fn spawn_forever(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
     pub fn spawn_forever(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
-        // The root scope will never be unmounted so we can just add the task at the top of the app
-        let id = self.tasks.spawn(ScopeId(0), fut);
-
-        // wake up the scheduler if it is sleeping
-        self.tasks
-            .sender
-            .unbounded_send(SchedulerMsg::TaskNotified(id))
-            .expect("Scheduler should exist");
-
-        self.spawned_tasks.borrow_mut().insert(id);
-
-        id
+        self.context().spawn_forever(fut)
     }
     }
 
 
     /// Informs the scheduler that this task is no longer needed and should be removed.
     /// Informs the scheduler that this task is no longer needed and should be removed.
     ///
     ///
     /// This drops the task immediately.
     /// This drops the task immediately.
     pub fn remove_future(&self, id: TaskId) {
     pub fn remove_future(&self, id: TaskId) {
-        self.tasks.remove(id);
+        self.context().remove_future(id);
     }
     }
 
 
     /// Take a lazy [`crate::VNode`] structure and actually build it with the context of the efficient [`bumpalo::Bump`] allocator.
     /// Take a lazy [`crate::VNode`] structure and actually build it with the context of the efficient [`bumpalo::Bump`] allocator.
@@ -481,19 +432,22 @@ impl<'src> ScopeState {
     /// fn(Scope<Props>) -> Element;
     /// fn(Scope<Props>) -> Element;
     /// async fn(Scope<Props<'_>>) -> Element;
     /// async fn(Scope<Props<'_>>) -> Element;
     /// ```
     /// ```
-    pub fn component<P>(
+    pub fn component<'child, P>(
         &'src self,
         &'src self,
-        component: fn(Scope<'src, P>) -> Element<'src>,
+        component: fn(Scope<'child, P>) -> Element<'child>,
         props: P,
         props: P,
         fn_name: &'static str,
         fn_name: &'static str,
     ) -> DynamicNode<'src>
     ) -> DynamicNode<'src>
     where
     where
+        // The properties must be valid until the next bump frame
         P: Properties + 'src,
         P: Properties + 'src,
+        // The current bump allocator frame must outlive the child's borrowed props
+        'src: 'child,
     {
     {
         let vcomp = VProps::new(component, P::memoize, props);
         let vcomp = VProps::new(component, P::memoize, props);
 
 
         // cast off the lifetime of the render return
         // cast off the lifetime of the render return
-        let as_dyn: Box<dyn AnyProps<'src> + '_> = Box::new(vcomp);
+        let as_dyn: Box<dyn AnyProps<'child> + '_> = Box::new(vcomp);
         let extended: Box<dyn AnyProps<'src> + 'src> = unsafe { std::mem::transmute(as_dyn) };
         let extended: Box<dyn AnyProps<'src> + 'src> = unsafe { std::mem::transmute(as_dyn) };
 
 
         DynamicNode::Component(VComponent {
         DynamicNode::Component(VComponent {
@@ -510,7 +464,10 @@ impl<'src> ScopeState {
         let handler: &mut dyn FnMut(T) = self.bump().alloc(f);
         let handler: &mut dyn FnMut(T) = self.bump().alloc(f);
         let caller = unsafe { BumpBox::from_raw(handler as *mut dyn FnMut(T)) };
         let caller = unsafe { BumpBox::from_raw(handler as *mut dyn FnMut(T)) };
         let callback = RefCell::new(Some(caller));
         let callback = RefCell::new(Some(caller));
-        EventHandler { callback }
+        EventHandler {
+            callback,
+            origin: self.context().id,
+        }
     }
     }
 
 
     /// Create a new [`AttributeValue`] with the listener variant from a callback
     /// Create a new [`AttributeValue`] with the listener variant from a callback
@@ -564,7 +521,8 @@ impl<'src> ScopeState {
 
 
     /// Mark this component as suspended and then return None
     /// Mark this component as suspended and then return None
     pub fn suspend(&self) -> Option<Element> {
     pub fn suspend(&self) -> Option<Element> {
-        self.suspended.set(true);
+        let cx = self.context();
+        cx.suspend();
         None
         None
     }
     }
 
 

+ 43 - 25
packages/core/src/virtual_dom.rs

@@ -9,6 +9,7 @@ use crate::{
     mutations::Mutation,
     mutations::Mutation,
     nodes::RenderReturn,
     nodes::RenderReturn,
     nodes::{Template, TemplateId},
     nodes::{Template, TemplateId},
+    runtime::{Runtime, RuntimeGuard},
     scopes::{ScopeId, ScopeState},
     scopes::{ScopeId, ScopeState},
     AttributeValue, Element, Event, Scope,
     AttributeValue, Element, Event, Scope,
 };
 };
@@ -174,24 +175,24 @@ use std::{any::Any, cell::Cell, collections::BTreeSet, future::Future, rc::Rc};
 /// }
 /// }
 /// ```
 /// ```
 pub struct VirtualDom {
 pub struct VirtualDom {
-    // Maps a template path to a map of byteindexes to templates
-    pub(crate) templates: FxHashMap<TemplateId, FxHashMap<usize, Template<'static>>>,
     pub(crate) scopes: Slab<Box<ScopeState>>,
     pub(crate) scopes: Slab<Box<ScopeState>>,
+
     pub(crate) dirty_scopes: BTreeSet<DirtyScope>,
     pub(crate) dirty_scopes: BTreeSet<DirtyScope>,
-    pub(crate) scheduler: Rc<Scheduler>,
+
+    // Maps a template path to a map of byteindexes to templates
+    pub(crate) templates: FxHashMap<TemplateId, FxHashMap<usize, Template<'static>>>,
 
 
     // Every element is actually a dual reference - one to the template and the other to the dynamic node in that template
     // Every element is actually a dual reference - one to the template and the other to the dynamic node in that template
     pub(crate) elements: Slab<ElementRef>,
     pub(crate) elements: Slab<ElementRef>,
 
 
-    // While diffing we need some sort of way of breaking off a stream of suspended mutations.
-    pub(crate) scope_stack: Vec<ScopeId>,
+    pub(crate) mutations: Mutations<'static>,
+
+    pub(crate) runtime: Rc<Runtime>,
 
 
     // Currently suspended scopes
     // Currently suspended scopes
     pub(crate) suspended_scopes: FxHashSet<ScopeId>,
     pub(crate) suspended_scopes: FxHashSet<ScopeId>,
 
 
     pub(crate) rx: futures_channel::mpsc::UnboundedReceiver<SchedulerMsg>,
     pub(crate) rx: futures_channel::mpsc::UnboundedReceiver<SchedulerMsg>,
-
-    pub(crate) mutations: Mutations<'static>,
 }
 }
 
 
 impl VirtualDom {
 impl VirtualDom {
@@ -251,16 +252,16 @@ impl VirtualDom {
     /// ```
     /// ```
     pub fn new_with_props<P: 'static>(root: fn(Scope<P>) -> Element, root_props: P) -> Self {
     pub fn new_with_props<P: 'static>(root: fn(Scope<P>) -> Element, root_props: P) -> Self {
         let (tx, rx) = futures_channel::mpsc::unbounded();
         let (tx, rx) = futures_channel::mpsc::unbounded();
+        let scheduler = Scheduler::new(tx);
         let mut dom = Self {
         let mut dom = Self {
             rx,
             rx,
-            scheduler: Scheduler::new(tx),
-            templates: Default::default(),
+            runtime: Runtime::new(scheduler),
             scopes: Default::default(),
             scopes: Default::default(),
+            dirty_scopes: Default::default(),
+            templates: Default::default(),
             elements: Default::default(),
             elements: Default::default(),
-            scope_stack: Vec::new(),
-            dirty_scopes: BTreeSet::new(),
-            suspended_scopes: FxHashSet::default(),
             mutations: Mutations::default(),
             mutations: Mutations::default(),
+            suspended_scopes: Default::default(),
         };
         };
 
 
         let root = dom.new_scope(
         let root = dom.new_scope(
@@ -269,7 +270,7 @@ impl VirtualDom {
         );
         );
 
 
         // Unlike react, we provide a default error boundary that just renders the error as a string
         // Unlike react, we provide a default error boundary that just renders the error as a string
-        root.provide_context(Rc::new(ErrorBoundary::new(ScopeId(0))));
+        root.provide_context(Rc::new(ErrorBoundary::new(ScopeId::ROOT)));
 
 
         // the root element is always given element ID 0 since it's the container for the entire tree
         // the root element is always given element ID 0 since it's the container for the entire tree
         dom.elements.insert(ElementRef::none());
         dom.elements.insert(ElementRef::none());
@@ -281,14 +282,14 @@ impl VirtualDom {
     ///
     ///
     /// This is useful for inserting or removing contexts from a scope, or rendering out its root node
     /// This is useful for inserting or removing contexts from a scope, or rendering out its root node
     pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
     pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
-        self.scopes.get(id.0).map(|f| f.as_ref())
+        self.scopes.get(id.0).map(|s| &**s)
     }
     }
 
 
     /// Get the single scope at the top of the VirtualDom tree that will always be around
     /// Get the single scope at the top of the VirtualDom tree that will always be around
     ///
     ///
     /// This scope has a ScopeId of 0 and is the root of the tree
     /// This scope has a ScopeId of 0 and is the root of the tree
     pub fn base_scope(&self) -> &ScopeState {
     pub fn base_scope(&self) -> &ScopeState {
-        self.get_scope(ScopeId(0)).unwrap()
+        self.get_scope(ScopeId::ROOT).unwrap()
     }
     }
 
 
     /// Build the virtualdom with a global context inserted into the base scope
     /// Build the virtualdom with a global context inserted into the base scope
@@ -301,10 +302,10 @@ impl VirtualDom {
 
 
     /// Manually mark a scope as requiring a re-render
     /// Manually mark a scope as requiring a re-render
     ///
     ///
-    /// Whenever the VirtualDom "works", it will re-render this scope
+    /// Whenever the Runtime "works", it will re-render this scope
     pub fn mark_dirty(&mut self, id: ScopeId) {
     pub fn mark_dirty(&mut self, id: ScopeId) {
         if let Some(scope) = self.get_scope(id) {
         if let Some(scope) = self.get_scope(id) {
-            let height = scope.height;
+            let height = scope.height();
             self.dirty_scopes.insert(DirtyScope { height, id });
             self.dirty_scopes.insert(DirtyScope { height, id });
         }
         }
     }
     }
@@ -325,6 +326,8 @@ impl VirtualDom {
         element: ElementId,
         element: ElementId,
         bubbles: bool,
         bubbles: bool,
     ) {
     ) {
+        let _runtime = RuntimeGuard::new(self.runtime.clone());
+
         /*
         /*
         ------------------------
         ------------------------
         The algorithm works by walking through the list of dynamic attributes, checking their paths, and breaking when
         The algorithm works by walking through the list of dynamic attributes, checking their paths, and breaking when
@@ -387,9 +390,14 @@ impl VirtualDom {
                     // We check the bubble state between each call to see if the event has been stopped from bubbling
                     // We check the bubble state between each call to see if the event has been stopped from bubbling
                     for listener in listeners.drain(..).rev() {
                     for listener in listeners.drain(..).rev() {
                         if let AttributeValue::Listener(listener) = listener {
                         if let AttributeValue::Listener(listener) = listener {
+                            let origin = el_ref.scope;
+                            self.runtime.scope_stack.borrow_mut().push(origin);
+                            self.runtime.rendering.set(false);
                             if let Some(cb) = listener.borrow_mut().as_deref_mut() {
                             if let Some(cb) = listener.borrow_mut().as_deref_mut() {
                                 cb(uievent.clone());
                                 cb(uievent.clone());
                             }
                             }
+                            self.runtime.scope_stack.borrow_mut().pop();
+                            self.runtime.rendering.set(true);
 
 
                             if !uievent.propagates.get() {
                             if !uievent.propagates.get() {
                                 return;
                                 return;
@@ -418,9 +426,14 @@ impl VirtualDom {
                         // Only call the listener if this is the exact target element.
                         // Only call the listener if this is the exact target element.
                         if attr.name.trim_start_matches("on") == name && target_path == this_path {
                         if attr.name.trim_start_matches("on") == name && target_path == this_path {
                             if let AttributeValue::Listener(listener) = &attr.value {
                             if let AttributeValue::Listener(listener) = &attr.value {
+                                let origin = el_ref.scope;
+                                self.runtime.scope_stack.borrow_mut().push(origin);
+                                self.runtime.rendering.set(false);
                                 if let Some(cb) = listener.borrow_mut().as_deref_mut() {
                                 if let Some(cb) = listener.borrow_mut().as_deref_mut() {
                                     cb(uievent.clone());
                                     cb(uievent.clone());
                                 }
                                 }
+                                self.runtime.scope_stack.borrow_mut().pop();
+                                self.runtime.rendering.set(true);
 
 
                                 break;
                                 break;
                             }
                             }
@@ -501,10 +514,11 @@ impl VirtualDom {
                 if sync.template.get().name.rsplit_once(':').unwrap().0
                 if sync.template.get().name.rsplit_once(':').unwrap().0
                     == template.name.rsplit_once(':').unwrap().0
                     == template.name.rsplit_once(':').unwrap().0
                 {
                 {
-                    let height = scope.height;
+                    let context = scope.context();
+                    let height = context.height;
                     self.dirty_scopes.insert(DirtyScope {
                     self.dirty_scopes.insert(DirtyScope {
                         height,
                         height,
-                        id: scope.id,
+                        id: context.id,
                     });
                     });
                 }
                 }
             }
             }
@@ -532,10 +546,11 @@ impl VirtualDom {
     /// apply_edits(edits);
     /// apply_edits(edits);
     /// ```
     /// ```
     pub fn rebuild(&mut self) -> Mutations {
     pub fn rebuild(&mut self) -> Mutations {
-        match unsafe { self.run_scope(ScopeId(0)).extend_lifetime_ref() } {
+        let _runtime = RuntimeGuard::new(self.runtime.clone());
+        match unsafe { self.run_scope(ScopeId::ROOT).extend_lifetime_ref() } {
             // Rebuilding implies we append the created elements to the root
             // Rebuilding implies we append the created elements to the root
             RenderReturn::Ready(node) => {
             RenderReturn::Ready(node) => {
-                let m = self.create_scope(ScopeId(0), node);
+                let m = self.create_scope(ScopeId::ROOT, node);
                 self.mutations.edits.push(Mutation::AppendChildren {
                 self.mutations.edits.push(Mutation::AppendChildren {
                     id: ElementId(0),
                     id: ElementId(0),
                     m,
                     m,
@@ -610,9 +625,12 @@ impl VirtualDom {
                     continue;
                     continue;
                 }
                 }
 
 
-                // Run the scope and get the mutations
-                self.run_scope(dirty.id);
-                self.diff_scope(dirty.id);
+                {
+                    let _runtime = RuntimeGuard::new(self.runtime.clone());
+                    // Run the scope and get the mutations
+                    self.run_scope(dirty.id);
+                    self.diff_scope(dirty.id);
+                }
             }
             }
 
 
             // If there's more work, then just continue, plenty of work to do
             // If there's more work, then just continue, plenty of work to do
@@ -645,6 +663,6 @@ impl VirtualDom {
 impl Drop for VirtualDom {
 impl Drop for VirtualDom {
     fn drop(&mut self) {
     fn drop(&mut self) {
         // Simply drop this scope which drops all of its children
         // Simply drop this scope which drops all of its children
-        self.drop_scope(ScopeId(0), true);
+        self.drop_scope(ScopeId::ROOT, true);
     }
     }
 }
 }

+ 4 - 4
packages/core/tests/attr_cleanup.rs

@@ -34,7 +34,7 @@ fn attrs_cycle() {
         ]
         ]
     );
     );
 
 
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().santize().edits,
         dom.render_immediate().santize().edits,
         [
         [
@@ -56,7 +56,7 @@ fn attrs_cycle() {
         ]
         ]
     );
     );
 
 
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().santize().edits,
         dom.render_immediate().santize().edits,
         [
         [
@@ -65,7 +65,7 @@ fn attrs_cycle() {
         ]
         ]
     );
     );
 
 
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().santize().edits,
         dom.render_immediate().santize().edits,
         [
         [
@@ -88,7 +88,7 @@ fn attrs_cycle() {
     );
     );
 
 
     // we take the node taken by attributes since we reused it
     // we take the node taken by attributes since we reused it
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().santize().edits,
         dom.render_immediate().santize().edits,
         [
         [

+ 1 - 1
packages/core/tests/bubble_error.rs

@@ -24,7 +24,7 @@ fn bubbles_error() {
         let _edits = dom.rebuild().santize();
         let _edits = dom.rebuild().santize();
     }
     }
 
 
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
 
 
     _ = dom.render_immediate();
     _ = dom.render_immediate();
 }
 }

+ 3 - 3
packages/core/tests/context_api.rs

@@ -27,11 +27,11 @@ fn state_shares() {
         ]
         ]
     );
     );
 
 
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     _ = dom.render_immediate();
     _ = dom.render_immediate();
     assert_eq!(dom.base_scope().consume_context::<i32>().unwrap(), 1);
     assert_eq!(dom.base_scope().consume_context::<i32>().unwrap(), 1);
 
 
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     _ = dom.render_immediate();
     _ = dom.render_immediate();
     assert_eq!(dom.base_scope().consume_context::<i32>().unwrap(), 2);
     assert_eq!(dom.base_scope().consume_context::<i32>().unwrap(), 2);
 
 
@@ -41,7 +41,7 @@ fn state_shares() {
         [SetText { value: "Value is 2", id: ElementId(1,) },]
         [SetText { value: "Value is 2", id: ElementId(1,) },]
     );
     );
 
 
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     dom.mark_dirty(ScopeId(2));
     dom.mark_dirty(ScopeId(2));
     let edits = dom.render_immediate();
     let edits = dom.render_immediate();
     assert_eq!(
     assert_eq!(

+ 3 - 3
packages/core/tests/cycle.rs

@@ -23,7 +23,7 @@ fn cycling_elements() {
         );
         );
     }
     }
 
 
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().santize().edits,
         dom.render_immediate().santize().edits,
         [
         [
@@ -33,7 +33,7 @@ fn cycling_elements() {
     );
     );
 
 
     // notice that the IDs cycle back to ElementId(1), preserving a minimal memory footprint
     // notice that the IDs cycle back to ElementId(1), preserving a minimal memory footprint
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().santize().edits,
         dom.render_immediate().santize().edits,
         [
         [
@@ -42,7 +42,7 @@ fn cycling_elements() {
         ]
         ]
     );
     );
 
 
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().santize().edits,
         dom.render_immediate().santize().edits,
         [
         [

+ 3 - 3
packages/core/tests/diff_component.rs

@@ -75,7 +75,7 @@ fn component_swap() {
         );
         );
     }
     }
 
 
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().santize().edits,
         dom.render_immediate().santize().edits,
         [
         [
@@ -84,7 +84,7 @@ fn component_swap() {
         ]
         ]
     );
     );
 
 
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().santize().edits,
         dom.render_immediate().santize().edits,
         [
         [
@@ -93,7 +93,7 @@ fn component_swap() {
         ]
         ]
     );
     );
 
 
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().santize().edits,
         dom.render_immediate().santize().edits,
         [
         [

+ 7 - 7
packages/core/tests/diff_element.rs

@@ -12,19 +12,19 @@ fn text_diff() {
     let mut vdom = VirtualDom::new(app);
     let mut vdom = VirtualDom::new(app);
     _ = vdom.rebuild();
     _ = vdom.rebuild();
 
 
-    vdom.mark_dirty(ScopeId(0));
+    vdom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         vdom.render_immediate().edits,
         vdom.render_immediate().edits,
         [SetText { value: "hello 1", id: ElementId(2) }]
         [SetText { value: "hello 1", id: ElementId(2) }]
     );
     );
 
 
-    vdom.mark_dirty(ScopeId(0));
+    vdom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         vdom.render_immediate().edits,
         vdom.render_immediate().edits,
         [SetText { value: "hello 2", id: ElementId(2) }]
         [SetText { value: "hello 2", id: ElementId(2) }]
     );
     );
 
 
-    vdom.mark_dirty(ScopeId(0));
+    vdom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         vdom.render_immediate().edits,
         vdom.render_immediate().edits,
         [SetText { value: "hello 3", id: ElementId(2) }]
         [SetText { value: "hello 3", id: ElementId(2) }]
@@ -46,7 +46,7 @@ fn element_swap() {
     let mut vdom = VirtualDom::new(app);
     let mut vdom = VirtualDom::new(app);
     _ = vdom.rebuild();
     _ = vdom.rebuild();
 
 
-    vdom.mark_dirty(ScopeId(0));
+    vdom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         vdom.render_immediate().santize().edits,
         vdom.render_immediate().santize().edits,
         [
         [
@@ -55,7 +55,7 @@ fn element_swap() {
         ]
         ]
     );
     );
 
 
-    vdom.mark_dirty(ScopeId(0));
+    vdom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         vdom.render_immediate().santize().edits,
         vdom.render_immediate().santize().edits,
         [
         [
@@ -64,7 +64,7 @@ fn element_swap() {
         ]
         ]
     );
     );
 
 
-    vdom.mark_dirty(ScopeId(0));
+    vdom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         vdom.render_immediate().santize().edits,
         vdom.render_immediate().santize().edits,
         [
         [
@@ -73,7 +73,7 @@ fn element_swap() {
         ]
         ]
     );
     );
 
 
-    vdom.mark_dirty(ScopeId(0));
+    vdom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         vdom.render_immediate().santize().edits,
         vdom.render_immediate().santize().edits,
         [
         [

+ 12 - 12
packages/core/tests/diff_keyed_list.rs

@@ -39,7 +39,7 @@ fn keyed_diffing_out_of_order() {
         );
         );
     }
     }
 
 
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().edits,
         dom.render_immediate().edits,
         [
         [
@@ -64,7 +64,7 @@ fn keyed_diffing_out_of_order_adds() {
 
 
     _ = dom.rebuild();
     _ = dom.rebuild();
 
 
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().edits,
         dom.render_immediate().edits,
         [
         [
@@ -90,7 +90,7 @@ fn keyed_diffing_out_of_order_adds_3() {
 
 
     _ = dom.rebuild();
     _ = dom.rebuild();
 
 
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().edits,
         dom.render_immediate().edits,
         [
         [
@@ -116,7 +116,7 @@ fn keyed_diffing_out_of_order_adds_4() {
 
 
     _ = dom.rebuild();
     _ = dom.rebuild();
 
 
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().edits,
         dom.render_immediate().edits,
         [
         [
@@ -142,7 +142,7 @@ fn keyed_diffing_out_of_order_adds_5() {
 
 
     _ = dom.rebuild();
     _ = dom.rebuild();
 
 
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().edits,
         dom.render_immediate().edits,
         [
         [
@@ -167,7 +167,7 @@ fn keyed_diffing_additions() {
 
 
     _ = dom.rebuild();
     _ = dom.rebuild();
 
 
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().santize().edits,
         dom.render_immediate().santize().edits,
         [
         [
@@ -192,7 +192,7 @@ fn keyed_diffing_additions_and_moves_on_ends() {
 
 
     _ = dom.rebuild();
     _ = dom.rebuild();
 
 
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().santize().edits,
         dom.render_immediate().santize().edits,
         [
         [
@@ -222,7 +222,7 @@ fn keyed_diffing_additions_and_moves_in_middle() {
     _ = dom.rebuild();
     _ = dom.rebuild();
 
 
     // LIS: 4, 5, 6
     // LIS: 4, 5, 6
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().santize().edits,
         dom.render_immediate().santize().edits,
         [
         [
@@ -256,7 +256,7 @@ fn controlled_keyed_diffing_out_of_order() {
     _ = dom.rebuild();
     _ = dom.rebuild();
 
 
     // LIS: 5, 6
     // LIS: 5, 6
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().santize().edits,
         dom.render_immediate().santize().edits,
         [
         [
@@ -289,7 +289,7 @@ fn controlled_keyed_diffing_out_of_order_max_test() {
 
 
     _ = dom.rebuild();
     _ = dom.rebuild();
 
 
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().santize().edits,
         dom.render_immediate().santize().edits,
         [
         [
@@ -318,7 +318,7 @@ fn remove_list() {
 
 
     _ = dom.rebuild();
     _ = dom.rebuild();
 
 
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().santize().edits,
         dom.render_immediate().santize().edits,
         [
         [
@@ -343,7 +343,7 @@ fn no_common_keys() {
 
 
     _ = dom.rebuild();
     _ = dom.rebuild();
 
 
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().santize().edits,
         dom.render_immediate().santize().edits,
         [
         [

+ 18 - 18
packages/core/tests/diff_unkeyed_list.rs

@@ -27,7 +27,7 @@ fn list_creates_one_by_one() {
     );
     );
 
 
     // Rendering the first item should replace the placeholder with an element
     // Rendering the first item should replace the placeholder with an element
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().santize().edits,
         dom.render_immediate().santize().edits,
         [
         [
@@ -38,7 +38,7 @@ fn list_creates_one_by_one() {
     );
     );
 
 
     // Rendering the next item should insert after the previous
     // Rendering the next item should insert after the previous
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().santize().edits,
         dom.render_immediate().santize().edits,
         [
         [
@@ -49,7 +49,7 @@ fn list_creates_one_by_one() {
     );
     );
 
 
     // ... and again!
     // ... and again!
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().santize().edits,
         dom.render_immediate().santize().edits,
         [
         [
@@ -60,7 +60,7 @@ fn list_creates_one_by_one() {
     );
     );
 
 
     // once more
     // once more
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().santize().edits,
         dom.render_immediate().santize().edits,
         [
         [
@@ -107,14 +107,14 @@ fn removes_one_by_one() {
 
 
     // Remove div(3)
     // Remove div(3)
     // Rendering the first item should replace the placeholder with an element
     // Rendering the first item should replace the placeholder with an element
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().santize().edits,
         dom.render_immediate().santize().edits,
         [Remove { id: ElementId(6) }]
         [Remove { id: ElementId(6) }]
     );
     );
 
 
     // Remove div(2)
     // Remove div(2)
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().santize().edits,
         dom.render_immediate().santize().edits,
         [Remove { id: ElementId(4) }]
         [Remove { id: ElementId(4) }]
@@ -122,7 +122,7 @@ fn removes_one_by_one() {
 
 
     // Remove div(1) and replace with a placeholder
     // Remove div(1) and replace with a placeholder
     // todo: this should just be a remove with no placeholder
     // todo: this should just be a remove with no placeholder
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().santize().edits,
         dom.render_immediate().santize().edits,
         [
         [
@@ -133,7 +133,7 @@ fn removes_one_by_one() {
 
 
     // load the 3 and replace the placeholder
     // load the 3 and replace the placeholder
     // todo: this should actually be append to, but replace placeholder is fine for now
     // todo: this should actually be append to, but replace placeholder is fine for now
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().santize().edits,
         dom.render_immediate().santize().edits,
         [
         [
@@ -170,7 +170,7 @@ fn list_shrink_multiroot() {
         ]
         ]
     );
     );
 
 
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().santize().edits,
         dom.render_immediate().santize().edits,
         [
         [
@@ -182,7 +182,7 @@ fn list_shrink_multiroot() {
         ]
         ]
     );
     );
 
 
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().santize().edits,
         dom.render_immediate().santize().edits,
         [
         [
@@ -194,7 +194,7 @@ fn list_shrink_multiroot() {
         ]
         ]
     );
     );
 
 
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().santize().edits,
         dom.render_immediate().santize().edits,
         [
         [
@@ -249,19 +249,19 @@ fn removes_one_by_one_multiroot() {
         ]
         ]
     );
     );
 
 
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().santize().edits,
         dom.render_immediate().santize().edits,
         [Remove { id: ElementId(10) }, Remove { id: ElementId(12) }]
         [Remove { id: ElementId(10) }, Remove { id: ElementId(12) }]
     );
     );
 
 
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().santize().edits,
         dom.render_immediate().santize().edits,
         [Remove { id: ElementId(6) }, Remove { id: ElementId(8) }]
         [Remove { id: ElementId(6) }, Remove { id: ElementId(8) }]
     );
     );
 
 
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     assert_eq!(
     assert_eq!(
         dom.render_immediate().santize().edits,
         dom.render_immediate().santize().edits,
         [
         [
@@ -328,7 +328,7 @@ fn remove_many() {
     }
     }
 
 
     {
     {
-        dom.mark_dirty(ScopeId(0));
+        dom.mark_dirty(ScopeId::ROOT);
         let edits = dom.render_immediate().santize();
         let edits = dom.render_immediate().santize();
         assert_eq!(
         assert_eq!(
             edits.edits,
             edits.edits,
@@ -341,7 +341,7 @@ fn remove_many() {
     }
     }
 
 
     {
     {
-        dom.mark_dirty(ScopeId(0));
+        dom.mark_dirty(ScopeId::ROOT);
         let edits = dom.render_immediate().santize();
         let edits = dom.render_immediate().santize();
         assert_eq!(
         assert_eq!(
             edits.edits,
             edits.edits,
@@ -360,7 +360,7 @@ fn remove_many() {
     }
     }
 
 
     {
     {
-        dom.mark_dirty(ScopeId(0));
+        dom.mark_dirty(ScopeId::ROOT);
         let edits = dom.render_immediate().santize();
         let edits = dom.render_immediate().santize();
         assert_eq!(
         assert_eq!(
             edits.edits,
             edits.edits,
@@ -376,7 +376,7 @@ fn remove_many() {
     }
     }
 
 
     {
     {
-        dom.mark_dirty(ScopeId(0));
+        dom.mark_dirty(ScopeId::ROOT);
         let edits = dom.render_immediate().santize();
         let edits = dom.render_immediate().santize();
         assert_eq!(
         assert_eq!(
             edits.edits,
             edits.edits,

+ 2 - 2
packages/core/tests/fuzzing.rs

@@ -179,7 +179,7 @@ fn create_random_dynamic_node(cx: &ScopeState, depth: usize) -> DynamicNode {
                 node_paths: &[&[0]],
                 node_paths: &[&[0]],
                 attr_paths: &[],
                 attr_paths: &[],
             }),
             }),
-            root_ids: Default::default(),
+            root_ids: bumpalo::collections::Vec::new_in(cx.bump()).into(),
             dynamic_nodes: cx.bump().alloc([cx.component(
             dynamic_nodes: cx.bump().alloc([cx.component(
                 create_random_element,
                 create_random_element,
                 DepthProps { depth, root: false },
                 DepthProps { depth, root: false },
@@ -276,7 +276,7 @@ fn create_random_element(cx: Scope<DepthProps>) -> Element {
                 key: None,
                 key: None,
                 parent: None,
                 parent: None,
                 template: Cell::new(template),
                 template: Cell::new(template),
-                root_ids: Default::default(),
+                root_ids: bumpalo::collections::Vec::new_in(cx.bump()).into(),
                 dynamic_nodes: {
                 dynamic_nodes: {
                     let dynamic_nodes: Vec<_> = dynamic_node_types
                     let dynamic_nodes: Vec<_> = dynamic_node_types
                         .iter()
                         .iter()

+ 8 - 8
packages/core/tests/lifecycle.rs

@@ -58,7 +58,7 @@ fn events_generate() {
 
 
     dom.handle_event("click", Rc::new(MouseData::default()), ElementId(1), true);
     dom.handle_event("click", Rc::new(MouseData::default()), ElementId(1), true);
 
 
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     let edits = dom.render_immediate();
     let edits = dom.render_immediate();
 
 
     assert_eq!(
     assert_eq!(
@@ -107,7 +107,7 @@ fn events_generate() {
 //     );
 //     );
 
 
 //     assert_eq!(
 //     assert_eq!(
-//         dom.hard_diff(ScopeId(0)).edits,
+//         dom.hard_diff(ScopeId::ROOT).edits,
 //         [
 //         [
 //             CreateElement { root: Some(2), tag: "div", children: 0 },
 //             CreateElement { root: Some(2), tag: "div", children: 0 },
 //             ReplaceWith { root: Some(1), nodes: vec![2] }
 //             ReplaceWith { root: Some(1), nodes: vec![2] }
@@ -115,7 +115,7 @@ fn events_generate() {
 //     );
 //     );
 
 
 //     assert_eq!(
 //     assert_eq!(
-//         dom.hard_diff(ScopeId(0)).edits,
+//         dom.hard_diff(ScopeId::ROOT).edits,
 //         [
 //         [
 //             CreateTextNode { root: Some(1), text: "Text2" },
 //             CreateTextNode { root: Some(1), text: "Text2" },
 //             ReplaceWith { root: Some(2), nodes: vec![1] }
 //             ReplaceWith { root: Some(2), nodes: vec![1] }
@@ -124,7 +124,7 @@ fn events_generate() {
 
 
 //     // child {}
 //     // child {}
 //     assert_eq!(
 //     assert_eq!(
-//         dom.hard_diff(ScopeId(0)).edits,
+//         dom.hard_diff(ScopeId::ROOT).edits,
 //         [
 //         [
 //             CreateElement { root: Some(2), tag: "h1", children: 0 },
 //             CreateElement { root: Some(2), tag: "h1", children: 0 },
 //             ReplaceWith { root: Some(1), nodes: vec![2] }
 //             ReplaceWith { root: Some(1), nodes: vec![2] }
@@ -133,7 +133,7 @@ fn events_generate() {
 
 
 //     // placeholder
 //     // placeholder
 //     assert_eq!(
 //     assert_eq!(
-//         dom.hard_diff(ScopeId(0)).edits,
+//         dom.hard_diff(ScopeId::ROOT).edits,
 //         [
 //         [
 //             CreatePlaceholder { root: Some(1) },
 //             CreatePlaceholder { root: Some(1) },
 //             ReplaceWith { root: Some(2), nodes: vec![1] }
 //             ReplaceWith { root: Some(2), nodes: vec![1] }
@@ -141,7 +141,7 @@ fn events_generate() {
 //     );
 //     );
 
 
 //     assert_eq!(
 //     assert_eq!(
-//         dom.hard_diff(ScopeId(0)).edits,
+//         dom.hard_diff(ScopeId::ROOT).edits,
 //         [
 //         [
 //             CreateTextNode { root: Some(2), text: "text 3" },
 //             CreateTextNode { root: Some(2), text: "text 3" },
 //             ReplaceWith { root: Some(1), nodes: vec![2] }
 //             ReplaceWith { root: Some(1), nodes: vec![2] }
@@ -149,7 +149,7 @@ fn events_generate() {
 //     );
 //     );
 
 
 //     assert_eq!(
 //     assert_eq!(
-//         dom.hard_diff(ScopeId(0)).edits,
+//         dom.hard_diff(ScopeId::ROOT).edits,
 //         [
 //         [
 //             CreateTextNode { text: "text 0", root: Some(1) },
 //             CreateTextNode { text: "text 0", root: Some(1) },
 //             CreateTextNode { text: "text 1", root: Some(3) },
 //             CreateTextNode { text: "text 1", root: Some(3) },
@@ -158,7 +158,7 @@ fn events_generate() {
 //     );
 //     );
 
 
 //     assert_eq!(
 //     assert_eq!(
-//         dom.hard_diff(ScopeId(0)).edits,
+//         dom.hard_diff(ScopeId::ROOT).edits,
 //         [
 //         [
 //             CreateElement { tag: "h1", root: Some(2), children: 0 },
 //             CreateElement { tag: "h1", root: Some(2), children: 0 },
 //             ReplaceWith { root: Some(1), nodes: vec![2] },
 //             ReplaceWith { root: Some(1), nodes: vec![2] },

+ 6 - 6
packages/core/tests/miri_simple.rs

@@ -11,7 +11,7 @@ fn app_drops() {
     let mut dom = VirtualDom::new(app);
     let mut dom = VirtualDom::new(app);
 
 
     _ = dom.rebuild();
     _ = dom.rebuild();
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     _ = dom.render_immediate();
     _ = dom.render_immediate();
 }
 }
 
 
@@ -31,7 +31,7 @@ fn hooks_drop() {
     let mut dom = VirtualDom::new(app);
     let mut dom = VirtualDom::new(app);
 
 
     _ = dom.rebuild();
     _ = dom.rebuild();
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     _ = dom.render_immediate();
     _ = dom.render_immediate();
 }
 }
 
 
@@ -58,7 +58,7 @@ fn contexts_drop() {
     let mut dom = VirtualDom::new(app);
     let mut dom = VirtualDom::new(app);
 
 
     _ = dom.rebuild();
     _ = dom.rebuild();
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     _ = dom.render_immediate();
     _ = dom.render_immediate();
 }
 }
 
 
@@ -77,7 +77,7 @@ fn tasks_drop() {
     let mut dom = VirtualDom::new(app);
     let mut dom = VirtualDom::new(app);
 
 
     _ = dom.rebuild();
     _ = dom.rebuild();
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     _ = dom.render_immediate();
     _ = dom.render_immediate();
 }
 }
 
 
@@ -91,7 +91,7 @@ fn root_props_drop() {
     );
     );
 
 
     _ = dom.rebuild();
     _ = dom.rebuild();
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
     _ = dom.render_immediate();
     _ = dom.render_immediate();
 }
 }
 
 
@@ -121,7 +121,7 @@ fn diffing_drops_old() {
 
 
     let mut dom = VirtualDom::new(app);
     let mut dom = VirtualDom::new(app);
     _ = dom.rebuild();
     _ = dom.rebuild();
-    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId::ROOT);
 
 
     _ = dom.render_immediate();
     _ = dom.render_immediate();
 }
 }

+ 8 - 8
packages/core/tests/miri_stress.rs

@@ -62,7 +62,7 @@ fn test_memory_leak() {
     _ = dom.rebuild();
     _ = dom.rebuild();
 
 
     for _ in 0..5 {
     for _ in 0..5 {
-        dom.mark_dirty(ScopeId(0));
+        dom.mark_dirty(ScopeId::ROOT);
         _ = dom.render_immediate();
         _ = dom.render_immediate();
     }
     }
 }
 }
@@ -97,13 +97,13 @@ fn memo_works_properly() {
 
 
     _ = dom.rebuild();
     _ = dom.rebuild();
     // todo!()
     // todo!()
-    // dom.hard_diff(ScopeId(0));
-    // dom.hard_diff(ScopeId(0));
-    // dom.hard_diff(ScopeId(0));
-    // dom.hard_diff(ScopeId(0));
-    // dom.hard_diff(ScopeId(0));
-    // dom.hard_diff(ScopeId(0));
-    // dom.hard_diff(ScopeId(0));
+    // dom.hard_diff(ScopeId::ROOT);
+    // dom.hard_diff(ScopeId::ROOT);
+    // dom.hard_diff(ScopeId::ROOT);
+    // dom.hard_diff(ScopeId::ROOT);
+    // dom.hard_diff(ScopeId::ROOT);
+    // dom.hard_diff(ScopeId::ROOT);
+    // dom.hard_diff(ScopeId::ROOT);
 }
 }
 
 
 #[test]
 #[test]

+ 2 - 1
packages/desktop/Cargo.toml

@@ -55,7 +55,8 @@ default = ["tokio_runtime", "hot-reload"]
 tokio_runtime = ["tokio"]
 tokio_runtime = ["tokio"]
 fullscreen = ["wry/fullscreen"]
 fullscreen = ["wry/fullscreen"]
 transparent = ["wry/transparent"]
 transparent = ["wry/transparent"]
-tray = ["wry/tray"]
+devtools = ["wry/devtools"]
+dox = ["wry/dox"]
 hot-reload = ["dioxus-hot-reload"]
 hot-reload = ["dioxus-hot-reload"]
 
 
 [dev-dependencies]
 [dev-dependencies]

+ 7 - 2
packages/desktop/src/protocol.rs

@@ -158,8 +158,13 @@ fn get_mime_from_path(trimmed: &Path) -> Result<&'static str> {
     }
     }
 
 
     let res = match infer::get_from_path(trimmed)?.map(|f| f.mime_type()) {
     let res = match infer::get_from_path(trimmed)?.map(|f| f.mime_type()) {
-        Some(t) if t == "text/plain" => get_mime_by_ext(trimmed),
-        Some(f) => f,
+        Some(f) => {
+            if f == "text/plain" {
+                get_mime_by_ext(trimmed)
+            } else {
+                f
+            }
+        }
         None => get_mime_by_ext(trimmed),
         None => get_mime_by_ext(trimmed),
     };
     };
 
 

+ 7 - 1
packages/dioxus-tui/examples/hover.rs

@@ -1,6 +1,7 @@
 use dioxus::{events::MouseData, prelude::*};
 use dioxus::{events::MouseData, prelude::*};
 use dioxus_core::Event;
 use dioxus_core::Event;
 use std::convert::TryInto;
 use std::convert::TryInto;
+use std::fmt::Write;
 use std::rc::Rc;
 use std::rc::Rc;
 
 
 fn main() {
 fn main() {
@@ -9,7 +10,12 @@ fn main() {
 
 
 fn app(cx: Scope) -> Element {
 fn app(cx: Scope) -> Element {
     fn to_str(c: &[i32; 3]) -> String {
     fn to_str(c: &[i32; 3]) -> String {
-        "#".to_string() + &c.iter().map(|c| format!("{c:02X?}")).collect::<String>()
+        let mut result = String::new();
+        result += "#";
+        for c in c.iter() {
+            write!(result, "{c:02X?}").unwrap();
+        }
+        result
     }
     }
 
 
     fn get_brightness(m: &Rc<MouseData>) -> i32 {
     fn get_brightness(m: &Rc<MouseData>) -> i32 {

+ 1 - 1
packages/fullstack/src/adapters/mod.rs

@@ -112,7 +112,7 @@ impl Service for ServerFnHandler {
             let mut res = http::Response::builder();
             let mut res = http::Response::builder();
 
 
             // Set the headers from the server context
             // Set the headers from the server context
-            let parts = server_context.parts.read().unwrap();
+            let parts = server_context.response_parts().unwrap();
             *res.headers_mut().expect("empty headers should be valid") = parts.headers.clone();
             *res.headers_mut().expect("empty headers should be valid") = parts.headers.clone();
 
 
             let serialized = result?;
             let serialized = result?;

+ 6 - 5
packages/fullstack/src/launch.rs

@@ -119,21 +119,22 @@ impl<Props: Clone + serde::Serialize + serde::de::DeserializeOwned + Send + Sync
     }
     }
 
 
     #[cfg(feature = "web")]
     #[cfg(feature = "web")]
-    fn launch_web(self) {
+    /// Launch the web application
+    pub fn launch_web(self) {
         let cfg = self.web_cfg.hydrate(true);
         let cfg = self.web_cfg.hydrate(true);
         dioxus_web::launch_with_props(self.component, get_root_props_from_document().unwrap(), cfg);
         dioxus_web::launch_with_props(self.component, get_root_props_from_document().unwrap(), cfg);
     }
     }
 
 
     #[cfg(feature = "desktop")]
     #[cfg(feature = "desktop")]
-    fn launch_desktop(self) {
+    /// Launch the web application
+    pub fn launch_desktop(self) {
         let cfg = self.desktop_cfg;
         let cfg = self.desktop_cfg;
         dioxus_desktop::launch_with_props(self.component, self.props, cfg);
         dioxus_desktop::launch_with_props(self.component, self.props, cfg);
     }
     }
 
 
-    /// Launch a server with the given configuration
-    /// This will use the routing integration of the currently enabled integration feature
     #[cfg(feature = "ssr")]
     #[cfg(feature = "ssr")]
-    async fn launch_server(self) {
+    /// Launch a server application
+    pub async fn launch_server(self) {
         let addr = self.addr;
         let addr = self.addr;
         println!("Listening on {}", addr);
         println!("Listening on {}", addr);
         let cfg = self.server_cfg.build();
         let cfg = self.server_cfg.build();

+ 17 - 0
packages/generational-box/Cargo.toml

@@ -0,0 +1,17 @@
+[package]
+name = "generational-box"
+authors = ["Evan Almloff"]
+version = "0.0.0"
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+bumpalo = { version = "3.6" }
+
+[dev-dependencies]
+rand = "0.8.5"
+
+[features]
+default = ["check_generation"]
+check_generation = []

+ 34 - 0
packages/generational-box/README.md

@@ -0,0 +1,34 @@
+# Generational Box
+
+Generational Box is a runtime for Rust that allows any static type to implement `Copy`. It can be combined with a global runtime to create an ergonomic state solution like `dioxus-signals`. This crate contains no `unsafe` code.
+
+Three main types manage state in Generational Box:
+
+- Store: Handles recycling generational boxes that have been dropped. Your application should have one store or one store per thread.
+- Owner: Handles dropping generational boxes. The owner acts like a runtime lifetime guard. Any states that you create with an owner will be dropped when that owner is dropped.
+- GenerationalBox: The core Copy state type. The generational box will be dropped when the owner is dropped.
+
+Example:
+
+```rust
+// Create a store for this thread
+let store = Store::default();
+
+{
+    // Create an owner for some state for a scope
+    let owner = store.owner();
+
+    // Create some non-copy data, move it into a owner, and work with copy data
+    let data: String = "hello world".to_string();
+    let key = owner.insert(data);
+    
+    // The generational box can be read from and written to like a RefCell
+    let value = key.read();
+    assert_eq!(*value, "hello world");
+}
+// Reading value at this point will cause a panic
+```
+
+## How it works
+
+Internally 

+ 359 - 0
packages/generational-box/src/lib.rs

@@ -0,0 +1,359 @@
+#![doc = include_str!("../README.md")]
+#![warn(missing_docs)]
+
+use std::{
+    cell::{Cell, Ref, RefCell, RefMut},
+    fmt::Debug,
+    marker::PhantomData,
+    rc::Rc,
+};
+
+use bumpalo::Bump;
+
+/// # Example
+///
+/// ```compile_fail
+/// let data = String::from("hello world");
+/// let store = Store::default();
+/// let owner = store.owner();
+/// let key = owner.insert(&data);
+/// drop(data);
+/// assert_eq!(*key.read(), "hello world");
+/// ```
+#[allow(unused)]
+fn compile_fail() {}
+
+#[test]
+fn reused() {
+    let store = Store::default();
+    let first_ptr;
+    {
+        let owner = store.owner();
+        first_ptr = owner.insert(1).raw.data.as_ptr();
+        drop(owner);
+    }
+    {
+        let owner = store.owner();
+        let second_ptr = owner.insert(1234).raw.data.as_ptr();
+        assert_eq!(first_ptr, second_ptr);
+        drop(owner);
+    }
+}
+
+#[test]
+fn leaking_is_ok() {
+    let data = String::from("hello world");
+    let store = Store::default();
+    let key;
+    {
+        // create an owner
+        let owner = store.owner();
+        // insert data into the store
+        key = owner.insert(data);
+        // don't drop the owner
+        std::mem::forget(owner);
+    }
+    assert_eq!(key.try_read().as_deref(), Some(&"hello world".to_string()));
+}
+
+#[test]
+fn drops() {
+    let data = String::from("hello world");
+    let store = Store::default();
+    let key;
+    {
+        // create an owner
+        let owner = store.owner();
+        // insert data into the store
+        key = owner.insert(data);
+        // drop the owner
+    }
+    assert!(key.try_read().is_none());
+}
+
+#[test]
+fn works() {
+    let store = Store::default();
+    let owner = store.owner();
+    let key = owner.insert(1);
+
+    assert_eq!(*key.read(), 1);
+}
+
+#[test]
+fn insert_while_reading() {
+    let store = Store::default();
+    let owner = store.owner();
+    let key;
+    {
+        let data: String = "hello world".to_string();
+        key = owner.insert(data);
+    }
+    let value = key.read();
+    owner.insert(&1);
+    assert_eq!(*value, "hello world");
+}
+
+#[test]
+#[should_panic]
+fn panics() {
+    let store = Store::default();
+    let owner = store.owner();
+    let key = owner.insert(1);
+    drop(owner);
+
+    assert_eq!(*key.read(), 1);
+}
+
+#[test]
+fn fuzz() {
+    fn maybe_owner_scope(
+        store: &Store,
+        valid_keys: &mut Vec<GenerationalBox<String>>,
+        invalid_keys: &mut Vec<GenerationalBox<String>>,
+        path: &mut Vec<u8>,
+    ) {
+        let branch_cutoff = 5;
+        let children = if path.len() < branch_cutoff {
+            rand::random::<u8>() % 4
+        } else {
+            rand::random::<u8>() % 2
+        };
+
+        for i in 0..children {
+            let owner = store.owner();
+            let key = owner.insert(format!("hello world {path:?}"));
+            valid_keys.push(key);
+            path.push(i);
+            // read all keys
+            println!("{:?}", path);
+            for key in valid_keys.iter() {
+                let value = key.read();
+                println!("{:?}", value);
+                assert!(value.starts_with("hello world"));
+            }
+            #[cfg(any(debug_assertions, feature = "check_generation"))]
+            for key in invalid_keys.iter() {
+                assert!(!key.validate());
+            }
+            maybe_owner_scope(store, valid_keys, invalid_keys, path);
+            invalid_keys.push(valid_keys.pop().unwrap());
+            path.pop();
+        }
+    }
+
+    for _ in 0..10 {
+        let store = Store::default();
+        maybe_owner_scope(&store, &mut Vec::new(), &mut Vec::new(), &mut Vec::new());
+    }
+}
+
+/// The core Copy state type. The generational box will be dropped when the [Owner] is dropped.
+pub struct GenerationalBox<T> {
+    raw: MemoryLocation,
+    #[cfg(any(debug_assertions, feature = "check_generation"))]
+    generation: u32,
+    _marker: PhantomData<T>,
+}
+
+impl<T: 'static> Debug for GenerationalBox<T> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        #[cfg(any(debug_assertions, feature = "check_generation"))]
+        f.write_fmt(format_args!(
+            "{:?}@{:?}",
+            self.raw.data.as_ptr(),
+            self.generation
+        ))?;
+        #[cfg(not(any(debug_assertions, feature = "check_generation")))]
+        f.write_fmt(format_args!("{:?}", self.raw.data.as_ptr()))?;
+        Ok(())
+    }
+}
+
+impl<T: 'static> GenerationalBox<T> {
+    #[inline(always)]
+    fn validate(&self) -> bool {
+        #[cfg(any(debug_assertions, feature = "check_generation"))]
+        {
+            self.raw.generation.get() == self.generation
+        }
+        #[cfg(not(any(debug_assertions, feature = "check_generation")))]
+        {
+            true
+        }
+    }
+
+    /// Try to read the value. Returns None if the value is no longer valid.
+    pub fn try_read(&self) -> Option<Ref<'_, T>> {
+        self.validate()
+            .then(|| {
+                Ref::filter_map(self.raw.data.borrow(), |any| {
+                    any.as_ref()?.downcast_ref::<T>()
+                })
+                .ok()
+            })
+            .flatten()
+    }
+
+    /// Read the value. Panics if the value is no longer valid.
+    pub fn read(&self) -> Ref<'_, 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>> {
+        self.validate()
+            .then(|| {
+                RefMut::filter_map(self.raw.data.borrow_mut(), |any| {
+                    any.as_mut()?.downcast_mut::<T>()
+                })
+                .ok()
+            })
+            .flatten()
+    }
+
+    /// Write the value. Panics if the value is no longer valid.
+    pub fn write(&self) -> RefMut<'_, T> {
+        self.try_write().unwrap()
+    }
+
+    /// Set the value. Panics if the value is no longer valid.
+    pub fn set(&self, value: T) {
+        self.validate().then(|| {
+            *self.raw.data.borrow_mut() = Some(Box::new(value));
+        });
+    }
+
+    /// Returns true if the pointer is equal to the other pointer.
+    pub fn ptr_eq(&self, other: &Self) -> bool {
+        #[cfg(any(debug_assertions, feature = "check_generation"))]
+        {
+            self.raw.data.as_ptr() == other.raw.data.as_ptr() && self.generation == other.generation
+        }
+        #[cfg(not(any(debug_assertions, feature = "check_generation")))]
+        {
+            self.raw.data.as_ptr() == other.raw.data.as_ptr()
+        }
+    }
+}
+
+impl<T> Copy for GenerationalBox<T> {}
+
+impl<T> Clone for GenerationalBox<T> {
+    fn clone(&self) -> Self {
+        *self
+    }
+}
+
+#[derive(Clone, Copy)]
+struct MemoryLocation {
+    data: &'static RefCell<Option<Box<dyn std::any::Any>>>,
+    #[cfg(any(debug_assertions, feature = "check_generation"))]
+    generation: &'static Cell<u32>,
+}
+
+impl MemoryLocation {
+    #[allow(unused)]
+    fn drop(&self) {
+        let old = self.data.borrow_mut().take();
+        #[cfg(any(debug_assertions, feature = "check_generation"))]
+        if old.is_some() {
+            drop(old);
+            let new_generation = self.generation.get() + 1;
+            self.generation.set(new_generation);
+        }
+    }
+
+    fn replace<T: 'static>(&mut self, value: T) -> GenerationalBox<T> {
+        let mut inner_mut = self.data.borrow_mut();
+
+        let raw = Box::new(value);
+        let old = inner_mut.replace(raw);
+        assert!(old.is_none());
+        GenerationalBox {
+            raw: *self,
+            #[cfg(any(debug_assertions, feature = "check_generation"))]
+            generation: self.generation.get(),
+            _marker: PhantomData,
+        }
+    }
+}
+
+/// Handles recycling generational boxes that have been dropped. Your application should have one store or one store per thread.
+#[derive(Clone)]
+pub struct Store {
+    bump: &'static Bump,
+    recycled: Rc<RefCell<Vec<MemoryLocation>>>,
+}
+
+impl Default for Store {
+    fn default() -> Self {
+        Self {
+            bump: Box::leak(Box::new(Bump::new())),
+            recycled: Default::default(),
+        }
+    }
+}
+
+impl Store {
+    fn recycle(&self, location: MemoryLocation) {
+        location.drop();
+        self.recycled.borrow_mut().push(location);
+    }
+
+    fn claim(&self) -> MemoryLocation {
+        if let Some(location) = self.recycled.borrow_mut().pop() {
+            location
+        } else {
+            let data: &'static RefCell<_> = self.bump.alloc(RefCell::new(None));
+            MemoryLocation {
+                data,
+                #[cfg(any(debug_assertions, feature = "check_generation"))]
+                generation: self.bump.alloc(Cell::new(0)),
+            }
+        }
+    }
+
+    /// Create a new owner. The owner will be responsible for dropping all of the generational boxes that it creates.
+    pub fn owner(&self) -> Owner {
+        Owner {
+            store: self.clone(),
+            owned: Default::default(),
+        }
+    }
+}
+
+/// Owner: Handles dropping generational boxes. The owner acts like a runtime lifetime guard. Any states that you create with an owner will be dropped when that owner is dropped.
+pub struct Owner {
+    store: Store,
+    owned: Rc<RefCell<Vec<MemoryLocation>>>,
+}
+
+impl Owner {
+    /// Insert a value into the store. The value will be dropped when the owner is dropped.
+    pub fn insert<T: 'static>(&self, value: T) -> GenerationalBox<T> {
+        let mut location = self.store.claim();
+        let key = location.replace(value);
+        self.owned.borrow_mut().push(location);
+        key
+    }
+
+    /// Creates an invalid handle. This is useful for creating a handle that will be filled in later. If you use this before the value is filled in, you will get may get a panic or an out of date value.
+    pub fn invalid<T: 'static>(&self) -> GenerationalBox<T> {
+        let location = self.store.claim();
+        GenerationalBox {
+            raw: location,
+            #[cfg(any(debug_assertions, feature = "check_generation"))]
+            generation: location.generation.get(),
+            _marker: PhantomData,
+        }
+    }
+}
+
+impl Drop for Owner {
+    fn drop(&mut self) {
+        for location in self.owned.borrow().iter() {
+            self.store.recycle(*location)
+        }
+    }
+}

+ 2 - 0
packages/hooks/src/computed.rs

@@ -1,3 +1,5 @@
+//! Tracked and computed state in Dioxus
+
 use dioxus_core::{ScopeId, ScopeState};
 use dioxus_core::{ScopeId, ScopeState};
 use slab::Slab;
 use slab::Slab;
 use std::{
 use std::{

+ 1 - 2
packages/hooks/src/lib.rs

@@ -52,8 +52,7 @@ macro_rules! to_owned {
     };
     };
 }
 }
 
 
-mod computed;
-pub use computed::*;
+pub mod computed;
 
 
 mod use_on_unmount;
 mod use_on_unmount;
 pub use use_on_unmount::*;
 pub use use_on_unmount::*;

+ 1 - 1
packages/hooks/src/use_on_unmount.rs

@@ -1,4 +1,4 @@
-/// Creats 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)
+/// 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)
 ///
 ///
 /// Example:
 /// Example:
 /// ```rust
 /// ```rust

+ 14 - 0
packages/hooks/src/use_shared_state.rs

@@ -282,6 +282,20 @@ impl<T> UseSharedState<T> {
             ),
             ),
         }
         }
     }
     }
+
+    /// Take a reference to the inner value temporarily and produce a new value
+    #[cfg_attr(debug_assertions, track_caller)]
+    #[cfg_attr(debug_assertions, inline(never))]
+    pub fn with<O>(&self, immutable_callback: impl FnOnce(&T) -> O) -> O {
+        immutable_callback(&*self.read())
+    }
+
+    /// Take a mutable reference to the inner value temporarily and produce a new value
+    #[cfg_attr(debug_assertions, track_caller)]
+    #[cfg_attr(debug_assertions, inline(never))]
+    pub fn with_mut<O>(&self, mutable_callback: impl FnOnce(&mut T) -> O) -> O {
+        mutable_callback(&mut *self.write())
+    }
 }
 }
 
 
 impl<T> Clone for UseSharedState<T> {
 impl<T> Clone for UseSharedState<T> {

+ 1 - 1
packages/html/Cargo.toml

@@ -17,7 +17,7 @@ serde_repr = { version = "0.1", optional = true }
 wasm-bindgen = { workspace = true, optional = true }
 wasm-bindgen = { workspace = true, optional = true }
 euclid = "0.22.7"
 euclid = "0.22.7"
 enumset = "1.0.11"
 enumset = "1.0.11"
-keyboard-types = "0.6.2"
+keyboard-types = "0.7"
 async-trait = "0.1.58"
 async-trait = "0.1.58"
 serde-value = "0.7.0"
 serde-value = "0.7.0"
 tokio = { workspace = true, features = ["fs", "io-util"], optional = true }
 tokio = { workspace = true, features = ["fs", "io-util"], optional = true }

+ 2 - 0
packages/html/src/elements.rs

@@ -1258,6 +1258,8 @@ builder_constructors! {
         spellcheck: BoolOrDefault DEFAULT,
         spellcheck: BoolOrDefault DEFAULT,
         wrap: Wrap DEFAULT,
         wrap: Wrap DEFAULT,
         value: String volatile,
         value: String volatile,
+
+        initial_value: String DEFAULT,
     };
     };
 
 
 
 

+ 1 - 1
packages/html/src/eval.rs

@@ -42,7 +42,7 @@ pub fn use_eval(cx: &ScopeState) -> &EvalCreator {
         Rc::new(move |script: &str| {
         Rc::new(move |script: &str| {
             eval_provider
             eval_provider
                 .new_evaluator(script.to_string())
                 .new_evaluator(script.to_string())
-                .map(|evaluator| UseEval::new(evaluator))
+                .map(UseEval::new)
         }) as Rc<dyn Fn(&str) -> Result<UseEval, EvalError>>
         }) as Rc<dyn Fn(&str) -> Result<UseEval, EvalError>>
     })
     })
 }
 }

+ 10 - 1
packages/html/src/events/form.rs

@@ -1,4 +1,4 @@
-use std::{collections::HashMap, fmt::Debug};
+use std::{any::Any, collections::HashMap, fmt::Debug};
 
 
 use dioxus_core::Event;
 use dioxus_core::Event;
 
 
@@ -45,6 +45,12 @@ impl FileEngine for SerializedFileEngine {
             .await
             .await
             .map(|bytes| String::from_utf8_lossy(&bytes).to_string())
             .map(|bytes| String::from_utf8_lossy(&bytes).to_string())
     }
     }
+
+    async fn get_native_file(&self, file: &str) -> Option<Box<dyn Any>> {
+        self.read_file(file)
+            .await
+            .map(|val| Box::new(val) as Box<dyn Any>)
+    }
 }
 }
 
 
 #[cfg(feature = "serialize")]
 #[cfg(feature = "serialize")]
@@ -89,6 +95,9 @@ pub trait FileEngine {
 
 
     // read a file to string
     // read a file to string
     async fn read_file_to_string(&self, file: &str) -> Option<String>;
     async fn read_file_to_string(&self, file: &str) -> Option<String>;
+
+    // returns a file in platform's native representation
+    async fn get_native_file(&self, file: &str) -> Option<Box<dyn Any>>;
 }
 }
 
 
 impl_event! {
 impl_event! {

+ 6 - 0
packages/html/src/native_bind/native_file_engine.rs

@@ -1,3 +1,4 @@
+use std::any::Any;
 use std::path::PathBuf;
 use std::path::PathBuf;
 
 
 use crate::FileEngine;
 use crate::FileEngine;
@@ -40,4 +41,9 @@ impl FileEngine for NativeFileEngine {
 
 
         Some(contents)
         Some(contents)
     }
     }
+
+    async fn get_native_file(&self, file: &str) -> Option<Box<dyn Any>> {
+        let file = File::open(file).await.ok()?;
+        Some(Box::new(file))
+    }
 }
 }

+ 1 - 1
packages/native-core/Cargo.toml

@@ -12,7 +12,7 @@ authors = ["Jonathan Kelley", "Evan Almloff"]
 [dependencies]
 [dependencies]
 dioxus-core = { workspace = true, optional = true }
 dioxus-core = { workspace = true, optional = true }
 
 
-keyboard-types = "0.6.2"
+keyboard-types = "0.7"
 smallvec = "1.6"
 smallvec = "1.6"
 rustc-hash = { workspace = true }
 rustc-hash = { workspace = true }
 anymap = "1.0.0-beta.2"
 anymap = "1.0.0-beta.2"

+ 0 - 1
packages/native-core/src/real_dom.rs

@@ -446,7 +446,6 @@ impl<V: FromAnyValue + Send + Sync> RealDom<V> {
             drop(tree);
             drop(tree);
             children.reverse();
             children.reverse();
             if let Some(node) = self.get_mut(id) {
             if let Some(node) = self.get_mut(id) {
-                let node = node;
                 f(node);
                 f(node);
                 stack.extend(children.iter());
                 stack.extend(children.iter());
             }
             }

+ 2 - 2
packages/native-core/src/utils/persistant_iterator.rs

@@ -354,7 +354,7 @@ fn persist_removes() {
     // "3"
     // "3"
     iter2.next(&rdom).id();
     iter2.next(&rdom).id();
 
 
-    vdom.mark_dirty(ScopeId(0));
+    vdom.mark_dirty(ScopeId::ROOT);
     let update = vdom.render_immediate();
     let update = vdom.render_immediate();
     println!("{update:#?}");
     println!("{update:#?}");
     dioxus_state.apply_mutations(&mut rdom, update);
     dioxus_state.apply_mutations(&mut rdom, update);
@@ -419,7 +419,7 @@ fn persist_instertions_before() {
     // "2"
     // "2"
     iter.next(&rdom).id();
     iter.next(&rdom).id();
 
 
-    vdom.mark_dirty(ScopeId(0));
+    vdom.mark_dirty(ScopeId::ROOT);
     let update = vdom.render_immediate();
     let update = vdom.render_immediate();
     dioxus_state.apply_mutations(&mut rdom, update);
     dioxus_state.apply_mutations(&mut rdom, update);
 
 

+ 3 - 2
packages/native-core/tests/fuzzing.rs

@@ -187,7 +187,7 @@ fn create_random_dynamic_node(cx: &ScopeState, depth: usize) -> DynamicNode {
                 node_paths: &[&[0]],
                 node_paths: &[&[0]],
                 attr_paths: &[],
                 attr_paths: &[],
             }),
             }),
-            root_ids: Default::default(),
+            root_ids: dioxus::core::exports::bumpalo::collections::Vec::new_in(cx.bump()).into(),
             dynamic_nodes: cx.bump().alloc([cx.component(
             dynamic_nodes: cx.bump().alloc([cx.component(
                 create_random_element,
                 create_random_element,
                 DepthProps { depth, root: false },
                 DepthProps { depth, root: false },
@@ -257,7 +257,8 @@ fn create_random_element(cx: Scope<DepthProps>) -> Element {
                 key: None,
                 key: None,
                 parent: None,
                 parent: None,
                 template: Cell::new(template),
                 template: Cell::new(template),
-                root_ids: Default::default(),
+                root_ids: dioxus::core::exports::bumpalo::collections::Vec::new_in(cx.bump())
+                    .into(),
                 dynamic_nodes: {
                 dynamic_nodes: {
                     let dynamic_nodes: Vec<_> = dynamic_node_types
                     let dynamic_nodes: Vec<_> = dynamic_node_types
                         .iter()
                         .iter()

+ 2 - 1
packages/router-macro/src/lib.rs

@@ -482,7 +482,8 @@ impl RouteEnum {
                     let route = s;
                     let route = s;
                     let (route, _hash) = route.split_once('#').unwrap_or((route, ""));
                     let (route, _hash) = route.split_once('#').unwrap_or((route, ""));
                     let (route, query) = route.split_once('?').unwrap_or((route, ""));
                     let (route, query) = route.split_once('?').unwrap_or((route, ""));
-                    let mut segments = route.split('/');
+                    let query = dioxus_router::exports::urlencoding::decode(query).unwrap_or(query.into());
+                    let mut segments = route.split('/').map(|s| dioxus_router::exports::urlencoding::decode(s).unwrap_or(s.into()));
                     // skip the first empty segment
                     // skip the first empty segment
                     if s.starts_with('/') {
                     if s.starts_with('/') {
                         let _ = segments.next();
                         let _ = segments.next();

+ 1 - 1
packages/router-macro/src/query.rs

@@ -14,7 +14,7 @@ impl QuerySegment {
         let ident = &self.ident;
         let ident = &self.ident;
         let ty = &self.ty;
         let ty = &self.ty;
         quote! {
         quote! {
-            let #ident = <#ty as dioxus_router::routable::FromQuery>::from_query(query);
+            let #ident = <#ty as dioxus_router::routable::FromQuery>::from_query(&*query);
         }
         }
     }
     }
 
 

Vissa filer visades inte eftersom för många filer har ändrats