Skip to content

Commit 107747a

Browse files
authored
Make {watcher} a required dependency for autoreload (#4403)
* Make watcher a required dependency for autoreload Move {watcher} from Suggests to Imports and use it unconditionally for autoreload file watching, removing the legacy polling-based fallback. * Add PR number to NEWS
1 parent 36446f6 commit 107747a

5 files changed

Lines changed: 41 additions & 84 deletions

File tree

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ Imports:
9797
sourcetools,
9898
tools,
9999
utils,
100+
watcher (>= 0.2.0),
100101
withr,
101102
xtable
102103
Suggests:
@@ -119,7 +120,6 @@ Suggests:
119120
shinytest2,
120121
showtext,
121122
testthat (>= 3.2.1),
122-
watcher,
123123
yaml
124124
Config/Needs/check: shinyjs
125125
Config/roxygen2/version: 8.0.0

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# shiny (development version)
22

3+
* `{watcher}` is now a required dependency and is always used for autoreload file watching, so it no longer needs to be installed separately. The legacy polling-based file watcher has been removed. (#4403)
4+
35
# shiny 1.14.0
46

57
## New features

R/shiny-options.R

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,19 +65,18 @@ getShinyOption <- function(name, default = NULL) {
6565
#' changes are detected, all connected Shiny sessions are reloaded. This
6666
#' allows for fast feedback loops when tweaking Shiny UI.
6767
#'
68-
#' Monitoring for changes is no longer expensive, thanks to the \pkg{watcher}
69-
#' package, but this feature is still intended only for development.
68+
#' Shiny uses the \pkg{watcher} package to efficiently monitor for changes,
69+
#' but this feature is still intended only for development.
7070
#'
7171
#' You can customize the file patterns Shiny will monitor by setting the
7272
#' shiny.autoreload.pattern option. For example, to monitor only `ui.R`:
7373
#' `options(shiny.autoreload.pattern = glob2rx("ui.R"))`.
7474
#'
75-
#' As mentioned above, Shiny no longer polls watched files for changes.
76-
#' Instead, using \pkg{watcher}, Shiny is notified of file changes as they
77-
#' occur. These changes are batched together within a customizable latency
78-
#' period. You can adjust this period by setting
75+
#' Using \pkg{watcher}, Shiny is notified of file changes as they occur
76+
#' rather than polling. These changes are batched together within a
77+
#' customizable latency period. You can adjust this period by setting
7978
#' `options(shiny.autoreload.interval = 2000)` (in milliseconds). This value
80-
#' converted to seconds and passed to the `latency` argument of
79+
#' is converted to seconds and passed to the `latency` argument of
8180
#' [watcher::watcher()]. The default latency is 250ms.}
8281
#' \item{shiny.deprecation.messages (defaults to `TRUE`)}{This controls whether messages for
8382
#' deprecated functions in Shiny will be printed. See

R/shinyapp.R

Lines changed: 26 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -289,14 +289,13 @@ shinyAppDir_serverR <- function(appDir, options=list()) {
289289
)
290290
}
291291

292-
# Start a reactive observer that continually monitors dir for changes to files
293-
# that have the extensions: r, htm, html, js, css, png, jpg, jpeg, gif. Case is
294-
# ignored when checking extensions. If any changes are detected, all connected
295-
# Shiny sessions are reloaded.
292+
# Start a {watcher} file watcher that continually monitors dir for changes to
293+
# files that have the extensions: r, htm, html, js, css, png, jpg, jpeg, gif.
294+
# Case is ignored when checking extensions. If any changes are detected, all
295+
# connected Shiny sessions are reloaded.
296296
#
297-
# Use options(shiny.autoreload = TRUE) to enable this behavior. Since monitoring
298-
# for changes is expensive (we are polling for mtimes here, nothing fancy) this
299-
# feature is intended only for development.
297+
# Use options(shiny.autoreload = TRUE) to enable this behavior. This feature is
298+
# intended only for development.
300299
#
301300
# You can customize the file patterns Shiny will monitor by setting the
302301
# shiny.autoreload.pattern option. For example, to monitor only ui.R:
@@ -313,72 +312,30 @@ initAutoReloadMonitor <- function(dir) {
313312
".*\\.(r|html?|js|css|png|jpe?g|gif)$"
314313
)
315314

316-
317-
if (is_installed("watcher")) {
318-
check_for_update <- function(paths) {
319-
paths <- grep(
320-
filePattern,
321-
paths,
322-
ignore.case = TRUE,
323-
value = TRUE
324-
)
325-
326-
if (length(paths) == 0) {
327-
return()
328-
}
329-
330-
cachedAutoReloadLastChanged$set()
331-
autoReloadCallbacks$invoke()
332-
}
333-
334-
# [garrick, 2025-02-20] Shiny <= v1.10.0 used `invalidateLater()` with an
335-
# autoreload.interval in ms. {watcher} instead uses a latency parameter in
336-
# seconds, which serves a similar purpose and that I'm keeping for backcompat.
337-
latency <- getOption("shiny.autoreload.interval", 250) / 1000
338-
watcher <- watcher::watcher(dir, check_for_update, latency = latency)
339-
watcher$start()
340-
onStop(watcher$stop)
341-
} else {
342-
# Fall back to legacy observer behavior
343-
if (!is_false(getOption("shiny.autoreload.legacy_warning", TRUE))) {
344-
cli::cli_warn(
345-
c(
346-
"Using legacy autoreload file watching. Please install {.pkg watcher} for a more performant autoreload file watcher.",
347-
"i" = "Set {.run options(shiny.autoreload.legacy_warning = FALSE)} to suppress this warning."
348-
),
349-
.frequency = "regularly",
350-
.frequency_id = "shiny.autoreload.legacy_warning"
351-
)
315+
check_for_update <- function(paths) {
316+
paths <- grep(
317+
filePattern,
318+
paths,
319+
ignore.case = TRUE,
320+
value = TRUE
321+
)
322+
323+
if (length(paths) == 0) {
324+
return()
352325
}
353326

354-
lastValue <- NULL
355-
observeLabel <- paste0("File Auto-Reload - '", basename(dir), "'")
356-
watcher <- observe(label = observeLabel, {
357-
files <- sort_c(
358-
list.files(dir, pattern = filePattern, recursive = TRUE, ignore.case = TRUE)
359-
)
360-
times <- file.info(files)$mtime
361-
names(times) <- files
362-
363-
if (is.null(lastValue)) {
364-
# First run
365-
lastValue <<- times
366-
} else if (!identical(lastValue, times)) {
367-
# We've changed!
368-
lastValue <<- times
369-
cachedAutoReloadLastChanged$set()
370-
autoReloadCallbacks$invoke()
371-
}
372-
373-
invalidateLater(getOption("shiny.autoreload.interval", 500))
374-
})
375-
376-
onStop(watcher$destroy)
377-
378-
watcher$destroy
327+
cachedAutoReloadLastChanged$set()
328+
autoReloadCallbacks$invoke()
379329
}
380330

381-
invisible(watcher)
331+
# [garrick, 2025-02-20] Shiny <= v1.10.0 used `invalidateLater()` with an
332+
# autoreload.interval in ms. {watcher} instead uses a latency parameter in
333+
# seconds, which serves a similar purpose and that I'm keeping for backcompat.
334+
latency <- getOption("shiny.autoreload.interval", 250) / 1000
335+
watcher <- watcher::watcher(dir, check_for_update, latency = latency)
336+
watcher$start()
337+
338+
invisible(watcher$stop)
382339
}
383340

384341
#' Load an app's supporting R files

man/shinyOptions.Rd

Lines changed: 6 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)