golem
golem copied to clipboard
shiny.autoreload in {golem}
As of right now, options(shiny.autoreload = TRUE)
doesn't work with golem
Related SO question by @xari https://stackoverflow.com/questions/58648662/autoreload-in-golem-apps-shiny-option-doesnt-work
Ok so as far as I can tell it's because this behavior is triggered by shiny:::initAutoReloadMonitor
, which is triggered when running a "standard app" in a directory with runApp, which is not triggered by the current implementation of golem.
For example, if I create a new golem, delete the R/ folder, create a shiny app in a folder called R and do :
> options(shiny.autoreload = TRUE)
> runApp(appDir = 'R')
The option works as expected.
And :
> trace("runApp", where = asNamespace("shiny"))
> trace("initAutoReloadMonitor", where = asNamespace("shiny"))
Tracing function "initAutoReloadMonitor" in package
"namespace:shiny"
[1] "initAutoReloadMonitor"
> runApp(appDir = 'R')
trace: runApp(appDir = "R")
Tracing initAutoReloadMonitor(appDir) on entry
So runApp on directory works.
Then back in a golem app:
> options(shiny.autoreload = TRUE)
> trace("runApp", where = asNamespace("shiny"))
> trace("initAutoReloadMonitor", where = asNamespace("shiny"))
Then launch the run_dev,
> # Run the application
> lpppa::run_app()
Tracing runApp(x) on entry
Loading required package: shiny
Listening on http://127.0.0.1:5164
I have the trace of runApp
, but no initAutoReloadMonitor
. So apparently the runApp called from golem (natively) doesn't launch the reloadMonitor.
Not sure how to handle that so far 🤔
I am having this issue and it's slowing down my development workflow. I did some test too (by intentionally not using golem to find out the root cause) and I suspect it's the issue with Shiny modules? Golem app uses modules and it gets affected too?
Filed similar issues at shiny but without any help. autoreload doesn't load with Shiny modules #2711
In my package (it's golem but doesn't matter because I am running the app without any golem configurations), R/ dir have three files
- R/app.R
- R/shiny_run.R
- R/my_module.R
autoreload works without module
# app.R
library(shiny)
ui <- fluidPage(
selectInput("sample_input", "changeLabel", choices = letters[1:3])
)
server <- function(input, output, session) {
NULL
}
shinyApp(ui = ui, server = server)
# shiny_run.R
options(shiny.autoreload = TRUE)
shiny::runApp()
Editing the "changeLabel" in selectInput()
does the change the label.
autoreload doesn't work with module
# my_module.R
moduleUI <- function(id) {
ns <- NS(id)
selectInput(ns("sample_input"), "changeLabel", choices = letters[1:3])
}
moduleServer <- function(input, output, session) {
NULL
}
# app.R
library(shiny)
source("my_module.R")
ui <- fluidPage(
moduleUI('test')
)
server <- function(input, output, session) {
callModule(moduleServer, 'test')
}
shinyApp(ui = ui, server = server)
# shiny_run.R
options(shiny.autoreload = TRUE)
shiny::runApp()
Editing the "changeLabel" in selectInput()
does refresh the Shiny application but "changeLabel" does not change at all.
I'm just getting started with this, but I've been using a relatively hacky solution for this issue. Not sure if it's helpful to others, but I'll throw it here in case:
So, each time you change bits of your shiny application, you're supposed to run run_dev.R
, right?
Instead of getting auto.reload to work, I just use bash to watch for changes in the R/
directory and call run_dev.R
each time I see changes. Of course, it takes a bit to run run_dev.R
, but I still find it handy.
Step 1: Set the port
I set that in run_dev.R
:
# run_dev.R
[...]
# Run the application
options(shiny.port = 11616) # Added this line
run_app()
I just use port 11616 for fun, since it spells "app" (A=1, P=16).
Step 2: Create utils/watch_app.sh
and utils/run_app.sh
Then I create a ./utils
folder in my shiny directory and put two files there: watch_app.sh
and run_app.sh
.
watch_app.sh
just checks for changes in the "R" directory. If it sees any changes, it: (1) kills all processes on port 11616, and (2) runs the run_dev.R
script.
#!/bin/bash -eu
find ../R | entr sh -c 'lsof -i:11616 | awk '"'"'NR!=1 {print $2}'"'"' | xargs kill ; (cd .. && Rscript ./dev/run_dev.R &)'
run_app.sh
(1) makes sure that all processes on port 11616 are killed when watch_app.sh
is killed, and (2) runs watch_app.sh
:
#!/usr/bin/env bash
trap watch_app.sh kill $( lsof -i:11616 -t)
bash watch_app.sh
Typical workflow
(1) Open up RStudio and my project
(2) Open up a terminal, navigate to [project-root]/utils
, run run_app.sh
, minimize terminal
(3) Open up a browser, navigate to localhost:11616
(4) Edit things in RStudio and refresh the browser when I want to see changes.
It's definitely a lot slower than Shiny's autorefresh, but it seems to work ok.
Sources: using entr | killing by port | nesting single quotes hack.
My solution to this issue makes use of a very small Javascript script, which automatically reloads the Shiny app (which is in fact a web page) after a desired length of time. I realize that many Shiny users are not comfortable with using Javascript and I, myself, am learning as of the time of writing this. So, I will write my solution in the form of a detailed step by step guide:
1- Create a Javascript file
The {golem}
package makes the first step very trivial. Open the dev/02_dev.R
file and look for the function golem::add_js_file()
. Execute it by passing your desired file name to it as a character string (e.g. golem::add_js_file("script")
). Note that you do not need to specify any extension to the file name. After running this code, the file script.js
will be created in the inst/app/www/
folder. Look for it and open it (it does not matter whether you open it in RStudio or not).
2- Add the JavaScript code
Copy the code below, paste it in script.js
and save it:
// Function for reloading the page
reloadPage = () => {
window.location.reload()
}
setTimeout(reloadPage, 1000) // The page is reloaded every second (1000 milliseconds), adjust it to your needs
Do not forget to change the reload interval, which is 1000 milliseconds above (i.e. one second), if you want to.
3- Run your application
Run your application as usual by running the code in dev/run_dev.R
.
Warning
Keep in mind that this hack is mostly useful for building your UI and that you will not be able to efficiently test your app while the script is running because it will automatically reload. For this reason options(shiny.autoreload = TRUE)
would still be a more efficient way in your app development process.
In order to stop the script from running, you can just comment it out by using //
at the beginning of each line of code:
// Function for reloading the page
// reloadPage = () => {
// window.location.reload()
// }
// setTimeout(reloadPage, 1000) // The page is reloaded every second (1000 milliseconds), adjust it to your needs
Source: this solution was adapted from this Stackoverflow thread.
Hello everyone!
I have also came to this problem and it's slowing down my productivity. The solution proposed by @julianstanley is quite similar to the Shiny's native autoreload, but I have actually realized that I like more something similar to the classic devtools shortcut Ctrl+Shift+L
(calls load_all()
). So I have made a simple reload Bash script and utilized the shrtcts package, which can assign any R command to keyboard shortcut (within RStudio). Now I can reload my Shiny app with Ctrl+Shift+R
and watch its output in RStudio terminal. The steps are simple:
- Put this script in
<your golem package>/utils/reload_app.sh
:
#!/usr/bin/env bash
cd "$(dirname "$0")"
echo "Running $0"
APP_NAME=$(basename $(realpath ../))
app_pid=${APP_NAME}.pid
if [[ -f $app_pid ]]; then
printf "$APP_NAME is running, killing ... "
pkill -F $app_pid
printf "Done."
fi
printf "\nStarting $APP_NAME ... "
nohup Rscript ../dev/run_dev.R > $APP_NAME.log 2>&1 &
echo $! > $app_pid
printf "Done.\n"
I don't like killing processes through filtering their PID, so here, PID is saved to file and later used for killing.
- Install shrtcts
- Put this into
~/.Rprofile
:
reload_shiny_app <- function() {
system2("bash", paste0(golem::get_golem_wd(), "/utils/reload_app.sh"))
cat("Waiting for Shiny app to start...\n")
for (i in 1:1000) {
is_app_running <- system2("curl", c("-IL", "http://localhost:8080"), stdout = NULL, stderr = NULL)
##-- exit code 0 means everything is fine :)
if (!is_app_running) {
rstudioapi::viewer("http://localhost:8080/")
break
}
Sys.sleep(0.1)
}
}
The app port is hardcoded here, so change it according to your settings. Make sure you have curl installed, as it is used to check whether the app is running.
- Put this into
~/.shrtcts.R
:
#' Reload current Shiny application.
#'
#' @description <app package>/utils/reload_app.sh must be present.
#'
#' @interactive
#' @shortcut Ctrl+Shift+R
globalenv()$reload_shiny_app
I don't understand how shrtcts is parsing the shortcut command, but if you directly use in shortcut definition what's in the reload_shiny_app()
, then reload_app.sh
is executed twice. But maybe just something weird is happening on my side, so feel free to try it without the intermediate reload_shiny_app()
:wink:
- In RStudio you can now press
Ctrl+Shift+R
(or whatever you define in~/.shrtcts.R
) and your app will be started. Then open terminal, navigate toutils
directory and runtail -f <your app name>.log
to watch app's output. Anytime you pressCtrl+Shift+R
, your app will be reloaded and shown in RStudio viewer pane.
I think this sort of shortcut could be added to golem's RStudio addin.
Ok folks, so to sum up the issue is that the autoreload only works when calling runApp()
on a directory, and by default {golem} apps don't do this runApp()
.
Here is the workaround to make this work:
-
Step 1: create an app.R deployment file with
golem::add_rstudioconnect_file()
. -
Step 2: change your dev/run_dev.R script as such :
# Set options here
options(golem.app.prod = FALSE) # TRUE = production mode, FALSE = development mode
# Detach all loaded packages and clean your environment
golem::detach_all_attached()
# rm(list=ls(all.names = TRUE))
# Document and reload your package
golem::document_and_reload()
options(shiny.autoreload = TRUE)
# Run the application
shiny::runApp(appDir = here::here())
Enjoy 🎉
Ok folks, so to sum up the issue is that the autoreload only works when calling
runApp()
on a directory, and by default {golem} apps don't do thisrunApp()
.Here is the workaround to make this work:
* Step 1: create an app.R deployment file with `golem::add_rstudioconnect_file()`. * Step 2: change your dev/run_dev.R script as such :
# Set options here options(golem.app.prod = FALSE) # TRUE = production mode, FALSE = development mode # Detach all loaded packages and clean your environment golem::detach_all_attached() # rm(list=ls(all.names = TRUE)) # Document and reload your package golem::document_and_reload() options(shiny.autoreload = TRUE) # Run the application shiny::runApp()
Enjoy 🎉
Thanks for the follow-up, @ColinFay .
When my colleague and I originally encountered this issue, we came to the conclusion that our development workflow must have been fundamentally different from yours. At the time, we would make amendments to our business logic code, and then re-start our app to confirm that everything worked as intended. This involved a lot of waiting for the app to re-load, etc.
The conclusion that we came to is that you probably follow more of a test-driven-development workflow, where you run smaller pieces of your business logic without the need to start-up the entire app. Working this way means that the auto-reload feature is generally less necessary, as you only really need to start -up the app in development when testing UI features.
Chapter 10 of your book gets into TDD somewhat, but it could also be interesting to see a screencast that addresses this topic of "efficient development workflow".
Ok folks, so to sum up the issue is that the autoreload only works when calling
runApp()
on a directory, and by default {golem} apps don't do thisrunApp()
.Here is the workaround to make this work:
- Step 1: create an app.R deployment file with
golem::add_rstudioconnect_file()
.- Step 2: change your dev/run_dev.R script as such :
# Set options here options(golem.app.prod = FALSE) # TRUE = production mode, FALSE = development mode # Detach all loaded packages and clean your environment golem::detach_all_attached() # rm(list=ls(all.names = TRUE)) # Document and reload your package golem::document_and_reload() options(shiny.autoreload = TRUE) # Run the application shiny::runApp()
Enjoy 🎉
Hmm, I just tried this and it didn't work for me. My workflow was:
- Create
app.R
and make changes todev/run_dev.R
as Colin suggested - Click "Run App" in
dev/run_dev.R
. - Make changes in either
app_ui.R
or a UI module; neither change registered after refreshing the browser.
Attaching my session info below
I followed the same steps and it also doesn't work for me.
@dwhdai this refreshes only if you save the file inside R (not the browser), and also, there is an issue with {shiny}
itself https://github.com/rstudio/shiny/issues/2711 :)
I feel like this should be implemented at {golem}
level so that we have this under control.
For example in golem::run_dev()
🤔
EDIT: Unintentionally wrote this in French at first ...
Hi @ColinFay,
The aformentionned solution works (I merely added appDir = here::here()
in order to make it work with rstudio jobs while leaving run_dev.R inside the /dev folder, but its not what matters), but by "works" I mean "the page refreshes"
However, while the page refreshes, the UI changes do not appear. Unless I make a change to app.R, in which case the app in correctly refreshed and changes to modules taken into account. Of course, right after I wrote this I found the same kind of comment in the {shiny} repo ...
In any case, while I do not have a solution to contribute, knowing how to do this is good enough to make it work, I just add/remove a character in app.R and I can dev the app while watching my changes
So, thank you for writing a solution, and for what you guys do with {golem} :-)
Note to self : here is a prototype of implementing an auto.reload
in {golem}
:
loops <- new.env()
run_dev_later <- function(){
hash <- digest::digest(
file.info(
list.files(
path = c("R/", "inst"),
recursive = TRUE,
full.names = TRUE
)
)[, "mtime"]
)
proc <- processx::process$new(
stderr = "|",
stdout = "|",
"Rscript", c(
"-e",
"golem::run_dev()"
)
)
Sys.sleep(5)
run_dev_now <- function(){
new_hash <- digest::digest(
file.info(
list.files(
path = c("R/", "inst"),
recursive = TRUE,
full.names = TRUE
)
)[, "mtime"]
)
if (new_hash != hash){
cli::cat_rule("Relaunching the app")
hash <<- new_hash
proc$interrupt()
proc <<- processx::process$new(
"Rscript", c(
"-e",
"golem::run_dev()"
)
)
Sys.sleep(5)
}
later::later(
run_dev_now,
1,
loop = loops$run_dev
)
}
loops$run_dev <- later::create_loop()
run_dev_now()
}
run_dev_later()
# When done
later::destroy_loop(
loops$run_dev
)
Don't forget to put the stderr and stdout in a file.
logs <- tempfile()
proc <- processx::process$new(
stderr = logs,
stdout = logs,
"Rscript", c(
"-e",
"golem::run_dev()"
)
)
Sys.sleep(5)
readLines(logs)
proc$kill()
The same terminal as R would be the best :
proc <- processx::process$new(
stderr = "",
stdout = "",
"Rscript", c(
"-e",
"golem::run_dev()"
)
)
Sys.sleep(5)
proc$kill()
Can't wait for version 0.4.0 with this autoreload feature, it will make the app development process way quicker and a lot more pleasurable
Can't wait for version 0.4.0 with this autoreload feature, it will make the app development process way quicker and a lot more pleasurable
Hi,
It is implemented in the autorelaod
branch.
You can test it by installing this branch with the following command:
remotes::install_github("thinkr-open/golem", "autorelaod")
And inside your golem application golem::run_dev(autoreaload = TRUE)
Ive tested the solutions with a new golem project to nullify any of my own tinkering.
After installing the branch and running golem::run_dev(autoreload = TRUE)
, then editing a file, saving it and refreshing the browser, I dont see the changes I've made (e.g., I added a h1 tag on the main ui file). The other fix you suggested a while back (golem::add_rstudioconnect_file()
), doesnt work for me either. I can see that the page refreshes, but I dont see the UI being updated. I've tried to source the modules' file in the main app_server.R as well, but that didnt work either.
I've tried combinations of these solutions as well (e.g., run_dev(autoreload = TRUE)
) with the rstudioconnect version of dev/run-dev.R), but cannot seem to get it to work.
Im using Windows 19043.1288, R 4.1.1 and shiny 1.7.1.
Thank you, @Cervangirard. I'm having a strange issue, when I run golem::run_dev(autoreaload = TRUE)
, I receive this error message:
Error in golem::run_dev(autoreaload = TRUE) :
unused argument (autoreaload = TRUE)
It is like the parameter autoreaload
does not exist but when I inspect run_dev()
, it is there.
I removed and reinstalled Golem several times using remotes::install_github("thinkr-open/golem", "autorelaod")
but error persists.
sessionInfo()
#> R version 4.1.1 (2021-08-10)
#> Platform: x86_64-w64-mingw32/x64 (64-bit)
#> Running under: Windows 10 x64 (build 19043)
#>
#> Matrix products: default
#>
#> locale:
#> [1] LC_COLLATE=English_United States.1252
#> [2] LC_CTYPE=English_United States.1252
#> [3] LC_MONETARY=English_United States.1252
#> [4] LC_NUMERIC=C
#> [5] LC_TIME=English_United States.1252
#>
#> attached base packages:
#> [1] stats graphics grDevices utils datasets methods base
#>
#> loaded via a namespace (and not attached):
#> [1] digest_0.6.28 withr_2.4.2 magrittr_2.0.1 reprex_2.0.1
#> [5] evaluate_0.14 highr_0.9 stringi_1.7.5 rlang_0.4.11
#> [9] cli_3.0.1 rstudioapi_0.13 fs_1.5.0 rmarkdown_2.10
#> [13] tools_4.1.1 stringr_1.4.0 glue_1.4.2 xfun_0.27
#> [17] yaml_2.2.1 fastmap_1.1.0 compiler_4.1.1 htmltools_0.5.2
#> [21] knitr_1.36
**Golem 0.3.1.9002**
Created on 2021-10-28 by the reprex package (v2.0.1)
Thank you, @Cervangirard. I'm having a strange issue, when I run
golem::run_dev(autoreaload = TRUE)
, I receive this error message:Error in golem::run_dev(autoreaload = TRUE) : unused argument (autoreaload = TRUE)
It is like the parameter
autoreaload
does not exist but when I inspectrun_dev()
, it is there.I removed and reinstalled Golem several times using
remotes::install_github("thinkr-open/golem", "autorelaod")
but error persists.sessionInfo() #> R version 4.1.1 (2021-08-10) #> Platform: x86_64-w64-mingw32/x64 (64-bit) #> Running under: Windows 10 x64 (build 19043) #> #> Matrix products: default #> #> locale: #> [1] LC_COLLATE=English_United States.1252 #> [2] LC_CTYPE=English_United States.1252 #> [3] LC_MONETARY=English_United States.1252 #> [4] LC_NUMERIC=C #> [5] LC_TIME=English_United States.1252 #> #> attached base packages: #> [1] stats graphics grDevices utils datasets methods base #> #> loaded via a namespace (and not attached): #> [1] digest_0.6.28 withr_2.4.2 magrittr_2.0.1 reprex_2.0.1 #> [5] evaluate_0.14 highr_0.9 stringi_1.7.5 rlang_0.4.11 #> [9] cli_3.0.1 rstudioapi_0.13 fs_1.5.0 rmarkdown_2.10 #> [13] tools_4.1.1 stringr_1.4.0 glue_1.4.2 xfun_0.27 #> [17] yaml_2.2.1 fastmap_1.1.0 compiler_4.1.1 htmltools_0.5.2 #> [21] knitr_1.36 **Golem 0.3.1.9002**
Created on 2021-10-28 by the reprex package (v2.0.1)
Hello,
This is most likely becquse you wrote "autoreaload" instead of "autoreload"
Best,
Raphael
Haha simplest solution ever (I had copied and pasted, then the typo happened). Thank you, @rsimonmd !
However, the command is not working for me either, I'm having the same issue as @vreederene-90 :
After installing the branch and running
golem::run_dev(autoreload = TRUE)
, then editing a file, saving it and refreshing the browser, I dont see the changes I've made (e.g., I added a h1 tag on the main ui file).
@Cervangirard , I believe there is a misspelling in the name of the branch autorelaod
, shouldn't it be named autoreload
?
Hello @ColinFay,
Many thanks for all the work you are doing on Golem, great package, I am fan.
Regarding this autoreload bug in golem, has it been addressed?
Thanks!!
The new autoreload
option does not seem to respect different modals for viewing the app. For example, it does not use the RStudio window viewer when this line is run:
options(shiny.launch.browser = .rs.invokeShinyWindowViewer)
Moreover, autoreload always opens in a new external browser tab on reload. This is different from the normal Shiny autoreload which will refresh the preexisting browser tab.
This feature would definitely be nice to have with some refinement.
Ive written a small package that includes two workarounds to this problem: autoreload.fix.
Ive written a small package that includes two workarounds to this problem: autoreload.fix.
Hello @vreederene-90,
Thanks for the effort.
I am trying your package. So basically I have my golem project, I added the option(shiny.autoreload = TRUE)
as stated in your documentation. And added the short (F5) to RStudio for refreshing. It didn't work.
I also tried to run the command autoreload.fix::autorefresh()
but didn't work for me.
Anything I might be doing wrong?
Anything I might be doing wrong?
Please verify you have written options
instead of option
, and that this command is executed before you boot your app. You have to press the shortcut while the focus of your PC is on Rstudio, not the browser. Also be sure your working directory is set to your project root. If you still have any problems, let me know.
Anything I might be doing wrong?
Please verify you have written
options
instead ofoption
, and that this command is executed before you boot your app. You have to press the shortcut while the focus of your PC is on Rstudio, not the browser. Also be sure your working directory is set to your project root. If you still have any problems, let me know.
@vreederene-90 I tried the following, with no success unfortunately.
-
I added
options(shiny.autoreload = TRUE)
in myrun_dev.R
(options with an "s".) I executed the command beforehand in my R console. When runninggetwd()
in my R Console I get correct the path (my Shiny golem project). -
I created a shortcut for Refresh Shiny that I have tied to F5
-
I executed my Shiny app from my Terminal using
R -e "autoreload.fix::autorefresh()"
(making sure I am in the working dir of my project)
What happens is that the application doesn't load. The browser opens a tab with 127.0.0.1:3070 but a white empty page is shown.
I tried to get the focus back to RStudio and press fn + F5
(I am on mac), but in vain.
Any ideas ?
Thanks for your help
We spent some time with @vreederene-90 to check his solution autoreload.fix (thanks again Rene) and it works pretty well !
Hi everyone, not sure if this is still relevant , but I encountered the same problem yesterday and I found the following fix to work as well: patch the function shiny::cachedFuncWithFile
to not only monitor changes of app.R
/ ui.R
/ server.R
, but also changes of ".*\\.(r|html?|js|css|png|jpe?g|gif)$"
(as is done by shiny::initAutoReloadMonitor
):
cached_func_with_file <- function(dir,
file,
func,
case.sensitive = FALSE) {
dir <- normalizePath(dir, mustWork = TRUE)
value <- NULL
filePattern <- getOption(
"shiny.autoreload.pattern",
".*\\.(r|html?|js|css|png|jpe?g|gif)$"
)
last_mtimes <- NULL
function(...) {
file.path.func <- if (case.sensitive) file.path else file.path.ci
fname <- file.path.func(dir, file)
files <- list.files(dir, filePattern, recursive = TRUE, ignore.case = TRUE)
files <- sort_c(files)
mtimes <- file.info(files)$mtime
names(mtimes) <- files
if (!identical(last_mtimes, mtimes)) {
value <<- func(fname, ...)
last_mtimes <<- mtimes
}
value
}
}
shiny_env <- environment(shiny:::cachedFuncWithFile)
unlockBinding("cachedFuncWithFile", shiny_env)
body(shiny_env$cachedFuncWithFile) <- body(cached_func_with_file)
lockBinding("cachedFuncWithFile", shiny_env)
options(shiny.autoreload = TRUE)
After executing this code you can use shiny::runApp
as usual and it will automatically resource all relevant files on modification (in addition to restarting the active sessions). This code can e.g. be put in a wrapper function runAppPatched
or even at your local .Rprofile
.
Here you can see the the relevant code part of shiny
that calls cached_func_with_file
:
@toscm that didn't work for me, unfortunately. I ran your code in the Rstudio console then launched the app using golem::run_dev()
but no changes to app_ui.R
were picked up. Maybe I did it wrong.
It's definitely still a relevant issue though.. makes it hard to use golem on a day-to-day basis.