CroftSoft / Library / Tutorials

Rust-Dioxus Project Setup

2024 Jan 21 Sun

David Wallace Croft


Contents

Summary

This article provides step-by-step instructions for using the Rust programming language and the Dioxus user interface (UI) library to make a web application. As explained in the overview, the project structure supports static prerendering with client-side hydration.

Overview

A single page application (SPA) is a web application that downloads to the client web browser as JavaScript or WebAssembly (Wasm) code with minimal HTML. Once the code starts running in the browser, it generates the HTML that it needs for the SPA dynamically as the user navigates through the application. The SPA code can also request data from a server and integrate into the dynamically generated webpages client-side.

In contrast to web applications that require server-side rendering (SSR) to integrate the HTML and data, an SPA can be served from a Content Delivery Network (CDN). Unlike a static website served from a CDN, a search engine crawler cannot load and read the webpages directly to find content since the SPA is delivered as code which must be run in a client to dynamically generate the HTML. For this reason, some prefer SSR for search engine optimization (SEO).

For SEO, some tools let you prerender the static webpages for an SPA. Once a static webpage and the SPA code are downloaded into the browser, the SPA code integrates with the static webpage in a process called client-side hydration. This allows search engine crawlers to find HTML content directly from the CDN while also permitting the SPA code to run in the browser.

Dioxus is a UI library for the Rust programming language which can be deployed as an SPA by compiling the Rust source code to Wasm. This article provides step-by-step instructions for setting up a Rust-Dioxus project to make an SPA. The project structure supports static prerendering with client-side hydration so that the SPA can be served from a CDN.

