Skip to content

Commit b3f2948

Browse files
committed
chore: Restore legacy autoreload watcher if {watcher} not installed
1 parent f52efec commit b3f2948

File tree

2 files changed

+65
-25
lines changed

2 files changed

+65
-25
lines changed

DESCRIPTION

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,7 @@ Imports:
9393
glue (>= 1.3.2),
9494
bslib (>= 0.6.0),
9595
cachem (>= 1.1.0),
96-
lifecycle (>= 0.2.0),
97-
watcher
96+
lifecycle (>= 0.2.0)
9897
Suggests:
9998
coro (>= 1.1.0),
10099
datasets,
@@ -112,7 +111,8 @@ Suggests:
112111
dygraphs,
113112
ragg,
114113
showtext,
115-
sass
114+
sass,
115+
watcher
116116
URL: https://shiny.posit.co/,
117117
https://github.com/rstudio/shiny
118118
BugReports: https://github.com/rstudio/shiny/issues

R/shinyapp.R

Lines changed: 62 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -313,32 +313,72 @@ initAutoReloadMonitor <- function(dir) {
313313
".*\\.(r|html?|js|css|png|jpe?g|gif)$"
314314
)
315315

316-
lastValue <- NULL
317-
check_for_update <- function(paths) {
318-
paths <- grep(
319-
filePattern,
320-
paths,
321-
ignore.case = TRUE,
322-
value = TRUE
323-
)
324-
325-
if (length(paths) == 0) {
326-
return()
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 (!isFALSE(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+
)
327352
}
328353

329-
cachedAutoReloadLastChanged$set()
330-
autoReloadCallbacks$invoke()
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
331379
}
332380

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

344384
#' Load an app's supporting R files

0 commit comments

Comments
 (0)