Implement autoreload

This commit is contained in:
Joe Cheng
2015-11-04 13:44:38 -08:00
parent 9fde7509fa
commit e391c1fda3
10 changed files with 117 additions and 5 deletions

7
NEWS
View File

@@ -33,6 +33,13 @@ shiny 0.12.2.9000
* `invalidateLater` and `reactiveTimer` no longer require an explicit `session`
argument; the default value uses the current session.
* Added `session$reload()` method, the equivalent of hitting the browser's
Reload button.
* Added `shiny.autoreload` option, which will automatically cause browsers to
reload whenever Shiny app files change on disk. This is intended to shorten
the feedback cycle when tweaking UI code.
shiny 0.12.2
--------------------------------------------------------------------------------

54
R/app.R
View File

@@ -179,14 +179,18 @@ shinyAppDir_serverR <- function(appDir, options=list()) {
}
oldwd <- NULL
monitorHandle <- NULL
onStart <- function() {
oldwd <<- getwd()
setwd(appDir)
monitorHandle <<- initAutoReloadMonitor(appDir)
if (file.exists(file.path.ci(appDir, "global.R")))
sourceUTF8(file.path.ci(appDir, "global.R"))
}
onEnd <- function() {
setwd(oldwd)
monitorHandle()
monitorHandle <<- NULL
}
structure(
@@ -200,6 +204,52 @@ shinyAppDir_serverR <- function(appDir, options=list()) {
)
}
# Start a reactive observer that continually monitors dir for changes to files
# that have the extensions: r, htm, html, js, css, png, jpg, jpeg, gif. Case is
# ignored when checking extensions. If any changes are detected, all connected
# Shiny sessions are reloaded.
#
# Use option(shiny.autoreload = TRUE) to enable this behavior. Since monitoring
# for changes is expensive (we are polling for mtimes here, nothing fancy) this
# feature is intended only for development.
#
# You can customize the file patterns Shiny will monitor by setting the
# shiny.autoreload.pattern option. For example, to monitor only ui.R:
# option(shiny.autoreload.pattern = glob2rx("ui.R"))
#
# The return value is a function that halts monitoring when called.
initAutoReloadMonitor <- function(dir) {
if (!getOption("shiny.autoreload", FALSE)) {
return(function(){})
}
filePattern <- getOption("shiny.autoreload.pattern",
".*\\.(r|html?|js|css|png|jpe?g|gif)$")
lastValue <- NULL
obs <- observe({
files <- sort(list.files(dir, pattern = filePattern, recursive = TRUE,
ignore.case = TRUE))
times <- file.info(files)$mtime
names(times) <- files
if (is.null(lastValue)) {
# First run
lastValue <<- times
} else if (!identical(lastValue, times)) {
# We've changed!
lastValue <<- times
for (session in appsByToken$values()) {
session$reload()
}
}
invalidateLater(getOption("shiny.autoreload.interval", 500))
})
obs$destroy
}
# This reads in an app dir for a single-file application (e.g. app.R), and
# returns a shiny.appobj.
shinyAppDir_appR <- function(fileName, appDir, options=list()) {
@@ -233,12 +283,16 @@ shinyAppDir_appR <- function(fileName, appDir, options=list()) {
fallbackWWWDir <- system.file("www-dir", package = "shiny")
oldwd <- NULL
monitorHandle <- NULL
onStart <- function() {
oldwd <<- getwd()
setwd(appDir)
monitorHandle <<- initAutoReloadMonitor(appDir)
}
onEnd <- function() {
setwd(oldwd)
monitorHandle()
monitorHandle <<- NULL
}
structure(

View File

@@ -42,6 +42,22 @@ NULL
#' \item{shiny.trace}{If \code{TRUE}, all of the messages sent between the R
#' server and the web browser client will be printed on the console. This
#' is useful for debugging.}
#' \item{shiny.autoreload}{If \code{TRUE} when a Shiny app is launched, the
#' app directory will be continually monitored for changes to files that
#' have the extensions: r, htm, html, js, css, png, jpg, jpeg, gif. If any
#' changes are detected, all connected Shiny sessions are reloaded. This
#' allows for fast feedback loops when tweaking Shiny UI.
#'
#' Since monitoring for changes is expensive (we simply poll for last
#' modified times), this feature is intended only for development.
#'
#' You can customize the file patterns Shiny will monitor by setting the
#' shiny.autoreload.pattern option. For example, to monitor only ui.R:
#' \code{option(shiny.autoreload.pattern = glob2rx("ui.R"))}
#'
#' The default polling interval is 500 milliseconds. You can change this
#' by setting e.g. \code{option(shiny.autoreload.interval = 2000)} (every
#' two seconds).}
#' \item{shiny.reactlog}{If \code{TRUE}, enable logging of reactive events,
#' which can be viewed later with the \code{\link{showReactLog}} function.
#' This incurs a substantial performance penalty and should not be used in
@@ -212,6 +228,10 @@ workerId <- local({
#' endpoint. The return value of \code{filterFunc} should be a Rook-style
#' response.
#' }
#' \item{reload()}{
#' The equivalent of hitting the browser's Reload button. Only works if the
#' session is actually connected.
#' }
#' \item{request}{
#' An environment that implements the Rook specification for HTTP requests.
#' This is the request that was used to initiate the websocket connection
@@ -693,6 +713,9 @@ ShinySession <- R6Class(
if (private$showcase)
self$sendCustomMessage("reactlog", logEntry)
},
reload = function() {
self$sendCustomMessage("reload", TRUE)
},
# Public RPC methods
`@uploadieFinish` = function() {

View File

@@ -1108,6 +1108,10 @@ var ShinyApp = function() {
}
});
addCustomMessageHandler('reload', function(message) {
window.location.reload();
});
// Progress reporting ====================================================

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -76,6 +76,10 @@
endpoint. The return value of \code{filterFunc} should be a Rook-style
response.
}
\item{reload()}{
The equivalent of hitting the browser's Reload button. Only works if the
session is actually connected.
}
\item{request}{
An environment that implements the Rook specification for HTTP requests.
This is the request that was used to initiate the websocket connection

View File

@@ -16,6 +16,22 @@ be set with (for example) \code{options(shiny.trace=TRUE)}.
\item{shiny.trace}{If \code{TRUE}, all of the messages sent between the R
server and the web browser client will be printed on the console. This
is useful for debugging.}
\item{shiny.autoreload}{If \code{TRUE} when a Shiny app is launched, the
app directory will be continually monitored for changes to files that
have the extensions: r, htm, html, js, css, png, jpg, jpeg, gif. If any
changes are detected, all connected Shiny sessions are reloaded. This
allows for fast feedback loops when tweaking Shiny UI.
Since monitoring for changes is expensive (we simply poll for last
modified times), this feature is intended only for development.
You can customize the file patterns Shiny will monitor by setting the
shiny.autoreload.pattern option. For example, to monitor only ui.R:
\code{option(shiny.autoreload.pattern = glob2rx("ui.R"))}
The default polling interval is 500 milliseconds. You can change this
by setting e.g. \code{option(shiny.autoreload.interval = 2000)} (every
two seconds).}
\item{shiny.reactlog}{If \code{TRUE}, enable logging of reactive events,
which can be viewed later with the \code{\link{showReactLog}} function.
This incurs a substantial performance penalty and should not be used in

View File

@@ -516,6 +516,10 @@ var ShinyApp = function() {
}
});
addCustomMessageHandler('reload', function(message) {
window.location.reload();
});
// Progress reporting ====================================================