|
| 1 | +--- |
| 2 | +title: "From Server-side to Browser-based Shiny Apps and Back Again" |
| 3 | +subtitle: "Understanding Shiny to Shinylive Conversion and Source Extraction" |
| 4 | +author: "James Joseph Balamuta" |
| 5 | +vignette: > |
| 6 | + %\VignetteIndexEntry{behind-the-scenes} |
| 7 | + %\VignetteEngine{quarto::html} |
| 8 | + %\VignetteEncoding{UTF-8} |
| 9 | +--- |
| 10 | + |
| 11 | +[Shinylive][slexplain] represents a paradigm shift in how Shiny applications |
| 12 | +are deployed and executed. Instead of running on a **computational** server, |
| 13 | +these applications execute entirely within a web browser after being downloaded. |
| 14 | +This vignette explores the transformation process from a traditional Shiny |
| 15 | +application to a Shinylive application and explains how tools like [`peeky`][peekygh] |
| 16 | +can extract the source code from these browser-based applications. |
| 17 | + |
| 18 | +## The Traditional Shiny Model |
| 19 | + |
| 20 | +In a traditional Shiny application: |
| 21 | + |
| 22 | +1. Source code resides on a **computational** server |
| 23 | +2. R and/or Python are installed and running on the server |
| 24 | +3. Users interact with the app through their browser, which communicates and |
| 25 | + processes the interaction on the server. |
| 26 | +4. App code remains **private** and **inaccessible** to end users |
| 27 | + |
| 28 | +For example, consider a template for an R Shiny application: |
| 29 | + |
| 30 | +```r |
| 31 | +# Traditional Shiny app structure |
| 32 | +# app.R or (ui.R/server.R) |
| 33 | +library(shiny) |
| 34 | + |
| 35 | +ui <- fluidPage( |
| 36 | + # UI elements |
| 37 | +) |
| 38 | + |
| 39 | +server <- function(input, output, session) { |
| 40 | + # Server logic |
| 41 | +} |
| 42 | + |
| 43 | +shinyApp(ui, server) |
| 44 | +``` |
| 45 | + |
| 46 | +Once deployed, the server executes the application code and sends the results |
| 47 | +back to the user's browser. This code is **not** accessible to the user since |
| 48 | +it runs on the server's R process. |
| 49 | + |
| 50 | +## The Shinylive Transformation |
| 51 | + |
| 52 | +One of the key features of Shinylive is the ability to convert traditional Shiny |
| 53 | +applications to run entirely in the browser. This transformation involves |
| 54 | +arguably three key steps: Code Preparation, Conversion Process, and Browser |
| 55 | +Deployment. |
| 56 | + |
| 57 | +### Step 1: Code Preparation |
| 58 | + |
| 59 | +The first step in converting to Shinylive involves preparing your Shiny |
| 60 | +application code. The core application logic remains largely unchanged, but |
| 61 | +there are some key considerations: |
| 62 | + |
| 63 | +- All dependencies must be available in WebAssembly format |
| 64 | + - Check if the R package is in the [webR package repository][webrpkg] (mobile intensive) |
| 65 | + - See if the Python package is apart of the [built-in Pyodide packages][pyodidepkg] or |
| 66 | + has a **pure Python** implementation that can be used directly from PyPI. |
| 67 | +- File paths need to be adjusted for browser context |
| 68 | +- External resources must be properly bundled |
| 69 | + |
| 70 | +### Step 2: Conversion Process |
| 71 | + |
| 72 | +The conversion to Shinylive is automated by the |
| 73 | + |
| 74 | +1. **File Bundling**: All application files are collected and bundled together: |
| 75 | + - R/Python source code |
| 76 | + - Data files |
| 77 | + - Static assets (images, CSS, etc.) |
| 78 | + |
| 79 | +2. **Metadata Generation**: A `app.json` file is created containing: |
| 80 | + ```json |
| 81 | + [ |
| 82 | + { |
| 83 | + "name": "app.R", |
| 84 | + "content": "library(shiny)\n...", |
| 85 | + "type": "text" |
| 86 | + }, |
| 87 | + { |
| 88 | + "name": "data.csv", |
| 89 | + "content": "col1,col2\n...", |
| 90 | + "type": "text" |
| 91 | + } |
| 92 | + ] |
| 93 | + ``` |
| 94 | + |
| 95 | +3. **Runtime Preparation**: |
| 96 | + - For R: WebR environment is configured |
| 97 | + - For Python: Pyodide environment is set up |
| 98 | + |
| 99 | +### Step 3: Browser Deployment |
| 100 | + |
| 101 | +The converted application now runs entirely in the browser: |
| 102 | + |
| 103 | +1. WebR/Pyodide runtime initializes |
| 104 | +2. Application files are loaded from `app.json` |
| 105 | +3. UI renders and connects to a local runtime |
| 106 | +4. All computation happens client-side |
| 107 | + |
| 108 | +## Source Code Transparency |
| 109 | + |
| 110 | +Unlike traditional Shiny applications, Shinylive apps are inherently transparent. This transparency is a fundamental characteristic due to several factors: |
| 111 | + |
| 112 | +1. **Client-Side Execution**: All code must be available in the browser |
| 113 | +2. **Bundled Resources**: All files are packaged in accessible formats |
| 114 | +3. **No Server Privacy**: No server-side code protection exists |
| 115 | + |
| 116 | +## Extracting Source Code with `peeky` |
| 117 | + |
| 118 | +The `peeky` package leverages this transparency to extract source code from |
| 119 | +Shinylive applications. Here's how it works: |
| 120 | + |
| 121 | +### Detection |
| 122 | + |
| 123 | +TODO: Clarify |
| 124 | + |
| 125 | +```r |
| 126 | +# URL of the Shinylive application |
| 127 | +url <- "" |
| 128 | + |
| 129 | +# Auto-detect and handle both standalone and Quarto-embedded apps |
| 130 | +peeky::peek_shinylive_app(url) |
| 131 | +``` |
| 132 | + |
| 133 | +### Content Retrieval |
| 134 | + |
| 135 | +TODO: Clarify |
| 136 | + |
| 137 | +1. Downloads the webpage or app.json |
| 138 | +2. Identifies Shinylive components |
| 139 | +3. Extracts embedded code and resources |
| 140 | + |
| 141 | +### File Reconstruction |
| 142 | + |
| 143 | +By default, extracted content is reconstructed into runnable applications that |
| 144 | +have the directory structure of a standalone app with all of its components. |
| 145 | +If the application is embedded in a Quarto document, the extracted content can |
| 146 | +consist of one or more Shiny applications and, thus, is reconstructed into a |
| 147 | +into subdirectories, e.g. `app_1`, `app_2`, etc. Or, the content can be |
| 148 | +reconstructed into a new Quarto document containing just the applications. |
| 149 | + |
| 150 | +This can be done by specifying the `output_format` argument in |
| 151 | +`peek_quarto_shinylive_app()`: |
| 152 | + |
| 153 | +```r |
| 154 | +# Extract to directory structure |
| 155 | +peeky::peek_quarto_shinylive_app(url, output_format = "app-dir") |
| 156 | + |
| 157 | +# Or create a new Quarto document |
| 158 | +peeky::peek_quarto_shinylive_app(url, output_format = "quarto") |
| 159 | +``` |
| 160 | + |
| 161 | +## Security Implications |
| 162 | + |
| 163 | +This transparency has important implications for developers: |
| 164 | + |
| 165 | +1. **No Code Privacy**: All source code is accessible to users |
| 166 | +2. **Data Visibility**: Bundled datasets are accessible |
| 167 | +3. **API Keys**: Never include sensitive credentials |
| 168 | +4. **Business Logic**: Proprietary algorithms are visible |
| 169 | + |
| 170 | +## Best Practices |
| 171 | + |
| 172 | +When developing Shinylive applications: |
| 173 | + |
| 174 | +1. **Assume Transparency**: Design with the understanding that code is visible |
| 175 | +2. **Handle Sensitive Data**: Process sensitive data server-side if needed |
| 176 | +3. **License Clearly**: Include clear licensing terms |
| 177 | +4. **Consider Hybrid Approaches**: Use traditional Shiny for sensitive components |
| 178 | + and offload non-sensitive parts to Shinylive. |
| 179 | + |
| 180 | +## Conclusion |
| 181 | + |
| 182 | +The transformation from traditional Shiny to Shinylive represents a fundamental |
| 183 | +shift in how web applications are delivered. While this brings advantages in |
| 184 | +deployment and accessibility, it also requires developers to embrace |
| 185 | +transparency and adjust their development practices accordingly. Tools like |
| 186 | +`peeky` help demonstrate this transparency and can assist in understanding |
| 187 | +how Shinylive applications work. |
| 188 | + |
| 189 | +## References |
| 190 | + |
| 191 | +1. [Shinylive overview][slexplain] |
| 192 | +2. [webR Documentation][webr] |
| 193 | + - [webR Package Repository][webrpkg] |
| 194 | +3. [Pyodide Documentation][pyodide] |
| 195 | + - [Pyodide built-in packages][pyodidepkg] |
| 196 | +4. [Quarto Shinylive Extension][qsl] |
| 197 | +5. [Shiny Standalone webR Demo][protoslr] |
| 198 | + |
| 199 | +[quarto]: https://quarto.org |
| 200 | +[slexplain]: https://shiny.posit.co/py/docs/shinylive.html |
| 201 | +[qsl]: https://github.com/quarto-ext/shinylive |
| 202 | +[webr]: https://docs.r-wasm.org/webr/latest/ |
| 203 | +[webrpkg]: https://repos.r-wasm.org |
| 204 | +[pyodide]: https://pyodide.org/en/stable/ |
| 205 | +[pyodidepkg]: https://pyodide.org/en/stable/usage/packages-in-pyodide.html |
| 206 | +[peeksl]: https://github.com/coatless-tutorials/peeking-at-an-r-shinylive-app-source-code |
| 207 | +[wasm]: https://webassembly.org/ |
| 208 | +[peekygh]: https://github.com/coatless-rpkg/peeky |
| 209 | +[protoslr]: https://github.com/georgestagg/shiny-standalone-webr-demo |
0 commit comments