#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| viewerHeight: 400
#| components: [editor, viewer]
ui <- fluidPage(
fileInput("upload", "Upload a file")
)
server <- function(input, output, session) {
}
shinyApp(ui, server)
Try in interactive mode adding / changing the arguments label, width, buttonLabel and placeholder to see how it affects the UI appearance.
The UI needed to support file uploads is simple: just add shiny::fileInput() to your UI.
Like most other UI components, there are only two required arguments: id and label. The width, buttonLabel and placeholder arguments allow you to tweak the appearance in other ways. I won’t discuss them here, but you can read more about them in File Upload Control — fileInput.
9.1.2 Server
Handling fileInput() on the server is a little more complicated than other inputs. Most inputs return simple vectors, but fileInput() returns a data frame with four columns:
name: the original file name on the user’s computer.
size: the file size, in bytes. By default, the user can only upload files up to 5 MB. You can increase this limit by setting the shiny.maxRequestSize option prior to starting Shiny. For example, to allow up to 10 MB run options(shiny.maxRequestSize = 10 * 1024^2).
type: the MIME type of the file. This is a formal specification of the file type that is usually derived from the extension and is rarely needed in Shiny apps.
datapath: the path to where the data has been uploaded on the server. Treat this path as ephemeral: if the user uploads more files, this file may be deleted. The data is always saved to a temporary directory and given a temporary name.
R Code 9.2 : File upload server
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| viewerHeight: 400
#| components: [editor, viewer]
#| layout: vertical
ui <- fluidPage(
fileInput("upload", NULL, buttonLabel = "Upload...", multiple = TRUE),
tableOutput("files")
)
server <- function(input, output, session) {
output$files <- renderTable(input$upload)
}
shinyApp(ui, server)
Watch out! 9.1
fileInput() does not show multiple uploaded files. One can see only the last one.
This issue was on May 2, 2021 opened at GitHub, but it is still not closed. I do not know with my rudimentary knowledge at the moment (2025-06-08) how to solve this problem.
9.1.3 Uploading data
If the user is uploading a dataset, there are two details that you need to be aware of:
input$upload is initialized to NULL on page load, so you’ll need req(input$upload) to make sure your code waits until the first file is uploaded.
The accept argument allows you to limit the possible inputs. The easiest way is to supply a character vector of file extensions, like accept = ".csv". But the accept argument is only a suggestion to the browser, and is not always enforced, so it’s good practice to also validate it (e.g. Section 8.1.1) yourself. The easiest way to get the file extension in R is tools::file_ext(), just be aware it removes the leading . from the extension.
Putting all these ideas together gives us the following app where you can upload a .csv or .tsv file and see the first n rows. See it in action in https://hadley.shinyapps.io/ms-upload-validate.
Note that since multiple = FALSE (the default), input$file will be a single row data frame, and input$file$name and input$file$datapath will be a length-1 character vector.
9.2 Donwloads
9.2.1 Basics
Again, the UI is straightforward: use either downloadButton(id) or downloadLink(id) to give the user something to click to download a file. You can customise their appearance using the same class and icon arguments as for actionButtons(), as described in Section 2.2.7.
Unlike other outputs, downloadButton() is not paired with a render function. Instead, you use downloadHandler().
downloadHandler() has two arguments, both functions:
filename should be a function with no arguments that returns a file name (as a string). The job of this function is to create the name that will be shown to the user in the download dialog box.
content should be a function with one argument, file, which is the path to save the file. The job of this function is to save the file in a place that Shiny knows about, so it can then send it to the user. This is an unusual interface, but it allows Shiny to control where the file should be saved (so it can be placed in a secure location) while you still control the contents of that file.
Next we’ll put these pieces together to show how to transfer data files or reports to the user.
9.2.2 Downloading data
The following app shows off the basics of data download by allowing you to download any dataset in the datasets package as a tab separated file.
Tip 9.1: Use .tsv instead of csv
I recommend using .tsv (tab separated value) instead of .csv (comma separated values) because many European countries use commas to separate the whole and fractional parts of a number (e.g. 1,23 vs 1.23). This means they can’t use commas to separate fields and instead use semi-colons in so-called “c”sv files! You can avoid this complexity by using tab separated files, which work the same way everywhere.
This workaround is not necessary in an app.R file.
Note the use of validate() to only allow the user to download datasets that are data frames. A better approach would be to pre-filter the list, but this lets you see another application of validate().
9.2.3 Downloading reports
9.2.3.1 Standard
As well as downloading data, you may want the users of your app to download a report that summarizes the result of interactive exploration in the Shiny app. This is quite a lot of work, because you also need to display the same information in a different format, but it is very useful for high-stakes apps.
title: My Documentoutput: html_documentparams: year: 2018 region: Europe printcode: TRUE data: file.csv
Inside the document, you can refer to these values using params$year, params$region etc. The values in the YAML metadata are defaults; you’ll generally override them by providing the params argument in a call to rmarkdown::render(). This makes it easy to generate many different reports from the same .Rmd.
Watch out! 9.3: Downloading reports is not working
The code chunk does not produce an error. But whenever you click the button “Generate report” the following message is created in a new browser window:
An error has occurred! pandoc version 1.12.3 or higher is required and was not found (see the help page ?rmarkdown::pandoc_available).
It’ll generally take at least a few seconds to render a .Rmd, so this is a good place to use a notification from Section 8.2.
There are a couple of other tricks worth knowing about:
RMarkdown works in the current working directory, which will fail in many deployment scenarios (e.g. on https:://shinyapps.io). You can work around this by copying the report to a temporary directory when your app starts (i.e. outside of the server function):
By default, RMarkdown will render the report in the current process, which means that it will inherit many settings from the Shiny app (like loaded packages, options, etc). For greater robustness, I recommend running render() in a separate R session using the {callr} package:
At first I got the message with a code version I can’t reproduce > Warning: Error in : ! in callr subprocess
> Caused by error in abs_path(input):
> ! The file ’/var/folders/sd/g6yc4rq1731__gh38rw8whvc0000gr/T//RtmplpyWiO/file422772b5bfd1.Rmd’ does not exist.
In the current code version I got an error message whenever I try to include the report.Rmd file.
error: object ‘params’ not found
Resource 9.1 : Downloading reports with shiny and looking for shinylive error messages
You can see all these pieces put together in rmarkdown-report/, found inside the Mastering Shiny GitHub repo. It works as Shiny app but with shinylive I got the error message reported in Warning 9.4.
The {shinymeta} package solves a related problem: sometimes you need to be able to turn the current state of a Shiny app into a reproducible report that can be re-run in the future. (Github Source, Documentation. My latest CRAN version 0.2.0.3 is from November 17, 2021. In the meantime there were 15 commits indicating that a new version is in preparation. preparation. )
Learn more about the {shinymeta} package in Joe Cheng’s useR! 2019 keynote, “Shiny’s holy grail1: Interactivity with reproducibility”. There is also the code for the example Shiny app available, featuring package download data from CRAN.The code for the example Shiny app is available at GitHub. The sample code is useful in its own. It features package download data from CRAN.
Concerning again {shinymeta}: Keep in mind that there were breaking changes in the meantime by replacing bang-bang (!!) with the dot-dot operator (..()).
It seems to me that a question on StackOverflow is relevant to my shinylive-r issue. At least I got exactly the same error message as in the question. But even with the different hints to solve the problem, notably the edited answer by Yihui Xie and the following several comments, I could not solve the shinylive-r error message.
9.3 Case study
To finish up, we’ll work through a small case study where we upload a file (with user supplied separator), preview it, perform some optional transformations using the {janitor} package (Firke 2024), and then let the user download it as a .tsv.
To make it easier to understand how to use the app, I’ve used sidebarLayout() to divide the app into three main steps:
Uploading and parsing the file
Cleaning the file
Downloading the file
All three parts get assembled into a single fluidPage() and the server logic follows the same organization.
R Code 9.7 : Upload a file, clean it, and then download the results
# Uploads and downloads {#sec-chap09}```{r}#| label: setup#| results: hold#| include: falselibrary(glossary)glossary::glossary_path("../glossary-pb/glossary.yml")library(shiny)```## Table of content for chapter 09 {.unnumbered}::::: {#obj-chap03}:::: {.my-objectives}::: {.my-objectives-header}Chapter section list:::::: {.my-objectives-container}::::::::::::## File upload {#sec-09-file-upload}### UI:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-09-file-upload-ui}: File upload UI:::::::::::::{.my-r-code-container}```{shinylive-r}#| standalone: true#| viewerHeight: 400#| components: [editor, viewer]ui<- fluidPage(fileInput("upload","Upload a file"))server<- function(input, output, session){}shinyApp(ui, server)```***Try in interactive mode adding / changing the arguments `label`, `width`, `buttonLabel` and `placeholder` to see how it affects the UI appearance.:::::::::The UI needed to support file uploads is simple: just add `shiny::fileInput()` to your UI.Like most other UI components, there are only two required arguments: `id` and `label`. The `width`, `buttonLabel` and `placeholder` arguments allow you to tweak the appearance in other ways. I won’t discuss them here, but you can read more about them in [File Upload Control — fileInput](https://shiny.posit.co/r/reference/shiny/1.6.0/fileinput.html).### ServerHandling `fileInput()` on the server is a little more complicated than other inputs. Most inputs return simple vectors, but `fileInput()` returns a data frame with four columns:- **name**: the original file name on the user’s computer.- **size**: the file size, in bytes. By default, the user can only upload files up to 5 MB. You can increase this limit by setting the shiny.maxRequestSize option prior to starting Shiny. For example, to allow up to 10 MB run options(shiny.maxRequestSize = 10 * 1024^2).- **type**: the `r glossary("MIME type")` of the file. This is a formal specification of the file type that is usually derived from the extension and is rarely needed in Shiny apps.- **datapath**: the path to where the data has been uploaded on the server. Treat this path as ephemeral: if the user uploads more files, this file may be deleted. The data is always saved to a temporary directory and given a temporary name.:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-09-file-upload-server}: File upload server :::::::::::::{.my-r-code-container}```{shinylive-r}#| standalone: true#| viewerHeight: 400#| components: [editor, viewer]#| layout: verticalui<- fluidPage(fileInput("upload", NULL, buttonLabel = "Upload...", multiple = TRUE),tableOutput("files"))server<- function(input, output, session){output$files<- renderTable(input$upload)}shinyApp(ui, server)```::: {.callout-warning #wrn-09-file-upload-server}`fileInput()` does not show multiple uploaded files. One can see only the last one. This issue was on May 2, 2021 [opened at GitHub](https://github.com/hadley/mastering-shiny/issues/468), but it is still not closed. I do not know with my rudimentary knowledge at the moment (2025-06-08) how to solve this problem.::::::::::::### Uploading dataIf the user is uploading a dataset, there are two details that you need to be aware of:- `input$upload` is initialized to `NULL` on page load, so you’ll need `req(input$upload)` to make sure your code waits until the first file is uploaded.- The `accept` argument allows you to limit the possible inputs. The easiest way is to supply a character vector of file extensions, like `accept = ".csv"`. But the accept argument is only a suggestion to the browser, and is not always enforced, so it’s good practice to also validate it (e.g. @sec-08-validating-input) yourself. The easiest way to get the file extension in R is `tools::file_ext()`, just be aware it removes the leading `.` from the extension.Putting all these ideas together gives us the following app where you can upload a `.csv` or `.tsv` file and see the first `n` rows. See it in action in <https://hadley.shinyapps.io/ms-upload-validate>.::: {.column-page-inset}:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-09-uploading-data}: Uploading data:::::::::::::{.my-r-code-container}```{shinylive-r}#| standalone: true#| viewerHeight: 550#| components: [editor, viewer]library(shiny)ui<- fluidPage(fileInput("upload", NULL, accept = c(".csv",".tsv")),numericInput("n","Rows", value = 5, min = 1, step = 1),tableOutput("head"))server<- function(input, output, session){data<- reactive({req(input$upload)ext<- tools::file_ext(input$upload$name)switch(ext,csv = vroom::vroom(input$upload$datapath, delim = ","),tsv = vroom::vroom(input$upload$datapath, delim = "\t"),validate("Invalid file; Please upload a .csv or .tsv file"))})output$head<- renderTable({head(data(), input$n)})}shinyApp(ui, server)```::::::::::::::: {.callout-note #nte-09-upload-data}Note that since `multiple = FALSE` (the default), `input$file` will be a single row data frame, and `input$file$name` and `input$file$datapath` will be a length-1 character vector.:::## Donwloads### Basics- Again, the UI is straightforward: use either `downloadButton(id)` or `downloadLink(id)` to give the user something to click to download a file. You can customise their appearance using the same class and icon arguments as for `actionButtons()`, as described in @sec-02-action-buttons.- Unlike other outputs, `downloadButton()` is not paired with a render function. Instead, you use `downloadHandler()`.`downloadHandler()` has two arguments, both functions:- `filename` should be a function with no arguments that returns a file name (as a string). The job of this function is to create the name that will be shown to the user in the download dialog box.- `content` should be a function with one argument, `file`, which is the path to save the file. The job of this function is to save the file in a place that Shiny knows about, so it can then send it to the user. This is an unusual interface, but it allows Shiny to control where the file should be saved (so it can be placed in a secure location) while you still control the contents of that file.Next we’ll put these pieces together to show how to transfer data files or reports to the user.### Downloading dataThe following app shows off the basics of data download by allowing you to download any dataset in the datasets package as a tab separated file. ::: {.callout-tip #tip-09-use-tsv-instead-of-csv}###### Use `.tsv` instead of `csv`I recommend using `.tsv` (tab separated value) instead of `.csv` (comma separated values) because many European countries use commas to separate the whole and fractional parts of a number (e.g. 1,23 vs 1.23). This means they can’t use commas to separate fields and instead use semi-colons in so-called “c”sv files! You can avoid this complexity by using tab separated files, which work the same way everywhere.:::::: {.column-page-inset}:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-09-downloading-data}: Downloading data:::::::::::::{.my-r-code-container}```{shinylive-r}#| standalone: true#| viewerHeight: 650#| components: [editor, viewer]library(shiny)# Workaround for Chromium Issue 468227 ####### Need this to properly download the csv file# this bug and workaround is only for shinylive, #you do not need it in your regular appdownloadButton<- function(...){tag<- shiny::downloadButton(...)tag$attribs$download<- NULLtag}### End of workaround ####ui<- fluidPage(selectInput("dataset","Pick a dataset", ls("package:datasets")),tableOutput("preview"),downloadButton("download","Download .tsv"))server<- function(input, output, session){data<- reactive({out<- get(input$dataset,"package:datasets")if(!is.data.frame(out)){validate(paste0("'", input$dataset, "' is not a data frame"))}out})output$preview<- renderTable({head(data())})output$download<- downloadHandler(filename = function(){paste0(input$dataset,".tsv")},content = function(file){vroom::vroom_write(data(), file)})}shinyApp(ui, server)```::::::::::::::: {.callout-warning #wrn-09-downloading-data}###### Workaround for Chromium IssueTo properly download the file in `shinylive`you need a workaround. Put the following function outside `server()`:```{r}#| label: workaround-download-shinylive#| eval: falsedownloadButton<- function(...){tag<- shiny::downloadButton(...)tag$attribs$download<- NULLtag}```This workaround is not necessary in an `app.R`file.:::Note the use of `validate()`to only allow the user to download datasets that are data frames. A better approach would be to pre-filter the list, but this lets you see another application of `validate()`.### Downloading reports {#sec-09-downloading-reports}#### Standard {#sec-09downloading-reports-standard}As well as downloading data, you may want the users of your app to download a report that summarizes the result of interactive exploration in the Shiny app. This is quite a lot of work, because you also need to display the same information in a different format, but it is very useful for high-stakes apps.One powerful way to generate such a report is with a [parameterised RMarkdown document](https://bookdown.org/yihui/rmarkdown/parameterized-reports.html). A parameterised `r glossary("RMarkdown")`file has a params field in the `r glossary("YAML")`metadata:````markdowntitle: My Documentoutput: html_documentparams:year: 2018region: Europeprintcode: TRUEdata: file.csv````Inside the document, you can refer to these values using `params$year`,`params$region`etc. The values in the YAML metadata are defaults;you’ll generally override them by providing the params argument in a call to `rmarkdown::render()`. This makes it easy to generate many different reports from the same `r glossary(".Rmd")`.Here’s a simple example adapted from <https://shiny.rstudio.com/articles/generating-reports.html>, which describes this technique in more detail. The key idea is to call `rmarkdown::render()`from the content argument of `downloadHander()`. If you want to produce other output formats, just change the output format in the `.Rmd`, and make sure to update the extension (e.g. to `.pdf`). See it in action at <https://hadley.shinyapps.io/ms-download-rmd>.::: {.column-page-inset}:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-09-downloading-reports-1}: Downloading reports:::::::::::::{.my-r-code-container}```{shinylive-r}#| standalone: true#| viewerHeight: 550#| components: [editor, viewer]library(shiny)# Workaround for Chromium Issue 468227 ####### Need this to properly download the csv file# this bug and workaround is only for shinylive, #you do not need it in your regular appdownloadButton<- function(...){tag<- shiny::downloadButton(...)tag$attribs$download<- NULLtag}### End of workaround ###### see the warning callout under this code chunkSys.setenv(RSTUDIO_PANDOC="/Applications/RStudio.app/Contents/Resources/app/quarto/bin/tools/aarch64")## I got the same error message with another pandoc version at macOS system level## Sys.setenv(RSTUDIO_PANDOC="/opt/homebrew/bin")ui<- fluidPage(sliderInput("n","Number of points", 1, 100, 50),downloadButton("report","Generate report"))server<- function(input, output, session){output$report<- downloadHandler(filename = "report.html",content = function(file){params<- list(n = input$n)id<- showNotification("Rendering report...",duration = NULL,closeButton = FALSE)on.exit(removeNotification(id), add = TRUE)rmarkdown::render("report.Rmd",output_file = file,params = params,envir = new.env(parent = globalenv()))})}shinyApp(ui, server)## file: report.Rmd## {{< include report.Rmd >}}```::::::::::::::: {.callout-warning #wrn-09-downloading-reports-1}###### Downloading reports is not workingThe code chunk does not produce an error. But whenever you click the button "Generate report" the following message is created in a new browser window: > An error has occurred!> pandoc version 1.12.3 or higher is required and was not found (see the help page ?rmarkdown::pandoc_available).I tried to solve the problem with [hints from StackOverflow](https://stackoverflow.com/questions/28432607/pandoc-version-1-12-3-or-higher-is-required-and-was-not-found-r-shiny), especially the [answer edited by Yihui Xie](https://stackoverflow.com/a/29710643/7322615).```{r}#| label: run-sys-getenvSys.getenv("RSTUDIO_PANDOC")```But to add `Sys.setenv("RSTUDIO_PANDOC"=Sys.getenv("RSTUDIO_PANDOC"))`did not work.I tried it also with another [pandoc](https://pandoc.org/)version installed at my macOS with [homebrew](https://brew.sh/):```{r}#| label: run-rmarkdown-pandoc-execrmarkdown::pandoc_exec()```I assume there are path problems with `shinylive`.:::#### Using {**callr**} {#sec-09downloading-reports-callr}It’ll generally take at least a few seconds to render a `.Rmd`, so this is a good place to use a notification from @sec-08-notification.There are a couple of other tricks worth knowing about:- RMarkdown works in the current working directory, which will fail in many deployment scenarios (e.g. on <https:://shinyapps.io>). You can work around this by copying the report to a temporary directory when your app starts (i.e. outside of the server function):```{r}#| label: temp-directory#| eval: falsereport_path<- tempfile(fileext = ".Rmd")file.copy("report.Rmd", report_path, overwrite = TRUE)```Then replace "report.Rmd" with `report_path`inthe call to `rmarkdown::render()`:```{r}#| label: replace-report-path#| eval: falsermarkdown::render(report_path,output_file = file,params = params,envir = new.env(parent = globalenv()))```- By default, RMarkdown will render the report in the current process, which means that it will inherit many settings from the Shiny app (like loaded packages, options, etc). For greater robustness, I recommend running `render()`ina separate R session using the {**callr**} package:```{r}#| label: using-callrpackage#| eval: falserender_report<- function(input, output, params){rmarkdown::render(input,output_file = output,params = params,envir = new.env(parent = globalenv()))}server<- function(input, output){output$report<- downloadHandler(filename = "report.html",content = function(file){params<- list(n = input$slider)callr::r(render_report,list(input = report_path, output = file, params = params))})}```::: {.column-page-inset}:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-09-downloading-reports-2}: Downloading reports:::::::::::::{.my-r-code-container}```{shinylive-r}#| standalone: true#| viewerHeight: 550#| components: [editor, viewer]library(shiny)# shinylive Workaround Start ######downloadButton<- function(...){tag<- shiny::downloadButton(...)tag$attribs$download<- NULLtag}### End of workaround ##### Copy report to temporary directory. This is mostly important when# deploying the app, since often the working directory won't be writablereport_path<- tempfile(fileext = ".Rmd")file.copy("report.Rmd", report_path, overwrite = TRUE)render_report<- function(input, output, params){rmarkdown::render(input,output_file = output,params = params,envir = new.env(parent = globalenv()))}rmarkdown::render("report.Rmd",output_file = file,params = params,envir = new.env(parent = globalenv()))ui<- fluidPage(sliderInput("n","Number of points", 1, 100, 50),downloadButton("report","Generate report"))server<- function(input, output){output$report<- downloadHandler(filename = "report.html",content = function(file){params<- list(n = input$n)callr::r(render_report,list(input = report_path, output = file, params = params))})}shinyApp(ui, server)#### The include macro generates error#### "object 'params' not found## file: report.Rmd# {{< include report.Rmd >}} ```::::::::::::::: {.callout-warning #wrn-09-download-report-failure}The last two code chunks didn't work.At first I got the message with a code version I can't reproduce> Warning: Error in : ! in callr subprocess > Caused by error in `abs_path(input)`:> ! The file '/var/folders/sd/g6yc4rq1731__gh38rw8whvc0000gr/T//RtmplpyWiO/file422772b5bfd1.Rmd' does not exist.In the current code version I got an error message whenever I try to include the `report.Rmd`file.> error: object'params' not found::::::::{.my-resource}:::{.my-resource-header}:::::: {#lem-09-downloding-reports}: Downloading reports with shiny and looking for `shinylive`error messages:::::::::::::{.my-resource-container}- You can see all these pieces put together in [rmarkdown-report/](https://github.com/hadley/mastering-shiny/tree/main/rmarkdown-report), found inside the Mastering Shiny GitHub repo. It works as Shiny app but with `shinylive`I got the error message reported in @wrn-09-download-report-failure.- The {**shinymeta**} package solves a related problem: sometimes you need to be able to turn the current state of a Shiny app into a reproducible report that can be re-run in the future. ([Github Source](https://github.com/rstudio/shinymeta),[Documentation](https://rstudio.github.io/shinymeta/). My [latest CRAN version 0.2.0.3](https://github.com/rstudio/shinymeta/releases/tag/v0.2.0.3)is from November 17, 2021. In the meantime there were [15 commits](https://github.com/rstudio/shinymeta/compare/v0.2.0.3...main)indicating that a new version is in preparation. preparation. )- Learn more about the {**shinymeta**} package in Joe Cheng’s useR! 2019 keynote, “[Shiny’s holy grail](https://www.youtube.com/watch?v=5KByRC6eqC8)^[Attention: The video starts at minute 11:20.]: Interactivity with reproducibility”. There is also the code for the example Shiny app available, featuring package download data from `r glossary("CRAN")`.The code for the example Shiny app is available at GitHub. The sample code is useful in its own. It features package download data from `r glossary("CRAN")`.- Concerning again {**shinymeta**}: Keep in mind that there were [breaking changes](https://github.com/rstudio/shinymeta/wiki/Syntax-changes-for-shinymeta-0.2.0)inthe meantime by replacing bang-bang (`!!`)with the dot-dot operator (`..()`).- It seems to me that a [question on StackOverflow](https://stackoverflow.com/questions/28432607/pandoc-version-1-12-3-or-higher-is-required-and-was-not-found-r-shiny)is relevant to my `shinylive-r`issue. At least I got exactly the same error message as in the question. But even with the different hints to solve the problem, notably the [edited answer by Yihui Xie](https://stackoverflow.com/a/29710643/7322615)and the following several comments, I could not solve the `shinylive-r`error message.:::::::::## Case studyTo finish up, we’ll work through a small case study where we upload a file (with user supplied separator), preview it, perform some optional transformations using the {**janitor**} package [@janitor], and then let the user download it as a `.tsv`.To make it easier to understand how to use the app, I’ve used `sidebarLayout()`to divide the app into three main steps:1. Uploading and parsing the file2. Cleaning the file3. Downloading the fileAll three parts get assembled into a single `fluidPage()`and the server logic follows the same organization.::: {.column-page-inset}:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-09-case-study}: Upload a file, clean it, and then download the results:::::::::::::{.my-r-code-container}```{shinylive-r}#| standalone: true#| viewerHeight: 650#| components: [editor, viewer]library(shiny)# shinylive Workaround Start ######downloadButton<- function(...){tag<- shiny::downloadButton(...)tag$attribs$download<- NULLtag}### End of workaround ####ui_upload<- sidebarLayout(sidebarPanel(fileInput("file","Data", buttonLabel = "Upload..."),textInput("delim","Delimiter (leave blank to guess)", ""),numericInput("skip","Rows to skip", 0, min = 0),numericInput("rows","Rows to preview", 10, min = 1)),mainPanel(h3("Raw data"),tableOutput("preview1")))ui_clean<- sidebarLayout(sidebarPanel(checkboxInput("snake","Rename columns to snake case?"),checkboxInput("constant","Remove constant columns?"),checkboxInput("empty","Remove empty cols?")),mainPanel(h3("Cleaner data"),tableOutput("preview2")))ui_download<- fluidRow(column(width = 12, downloadButton("download", class = "btn-block")))ui<- fluidPage(ui_upload,ui_clean,ui_download)server<- function(input, output, session){# Upload ---------------------------------------------------------raw<- reactive({req(input$file)delim<- if (input$delim == "")NULL else input$delimvroom::vroom(input$file$datapath, delim = delim, skip = input$skip)})output$preview1<- renderTable(head(raw(), input$rows))# Clean ----------------------------------------------------------tidied<- reactive({out<- raw()if(input$snake){names(out)<- janitor::make_clean_names(names(out))}if(input$empty){out<- janitor::remove_empty(out,"cols")}if(input$constant){out<- janitor::remove_constant(out)}out})output$preview2<- renderTable(head(tidied(), input$rows))# Download -------------------------------------------------------output$download<- downloadHandler(filename = function(){paste0(tools::file_path_sans_ext(input$file$name),".tsv")},content = function(file){vroom::vroom_write(tidied(), file)})}shinyApp(ui, server)```::::::::::::## ExercisesUse the {**ambient**} package [@ambient] to generate [worley noise](https://ambient.data-imaginist.com/reference/noise_worley.html)and download a PNG of it.:::::{.my-exercise}:::{.my-exercise-header}:::::: {#exr-09-exercise-01}: Generate Worley noise and download a PNG of the resulting image:::::::::::::{.my-exercise-container}::: {.panel-tabset}###### Simple version:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-09-exercise-01-simple}: Generate noise with two variable parameters:::::::::::::{.my-r-code-container}```{shinylive-r}#| standalone: true#| viewerHeight: 1000#| components: [editor, viewer]#| layout: vertical## file: app.R{{< include apps_09/ex_01/app.R>}}## file: shinylive.R{{< include R/shinylive.R>}}```:::::::::###### Complex version:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-09-exercise01-complex}: Generate noise with eight variable parameters:::::::::::::{.my-r-code-container}```{shinylive-r}#| standalone: true#| viewerHeight: 700#| components: [editor, viewer]#| layout: verticallibrary(shiny)library(ambient)ui<- fluidPage(titlePanel("Generate Worley Noise"),sidebarLayout(sidebarPanel(width = 4,fluidRow(column(6,numericInput("height","Height (1-20)",min = 1, max = 20, value = 10),),column(6,numericInput("length_x","Length Height (100-5000)",min = 100, max = 10000, value = 1000),)),fluidRow(column(6,numericInput("width","Width (1-20)",min = 1, max = 20, value = 10),),column(6,numericInput("length_y","Length Width (100-5000)",min = 100, max = 10000, value = 1000),)),fluidRow(column(12,selectInput("distance","Select distance",list("Euclidean" = "euclidean","Manhattan" = "manhattan","Natural" = "natural")),)),fluidRow(column(12,selectInput("value","Select noise value",list("Distance" = "distance","Distance2" = "distance2","Distance2add" = "distance2add","Distance2sub" = "distance2sub","Distance2mul" = "distance2mul","Distance2div" = "distance2div","Cell" = "cell")))),fluidRow(column(12,sliderInput("seed","Seed for replication",min = 1000,max = 1100,value = 1042)))),mainPanel(width = 8,plotOutput("plot"))))server<- function(input, output){grid1<- reactive({long_grid(seq(1, input$height, length.out = input$length_x),seq(1, input$width, length.out = input$length_y))})grid2<- reactive({grid1()|>dplyr::mutate(noise = gen_worley(grid1()$x, grid1()$y,value = input$value,distance = input$distance,seed = input$seed))})output$plot<- renderPlot({plot(grid2(), noise)})}shinyApp(ui, server)```:::::::::::::::::::::