Project Setup

  1. Make a Rust project
    • Start by following the instructions in the Rust Project Setup tutorial
      • Then return to this webpage and proceed to the next step
  2. Install the Dioxus Command-Line Interface (CLI) "dx" cargo install dioxus-cli dx --version
  3. Change your working directory to the new project directory cd project-name/
  4. Open the project directory in your code editor code .
  5. Use the Dioxus CLI to create the Dioxus configuration file Dioxus.toml dx config init project-name
  6. Use the cargo CLI to add the Dioxus dependencies to your Cargo.toml file cargo add dioxus cargo add dioxus-web
  7. Replace src/main.rs use ::dioxus::prelude::*; fn main() { ::dioxus_web::launch(App); } #[allow(non_snake_case)] fn App(cx: Scope) -> Element { cx.render(rsx! { div { "Hello, world!" } }) }
  8. Overwrite your project root directory .gitignore file with the following: /dist /target
  9. Compile and serve the default project dx serve --hot-reload
  10. Test the code
  11. Test the hot reload feature
    • In src/main.rs, change "world" to "World"
    • Observe that the browser automatically updates when you save your change
  12. Stop the development server by pressing Control-C in the command-line terminal
  13. Make a git commit of the Rust-Dioxus project in its current state
    • Later you can use git to compare the following customizations to this commit
    git add . git commit -m 'Dioxus hello world'
  14. Push your commit to your remote repository git push
  15. Insert or overwrite the following sections of your Cargo.toml file
    • Do not overwrite the package section of your Cargo.toml file
    • The referenced "prerender" binary source code will be added in a following step
    [[bin]] name = "prerender" required-features = ["prerender"] [dependencies] console_error_panic_hook = "0.1.7" dioxus = "=0.4.3" dioxus-fullstack = { version = "=0.4.3", optional = true } dioxus-router = "=0.4.3" dioxus-web = "=0.4.3" log = "0.4.20" serde = "1.0.195" tokio = { version = "1.35.1", features = ["full"], optional = true } wasm-logger = "0.2.0" [features] hydrate = ["dioxus-fullstack/router", "dioxus-web/hydrate"] prerender = ["dioxus-fullstack/router", "dioxus-fullstack/ssr", "tokio"]
  16. Make a child directory for your static assets such as stylesheets and images mkdir assets/
  17. Make an empty CSS stylesheet file in the assets directory touch assets/stylesheet.css
  18. Overwrite your Dioxus configuration file Dioxus.toml with the following:
    • Update the placeholder value for the "name" property in the application section
    • Replace the "title" property value with what you want to go on the browser tab
    [application] asset_dir = "assets" default_platform = "web" name = "project-name" out_dir = "dist" [web.app] title = "Project Name" [web.watcher] reload_html = true watch_path = ["src", "assets"] [web.resource] style = ["/stylesheet.css"] script = [] [web.resource.dev] script = [] style = []
  19. Replace the code for src/main.rs again
    • Replace the placeholder value "project_name" in two places in the following
    • Use an underscore instead of a hyphen when you replace "project_name"
    #[cfg(feature = "hydrate")] use ::dioxus_fullstack::prelude::*; #[cfg(feature = "hydrate")] use ::dioxus_fullstack::router::{FullstackRouterConfig, RouteWithCfg}; #[cfg(feature = "hydrate")] use ::dioxus_web::Config; #[cfg(feature = "hydrate")] use project_name::route::Route; #[cfg(feature = "hydrate")] fn main() { let root_properties: FullstackRouterConfig<Route> = get_root_props_from_document() .expect("Failed to get root properties from document"); let config = Config::default().hydrate(true); ::dioxus_web::launch_with_props( RouteWithCfg::<Route>, root_properties, config, ); } #[cfg(not(feature = "hydrate"))] fn main() { project_name::launch(); }
  20. Make src/lib.rs use self::components::app::App; use ::log::Level; use ::wasm_logger::Config; pub mod components; pub mod route; pub fn launch() { let config = Config::new(Level::Debug); ::wasm_logger::init(config); ::dioxus_web::launch(App) }
  21. Make src/route.rs use super::components::colophon::Colophon; use super::components::home::Home; use super::components::page_not_found::PageNotFound; use super::components::template::Template; use ::dioxus::prelude::*; use ::dioxus_router::prelude::*; use ::serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Deserialize, PartialEq, Routable, Serialize)] pub enum Route { #[layout(Template)] #[route("/")] Home {}, #[route("/colophon")] Colophon {}, #[end_layout] #[route("/:..route")] PageNotFound { route: Vec<String>, }, }
  22. Make the binaries subdirectory mkdir src/bin/
  23. Make src/bin/prerender.rs
    • This is the code to support static prerendering
    • Replace the placeholder value "project_name"
    • Use an underscore instead of a hyphen when you replace "project_name"
    use ::dioxus_fullstack::prelude::*; use ::dioxus_fullstack::router::FullstackRouterConfig; use project_name::route::Route; const DIST: &str = "dist"; #[tokio::main] async fn main() { let fullstack_router_config = FullstackRouterConfig::<Route>::default(); let incremental_renderer_config = IncrementalRendererConfig::default().static_dir(DIST); let serve_config: ServeConfig<FullstackRouterConfig<Route>> = ServeConfigBuilder::new_with_router(fullstack_router_config) .assets_path(DIST) .incremental(incremental_renderer_config) .build(); pre_cache_static_routes_with_props(&serve_config) .await .unwrap(); }
  24. Make the components subdirectory mkdir src/components/
  25. Make src/components/mod.rs pub mod app; pub mod colophon; pub mod high_five; pub mod home; pub mod nav; pub mod page_not_found; pub mod template;
  26. Make src/components/app.rs use super::super::route::Route; use ::dioxus::prelude::*; use ::dioxus_router::prelude::*; #[allow(non_snake_case)] pub fn App(cx: Scope) -> Element { render! { Router::<Route> { } } }
  27. Make src/components/colophon.rs use super::high_five::HighFive; use ::dioxus::prelude::*; #[allow(non_snake_case)] pub fn Colophon(cx: Scope) -> Element { render! { h1 { "Colophon Page" } p { "This website was created using the Rust library ", a { href: "https://dioxuslabs.com/", target: "_blank", "Dioxus", }, "." } HighFive { } } }
  28. Make src/components/high_five.rs use ::dioxus::prelude::*; #[allow(non_snake_case)] pub fn HighFive(cx: Scope) -> Element { let mut count = use_state(cx, || 0); render! { h1 { "High-Five counter: {count}" } button { onclick: move |_| count += 1, "Up high!" } button { onclick: move |_| count -= 1, "Down low!" } } }
  29. Make src/components/home.rs use super::high_five::HighFive; use ::dioxus::prelude::*; #[allow(non_snake_case)] pub fn Home(cx: Scope) -> Element { render! { h1 { "Home Page" } p { "This line is a placeholder for home page content." } HighFive { } } }
  30. Make src/components/nav.rs use super::super::route::Route; use ::dioxus::prelude::*; use ::dioxus_router::prelude::*; #[allow(non_snake_case)] pub fn Nav(cx: Scope) -> Element { render! { nav { ul { li { Link { to: Route::Home {}, "Home" } } li { Link { to: Route::Colophon {}, "Colophon" } } } } } }
  31. Make src/components/page_not_found.rs use ::dioxus::prelude::*; #[allow(non_snake_case)] #[component] pub fn PageNotFound( cx: Scope, route: Vec<String>, ) -> Element { render! { h1 { "Page Not Found" } pre { color: "red", "{route:?}" } } }
  32. Make src/components/template.rs use super::super::route::Route; use super::nav::Nav; use ::dioxus::prelude::*; use ::dioxus_router::prelude::*; #[allow(non_snake_case)] pub fn Template(cx: Scope) -> Element { render! { Nav { } Outlet::<Route> {} } }
  33. Compile and serve the customized project dx serve --hot-reload
  34. Test the code
    • Open your browser to http://localhost:8080/
    • Click on the buttons on the Home page to verify that the Wasm is working
    • Click on the "Colophon" link in the navigation section
    • Click on the buttons on the Colophon page to verify that the Wasm is working
  35. View the page source
    • Click on the "Home" link in the navigation section
    • Right-click on a webpage and select "View Page Source"
    • Note how the page content is not visible within the HTML
    • This will be fixed in a later step
  36. Attempt to open the Colophon page directly in your browser
  37. Stop the development server by pressing Control-C in the command-line terminal
  38. Build the Single Page Application (SPA) with client-side hydration
    • This will recreate your distribution directory dist/
    dx build --features=hydrate --release
  39. Insert the prerendered static HyperText Markup Language (HTML)
    • This will update the HTML in dist/
    cargo run --bin prerender --features=prerender --release
  40. Install Node.js
    • See the installation instructions at https://nodejs.org/
    • The Node.js installation includes the Node Package Manager (npm) and npx CLIs
    • The npm CLI is used in the next major section which is optional
    • The npx CLI is used in the following step to execute a development HTTP server
    npm --version npx --version
  41. Serve the HTML and Wasm from your distribution directory
    • The following command will automatically open your browser
    npx http-server dist -o
  42. Verify that the webpage includes the text content
    • Press the reload button on your browser while holding the Shift key
    • Right-click on a webpage and select "View Page Source"
    • Note how the text content is visible within the HTML for search engines to find
  43. Open the Colophon page directly in your browser
    • Enter the URL http://localhost:8080/colophon/ in your browser toolbar
    • Note that the page loads without having to go to the Home page first
    • Click the buttons to verify that the Wasm is working
  44. Stop the development server by pressing Control-C in the command-line terminal
  45. Make a git commit of the Rust-Dioxus project in its current state
    git add . git commit -m 'Rust-Dioxus project setup'
  46. Push your commit to your remote repository git push

NPM Run Scripts

This optional section describes how to add Node Package Manager (npm) run scripts to make development and testing of your Dioxus project easier.

  1. Insert the following line into the project root directory .gitignore file
    • Keep the pre-existing lines in the .gitignore file
    /node_modules
  2. Make a package.json file in your project root directory { "devDependencies": { "http-server": "^14.1.1", "prettier": "3.2.4", "rimraf": "^5.0.5" }, "scripts": { "clean": "rimraf dist", "dist": "npm run clean && npm run make", "format": "prettier dist --ignore-path .prettierignore --write", "hydrate": "dx build --features=hydrate --release", "prerender": "cargo run --bin prerender --features=prerender --release", "make": "npm run hydrate && npm run prerender && npm run format", "serve": "http-server dist -o", "start": "dx serve --hot-reload", "test": "npm run dist && npm run serve" } }
  3. Install the dependencies npm install
  4. Test your npm run scripts npm test
  5. Update the README.md in your project root directory
    • Replace the placeholder values such as "Project Name" and "project-name"
    # Project Name - A description of Project Name - Makes a Content Delivery Network (CDN)-compatible static HTML distribution - Includes static prerendering with client-side hydration ## Utilities Installation - Install the Rust command line utility "cargo" - cargo is installed when you install Rust - https://www.rust-lang.org/ - Install the Dioxus Command Line Interface (CLI) "dx" - cargo install dioxus-cli - https://github.com/DioxusLabs/dioxus/tree/master/packages/cli - Install npm - npm installs utilities such as prettier - npm scripts run the dx and cargo commands - npm can be installed by installing node.js - https://nodejs.org/ ## Hot Reload - cd project-name/ - npm install - Installs the utility http-server to serve the HTML - Installs the utility pretter to format the HTML - Installs the utility rimraf to remove distribution directory dist/ - npm start - Open your browser to http://localhost:8080/ - Make changes to the HTML in src/lib.rs or the CSS in public/stylesheet.css - Note that the changes are updated in your browser as soon as you save ## Test Static Prerendering with Hydration - npm test - Deletes the distribution directory dist/ to start clean - Makes the index.html page with the hydration code - Inserts the prerendered HTML - Formats the HTML using the prettier utility - Launches http-server to serve the HTML - Opens your browser to the home page ## Other Commands - npm run clean - Deletes the distribution directory dist/ to start clean - npm run dist - Same as npm test - Except that it does not start http-server and open the browser - npm run format - Runs the utility prettier - npm run hydrate - Makes the index.html page with the hydration code - npm run prerender - Inserts the prerendered HTML - npm run make - Makes the index.html page with the hydration code - Inserts the prerendered HTML - Runs the utility prettier - But does not start by deleting dist/ - npm run serve - Starts the http-server - Opens the browser ## Links - CroftSoft Rust-Dioxus Project Setup Tutorial - https://www.croftsoft.com/library/tutorials/rust-dioxus-project-setup/ ## History - Initial release: YYYY-MM-DD
  6. Stop the development server by pressing Control-C in the command-line terminal
  7. Make a git commit of the Rust-Dioxus project in its current state
    git add . git commit -m 'npm run scripts'
  8. Push your commit to your remote repository git push

Links

In recommended reading order:


© 2023 - 2024 CroftSoft Inc

 
 
 
CroftSoft
 
 
About
Library
- Books
- Code
- Courses
- Links
- Media
- Software
- Tutorials
People
Portfolio
Update
 
 
Google
CroftSoft Web