Create Custom Shiny Inputs and Outputs with Rust, Leptos, and WebAssembly
shiny.leptos provides a framework and tooling within R to build custom Shiny input and output components using the Leptos Rust framework, compiling them to WebAssembly (WASM) for high-performance, interactive front-end elements within your Shiny applications.
This package helps you:
- Initialize: Set up the necessary directory structure (
srcrs,srcts,srcsass) and configuration files (Cargo.toml, package.json, vite.config.js, etc.) for Rust/Leptos and TypeScript development within your R package. - Scaffold: Generate template files (Rust, TypeScript, R) for new custom input or output components.
- Build: Compile your Rust/Leptos code to WASM, bundle TypeScript bindings using Vite, and compile Sass/SCSS stylesheets into a single CSS file, placing the outputs in the correct
inst/subdirectory for your R package.
You can install the development version of shiny.leptos from GitHub with:
# install.packages("remotes")
remotes::install_github("ixpantia/shiny.leptos")Prerequisites for Development:
To create components using shiny.leptos within your own R package, you will need the following installed on your system:
- Rust Toolchain: Install via rustup.
- WASM Target for Rust:
rustup target add wasm32-unknown-unknown wasm-pack:cargo install wasm-pack- Node.js and Yarn: Install Node.js (which includes npm) from nodejs.org, then install Yarn:
npm install -g yarn - Sass: Install the Dart Sass executable (see sass-lang.com/install). Ensure the
sasscommand is available in your system's PATH.
-
Initialize your Package: Navigate to the root directory of your R package in the terminal or R console and run:
shiny.leptos::init()
This sets up the
srcrs/,srcts/,srcsass/,out/directories and necessary config files. It also adds these directories to your.Rbuildignore. -
Create a New Component:
- Input Component: Use
new_input_component()to generate the basic files. For example, to create a component namedmy_counter:This will createshiny.leptos::new_input_component("my_counter")
R/my_counter.R,srcrs/src/my_counter.rs, andsrcts/src/my_counter.ts. - Output Component: Use
new_output_component()similarly. For example, to createmy_plot:This will createshiny.leptos::new_output_component("my_plot")
R/my_plot.R(withmy_plot_outputandrender_my_plot),srcrs/src/my_plot.rs, andsrcts/src/my_plot.ts. - Both functions update
srcrs/src/lib.rsandsrcts/src/index.tsto include the new component.
- Input Component: Use
-
Implement Your Component:
- Edit the generated
.rsfile (srcrs/src/) to implement the desired appearance and behavior using Leptos. - Modify the generated
.tsfile (srcts/src/) if the default Shiny input/output binding needs adjustments. - Customize the generated
.Rfile (R/) to set appropriate default values or add specific arguments to the R functions (UI, update/render). - Optionally, add styles for your component in
srcsass/.
- Edit the generated
-
Build Assets: From the root of your package, run the build command:
shiny.leptos::build() # Or run individual steps: # shiny.leptos::build_sass() # shiny.leptos::build_rs() # Called by build_ts # shiny.leptos::build_ts()
This compiles Sass, builds the Rust WASM package, installs/updates the WASM dependency, and bundles the TypeScript/JavaScript. The final assets (
*.js,style.css) are placed ininst/dist/. -
Document and Use:
- Run
devtools::document()to generate documentation and update your package'sNAMESPACE. - Use
devtools::install()to install your package locally. - Use your new component in a Shiny app like any other input or output!
- Run
Let's assume you created a leptos_button input component, implemented it, and built/installed the package yourPackageName.
# app.R
library(shiny)
library(yourPackageName) # Replace with your package name
ui <- fluidPage(
leptos_button("myButton1", value = 5),
hr(),
verbatimTextOutput("button1Value"),
actionButton("reset", "Reset Button 1")
)
server <- function(input, output, session) {
output$button1Value <- renderPrint({
paste("Button 1 Value:", input$myButton1)
})
observe({
update_leptos_button("myButton1", value = 0)
}) |> bindEvent(input$reset)
}
shinyApp(ui, server)Creating and using a custom output follows a similar pattern, using new_output_component, implementing the Rust view and R render/UI functions, building, and then using yourPackageName::my_output_output() in the UI and output$myOutput <- yourPackageName::render_my_output({...}) in the server.
init(): Initializes the required directory structure and configuration files.new_input_component(component_name): Creates scaffolding for a new input component.new_output_component(component_name): Creates scaffolding for a new output component.build(): Runs the full build process (Sass, Rust/WASM, TypeScript/Vite).
After running init(), your package will have these key directories:
srcrs/: Rust source code (Leptos components).srcts/: TypeScript source code (Shiny bindings).srcsass/: Sass/SCSS files for styling.out/: (Generated) Compiled WASM package, used bysrcts.inst/dist/: (Generated) Final JS and CSS assets for the R package.Cargo.toml: (Root) Rust workspace definition.R/: Generated R functions for UI and server interaction.
- ixpantia, SRL (Copyright Holder) [email protected]
- Andres Quintero (Author, Creator) [email protected]
MIT License See the LICENSE file for details.