Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
0d7c2b1
add eager_loading parameter
Nov 1, 2019
ef59eeb
:pencil2: add fields for eager_loading
Nov 1, 2019
f36c77a
:name_badge: add buildFingerprint
Nov 7, 2019
a869c4f
:name_badge: add checkFingerprint
Nov 7, 2019
46157f7
temporarily disable percy
rpkyle Dec 3, 2019
bd3ae5e
:see_no_evil: use path instead of mypath
Dec 4, 2019
1bf6158
:truck: dep path resolution into fn for :camel:
Dec 5, 2019
fa4c4bd
:pencil2: Dash-friendly ext/filename helpers
Dec 5, 2019
f351b8b
use getDependencyPath, + :paw_prints:/Etag support
Dec 5, 2019
a4e6754
updates to support async
Dec 13, 2019
436632e
Merge branch 'dev' into 134-async
rpkyle Dec 15, 2019
bae3256
:see_no_evil: fix misplaced paren
Dec 15, 2019
c04b8b4
fix variable name and parens
Dec 15, 2019
fd65186
:see_no_evil: fix misplaced paren
Dec 15, 2019
9cc86cb
Merge branch '134-async' of github.com:plotly/dashR into 134-async
Dec 15, 2019
ac81b37
:hammer: refactor tag generation
Dec 17, 2019
0c77d32
:sparkles: properly support gz compression
Dec 17, 2019
cfced69
sanity checks for response size
Dec 17, 2019
72cb717
update remote refs
Dec 18, 2019
df5888a
:bug: post-async fixes for CSS handling
Dec 18, 2019
0370ec8
add tryCompress fn
Dec 19, 2019
2987a7a
:bug: dashLogger fails when self does not exist
Dec 19, 2019
63885ec
Update CHANGELOG.md
rpkyle Dec 19, 2019
938b304
Update CHANGELOG.md
rpkyle Dec 19, 2019
a0c6bb6
update changelog
Dec 22, 2019
bd6a2d7
fix commit hashes
Dec 22, 2019
eb9521f
update Dash help page
Dec 22, 2019
4bea651
update URL to docs
Dec 22, 2019
937715b
:bug: fix example
Dec 22, 2019
a385ede
fix example; load pkgs
Dec 22, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
# Change Log for Dash for R
All notable changes to this project will be documented in this file.

## Unreleased
## [0.2.0] - 2019-12-23
### Added
- Support for asynchronous/dynamic loading of dependencies, resource caching, and asset fingerprinting [#157](https://github.com/plotly/dashR/pull/157)
- Compression of text resources using `brotli`, `gzip`, or `deflate` [#157](https://github.com/plotly/dashR/pull/157)
- Support for adding `<meta>` tags to index [#142](https://github.com/plotly/dashR/pull/142)
- Hot reloading now supported in debug mode [#127](https://github.com/plotly/dashR/pull/127)
- Support for displaying Dash for R applications within RStudio's viewer pane when `use_viewer = TRUE`
Expand Down
16 changes: 8 additions & 8 deletions DESCRIPTION
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
Package: dash
Title: An Interface to the Dash Ecosystem for Authoring Reactive Web Applications
Version: 0.1.0
Version: 0.2.0
Authors@R: c(person("Chris", "Parmer", role = c("aut"), email = "chris@plot.ly"), person("Ryan Patrick", "Kyle", role = c("aut", "cre"), comment = c(ORCID = "0000-0001-5829-9867"), email = "ryan@plot.ly"), person("Carson", "Sievert", role = c("aut"), comment = c(ORCID = "0000-0002-4958-2844")), person(family = "Plotly Technologies", role = "cph"))
Description: A framework for building analytical web applications, Dash offers a pleasant and productive development experience. No JavaScript required.
Depends:
R (>= 3.0.2)
Imports:
dashHtmlComponents (== 1.0.0),
dashCoreComponents (== 1.0.0),
dashTable (== 4.0.2),
dashHtmlComponents (== 1.0.2),
dashCoreComponents (== 1.6.0),
dashTable (== 4.5.1),
R6,
fiery (> 1.0.0),
routr (> 0.2.0),
plotly,
reqres,
reqres (>= 0.2.3),
jsonlite,
htmltools,
assertthat,
Expand All@@ -31,9 +31,9 @@ Collate:
'imports.R'
'print.R'
'internal.R'
Remotes: plotly/dash-html-components@17da1f4,
plotly/dash-core-components@cc1e654,
plotly/dash-table@042ad65
Remotes: plotly/dash-html-components@55c3884,
plotly/dash-core-components@c107e0f,
plotly/dash-table@3058bd5
License: MIT + file LICENSE
Encoding: UTF-8
LazyData: true
Expand Down
108 changes: 100 additions & 8 deletions R/dash.R
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,7 @@
#' server = fiery::Fire$new(),
#' assets_folder = 'assets',
#' assets_url_path = '/assets',
#' eager_loading = FALSE,
#' assets_ignore = '',
#' serve_locally = TRUE,
#' meta_tags = NULL,
Expand All@@ -30,6 +31,7 @@
#' .css files will be loaded immediately unless excluded by `assets_ignore`,
#' and other files such as images will be served if requested. Default is `assets`. \cr
#' `assets_url_path` \tab \tab Character. Specify the URL path for asset serving. Default is `assets`. \cr
#' `eager_loading` \tab \tab Logical. Controls whether asynchronous resources are prefetched (if `TRUE`) or loaded on-demand (if `FALSE`). \cr
#' `assets_ignore` \tab \tab Character. A regular expression, to match assets to omit from
#' immediate loading. Ignored files will still be served if specifically requested. You
#' cannot use this to prevent access to sensitive files. \cr
Expand DownExpand Up@@ -126,7 +128,10 @@
#'
#' @examples
#' \dontrun{
#' library(dashCoreComponents)
#' library(dashHtmlComponents)
#' library(dash)

#' app <- Dash$new()
#' app$layout(
#' dccInput(id = "inputID", value = "initial value", type = "text"),
Expand DownExpand Up@@ -160,13 +165,15 @@ Dash <- R6::R6Class(
server = fiery::Fire$new(),
assets_folder = 'assets',
assets_url_path = '/assets',
eager_loading = FALSE,
assets_ignore = '',
serve_locally = TRUE,
meta_tags = NULL,
routes_pathname_prefix = NULL,
requests_pathname_prefix = NULL,
external_scripts = NULL,
external_stylesheets = NULL,
compress = TRUE,
suppress_callback_exceptions = FALSE) {

# argument type checking
Expand All@@ -178,12 +185,14 @@ Dash <- R6::R6Class(
# save relevant args as private fields
private$name <- name
private$serve_locally <- serve_locally
private$eager_loading <- eager_loading
# remove leading and trailing slash(es) if present
private$assets_folder <- gsub("^/+|/+$", "", assets_folder)
# remove trailing slash in assets_url_path, if present
private$assets_url_path <- sub("/$", "", assets_url_path)
private$assets_ignore <- assets_ignore
private$suppress_callback_exceptions <- suppress_callback_exceptions
private$compress <- compress
private$app_root_path <- getAppPath()
private$app_launchtime <- as.integer(Sys.time())
private$meta_tags <- meta_tags
Expand DownExpand Up@@ -240,6 +249,9 @@ Dash <- R6::R6Class(
response$body <- to_JSON(lay, pretty = TRUE)
response$status <- 200L
response$type <- 'json'



TRUE
})

Expand All@@ -250,6 +262,7 @@ Dash <- R6::R6Class(
response$body <- to_JSON(list())
response$status <- 200L
response$type <- 'json'

return(FALSE)
}

Expand All@@ -265,6 +278,8 @@ Dash <- R6::R6Class(
response$body <- to_JSON(setNames(payload, NULL))
response$status <- 200L
response$type <- 'json'
if (private$compress)
response <- tryCompress(request, response)
TRUE
})

Expand DownExpand Up@@ -393,17 +408,33 @@ Dash <- R6::R6Class(
response$status <- 500L
private$stack_message <- NULL
}

if (private$compress)
response <- tryCompress(request, response)
TRUE
})

# This endpoint supports dynamic dependency loading
# during `_dash-update-component` -- for reference:
# https://github.com/plotly/dash/blob/1249ffbd051bfb5fdbe439612cbec7fa8fff5ab5/dash/dash.py#L488
# https://docs.python.org/3/library/pkgutil.html#pkgutil.get_data
#
# analogous to
# https://github.com/plotly/dash/blob/2d735aa250fc67b14dc8f6a337d15a16b7cbd6f8/dash/dash.py#L543-L551
dash_suite <- paste0(self$config$routes_pathname_prefix, "_dash-component-suites/:package_name/:filename")

route$add_handler("get", dash_suite, function(request, response, keys, ...) {
filename <- basename(file.path(keys$filename))

# checkFingerprint returns a list of length 2, the first element is
# the un-fingerprinted path, if a fingerprint is present (otherwise
# the original path is returned), while the second element indicates
# whether the original filename included a valid fingerprint (by
# Dash convention)
fingerprinting_metadata <- checkFingerprint(filename)

filename <- fingerprinting_metadata[[1]]
has_fingerprint <- fingerprinting_metadata[[2]] == TRUE

dep_list <- c(private$dependencies_internal,
private$dependencies,
private$dependencies_user)
Expand All@@ -413,7 +444,6 @@ Dash <- R6::R6Class(
clean_dependencies(dep_list)
)


# return warning if a dependency goes unmatched, since the page
# will probably fail to render properly anyway without it
if (length(dep_pkg$rpkg_path) == 0) {
Expand All@@ -424,16 +454,44 @@ Dash <- R6::R6Class(
response$body <- NULL
response$status <- 404L
} else {
# need to check for debug mode, don't cache, don't etag
# if debug mode is not active
dep_path <- system.file(dep_pkg$rpkg_path,
package = dep_pkg$rpkg_name)

response$body <- readLines(dep_path,
warn = FALSE,
encoding = "UTF-8")
response$status <- 200L

if (!private$debug && has_fingerprint) {
response$status <- 200L
response$set_header('Cache-Control',
sprintf('public, max-age=%s',
31536000) # 1 year
)
} else if (!private$debug && !has_fingerprint) {
modified <- as.character(as.integer(file.mtime(dep_path)))

response$set_header('ETag', modified)

request_etag <- request$headers[["If-None-Match"]]

if (!is.null(request_etag) && modified == request_etag) {
response$body <- NULL
response$status <- 304L
} else {
response$status <- 200L
}
} else {
response$status <- 200L
}

response$type <- get_mimetype(filename)
}

if (private$compress && length(response$body) > 0)
response <- tryCompress(request, response)

TRUE
})

Expand DownExpand Up@@ -476,14 +534,20 @@ Dash <- R6::R6Class(
response$body <- readLines(asset_path,
warn = FALSE,
encoding = "UTF-8")

if (private$compress && length(response$body) > 0) {
response <- tryCompress(request, response)
}
} else {
file_handle <- file(asset_path, "rb")
file_size <- file.size(asset_path)

response$body <- readBin(file_handle,
raw(),
file.size(asset_path))
file_size)
close(file_handle)
}

response$status <- 200L
}
TRUE
Expand All@@ -501,8 +565,13 @@ Dash <- R6::R6Class(
file.size(asset_path))
close(file_handle)

response$set_header('Cache-Control',
sprintf('public, max-age=%s',
'31536000')
)
response$type <- 'image/x-icon'
response$status <- 200L

TRUE
})

Expand All@@ -512,6 +581,9 @@ Dash <- R6::R6Class(
response$body <- private$.index
response$status <- 200L
response$type <- 'html'

if (private$compress)
response <- tryCompress(request, response)
TRUE
})

Expand DownExpand Up@@ -540,6 +612,7 @@ Dash <- R6::R6Class(
response$body <- to_JSON(resp)
response$status <- 200L
response$type <- 'json'

# reset the field for the next reloading operation
private$modified_since_reload <- list()
TRUE
Expand DownExpand Up@@ -830,13 +903,15 @@ Dash <- R6::R6Class(
# private fields defined on initiation
name = NULL,
serve_locally = NULL,
eager_loading = NULL,
meta_tags = NULL,
assets_folder = NULL,
assets_url_path = NULL,
assets_ignore = NULL,
routes_pathname_prefix = NULL,
requests_pathname_prefix = NULL,
suppress_callback_exceptions = NULL,
compress = NULL,
asset_map = NULL,
css = NULL,
scripts = NULL,
Expand DownExpand Up@@ -1188,7 +1263,24 @@ Dash <- R6::R6Class(
css_deps <- render_dependencies(css_deps,
local = private$serve_locally,
prefix=self$config$requests_pathname_prefix)


# ensure that no dependency has both async and dynamic set
if (any(
vapply(depsAll, function(dep)
length(intersect(c("dynamic", "async"), names(dep))) > 1,
logical(1)
)
)
)
stop("Can't have both 'dynamic' and 'async' in a Dash dependency; please correct and reload.", call. = FALSE)

# remove dependencies which are dynamic from the script list
# to avoid placing them into the index
depsAll <- depsAll[!vapply(depsAll,
isDynamic,
logical(1),
eager_loading = private$eager_loading)]

# scripts go after dash-renderer dependencies (i.e., React),
# but before dash-renderer itself
scripts_deps <- compact(lapply(depsAll, function(dep) {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Implement support for asynchronous loading in Dash for R by rpkyle · Pull Request #157 · plotly/dashR · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
0d7c2b1
add eager_loading parameter
Nov 1, 2019
ef59eeb
:pencil2: add fields for eager_loading
Nov 1, 2019
f36c77a
:name_badge: add buildFingerprint
Nov 7, 2019
a869c4f
:name_badge: add checkFingerprint
Nov 7, 2019
46157f7
temporarily disable percy
rpkyle Dec 3, 2019
bd3ae5e
:see_no_evil: use path instead of mypath
Dec 4, 2019
1bf6158
:truck: dep path resolution into fn for :camel:
Dec 5, 2019
fa4c4bd
:pencil2: Dash-friendly ext/filename helpers
Dec 5, 2019
f351b8b
use getDependencyPath, + :paw_prints:/Etag support
Dec 5, 2019
a4e6754
updates to support async
Dec 13, 2019
436632e
Merge branch 'dev' into 134-async
rpkyle Dec 15, 2019
bae3256
:see_no_evil: fix misplaced paren
Dec 15, 2019
c04b8b4
fix variable name and parens
Dec 15, 2019
fd65186
:see_no_evil: fix misplaced paren
Dec 15, 2019
9cc86cb
Merge branch '134-async' of github.com:plotly/dashR into 134-async
Dec 15, 2019
ac81b37
:hammer: refactor tag generation
Dec 17, 2019
0c77d32
:sparkles: properly support gz compression
Dec 17, 2019
cfced69
sanity checks for response size
Dec 17, 2019
72cb717
update remote refs
Dec 18, 2019
df5888a
:bug: post-async fixes for CSS handling
Dec 18, 2019
0370ec8
add tryCompress fn
Dec 19, 2019
2987a7a
:bug: dashLogger fails when self does not exist
Dec 19, 2019
63885ec
Update CHANGELOG.md
rpkyle Dec 19, 2019
938b304
Update CHANGELOG.md
rpkyle Dec 19, 2019
a0c6bb6
update changelog
Dec 22, 2019
bd6a2d7
fix commit hashes
Dec 22, 2019
eb9521f
update Dash help page
Dec 22, 2019
4bea651
update URL to docs
Dec 22, 2019
937715b
:bug: fix example
Dec 22, 2019
a385ede
fix example; load pkgs
Dec 22, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
# Change Log for Dash for R
All notable changes to this project will be documented in this file.

## Unreleased
## [0.2.0] - 2019-12-23
### Added
- Support for asynchronous/dynamic loading of dependencies, resource caching, and asset fingerprinting [#157](https://github.com/plotly/dashR/pull/157)
- Compression of text resources using `brotli`, `gzip`, or `deflate` [#157](https://github.com/plotly/dashR/pull/157)
- Support for adding `<meta>` tags to index [#142](https://github.com/plotly/dashR/pull/142)
- Hot reloading now supported in debug mode [#127](https://github.com/plotly/dashR/pull/127)
- Support for displaying Dash for R applications within RStudio's viewer pane when `use_viewer = TRUE`
Expand Down
16 changes: 8 additions & 8 deletions DESCRIPTION
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
Package: dash
Title: An Interface to the Dash Ecosystem for Authoring Reactive Web Applications
Version: 0.1.0
Version: 0.2.0
Authors@R: c(person("Chris", "Parmer", role = c("aut"), email = "chris@plot.ly"), person("Ryan Patrick", "Kyle", role = c("aut", "cre"), comment = c(ORCID = "0000-0001-5829-9867"), email = "ryan@plot.ly"), person("Carson", "Sievert", role = c("aut"), comment = c(ORCID = "0000-0002-4958-2844")), person(family = "Plotly Technologies", role = "cph"))
Description: A framework for building analytical web applications, Dash offers a pleasant and productive development experience. No JavaScript required.
Depends:
R (>= 3.0.2)
Imports:
dashHtmlComponents (== 1.0.0),
dashCoreComponents (== 1.0.0),
dashTable (== 4.0.2),
dashHtmlComponents (== 1.0.2),
dashCoreComponents (== 1.6.0),
dashTable (== 4.5.1),
R6,
fiery (> 1.0.0),
routr (> 0.2.0),
plotly,
reqres,
reqres (>= 0.2.3),
jsonlite,
htmltools,
assertthat,
Expand All@@ -31,9 +31,9 @@ Collate:
'imports.R'
'print.R'
'internal.R'
Remotes: plotly/dash-html-components@17da1f4,
plotly/dash-core-components@cc1e654,
plotly/dash-table@042ad65
Remotes: plotly/dash-html-components@55c3884,
plotly/dash-core-components@c107e0f,
plotly/dash-table@3058bd5
License: MIT + file LICENSE
Encoding: UTF-8
LazyData: true
Expand Down
108 changes: 100 additions & 8 deletions R/dash.R
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,7 @@
#' server = fiery::Fire$new(),
#' assets_folder = 'assets',
#' assets_url_path = '/assets',
#' eager_loading = FALSE,
#' assets_ignore = '',
#' serve_locally = TRUE,
#' meta_tags = NULL,
Expand All@@ -30,6 +31,7 @@
#' .css files will be loaded immediately unless excluded by `assets_ignore`,
#' and other files such as images will be served if requested. Default is `assets`. \cr
#' `assets_url_path` \tab \tab Character. Specify the URL path for asset serving. Default is `assets`. \cr
#' `eager_loading` \tab \tab Logical. Controls whether asynchronous resources are prefetched (if `TRUE`) or loaded on-demand (if `FALSE`). \cr
#' `assets_ignore` \tab \tab Character. A regular expression, to match assets to omit from
#' immediate loading. Ignored files will still be served if specifically requested. You
#' cannot use this to prevent access to sensitive files. \cr
Expand DownExpand Up@@ -126,7 +128,10 @@
#'
#' @examples
#' \dontrun{
#' library(dashCoreComponents)
#' library(dashHtmlComponents)
#' library(dash)

#' app <- Dash$new()
#' app$layout(
#' dccInput(id = "inputID", value = "initial value", type = "text"),
Expand DownExpand Up@@ -160,13 +165,15 @@ Dash <- R6::R6Class(
server = fiery::Fire$new(),
assets_folder = 'assets',
assets_url_path = '/assets',
eager_loading = FALSE,
assets_ignore = '',
serve_locally = TRUE,
meta_tags = NULL,
routes_pathname_prefix = NULL,
requests_pathname_prefix = NULL,
external_scripts = NULL,
external_stylesheets = NULL,
compress = TRUE,
suppress_callback_exceptions = FALSE) {

# argument type checking
Expand All@@ -178,12 +185,14 @@ Dash <- R6::R6Class(
# save relevant args as private fields
private$name <- name
private$serve_locally <- serve_locally
private$eager_loading <- eager_loading
# remove leading and trailing slash(es) if present
private$assets_folder <- gsub("^/+|/+$", "", assets_folder)
# remove trailing slash in assets_url_path, if present
private$assets_url_path <- sub("/$", "", assets_url_path)
private$assets_ignore <- assets_ignore
private$suppress_callback_exceptions <- suppress_callback_exceptions
private$compress <- compress
private$app_root_path <- getAppPath()
private$app_launchtime <- as.integer(Sys.time())
private$meta_tags <- meta_tags
Expand DownExpand Up@@ -240,6 +249,9 @@ Dash <- R6::R6Class(
response$body <- to_JSON(lay, pretty = TRUE)
response$status <- 200L
response$type <- 'json'



TRUE
})

Expand All@@ -250,6 +262,7 @@ Dash <- R6::R6Class(
response$body <- to_JSON(list())
response$status <- 200L
response$type <- 'json'

return(FALSE)
}

Expand All@@ -265,6 +278,8 @@ Dash <- R6::R6Class(
response$body <- to_JSON(setNames(payload, NULL))
response$status <- 200L
response$type <- 'json'
if (private$compress)
response <- tryCompress(request, response)
TRUE
})

Expand DownExpand Up@@ -393,17 +408,33 @@ Dash <- R6::R6Class(
response$status <- 500L
private$stack_message <- NULL
}

if (private$compress)
response <- tryCompress(request, response)
TRUE
})

# This endpoint supports dynamic dependency loading
# during `_dash-update-component` -- for reference:
# https://github.com/plotly/dash/blob/1249ffbd051bfb5fdbe439612cbec7fa8fff5ab5/dash/dash.py#L488
# https://docs.python.org/3/library/pkgutil.html#pkgutil.get_data
#
# analogous to
# https://github.com/plotly/dash/blob/2d735aa250fc67b14dc8f6a337d15a16b7cbd6f8/dash/dash.py#L543-L551
dash_suite <- paste0(self$config$routes_pathname_prefix, "_dash-component-suites/:package_name/:filename")

route$add_handler("get", dash_suite, function(request, response, keys, ...) {
filename <- basename(file.path(keys$filename))

# checkFingerprint returns a list of length 2, the first element is
# the un-fingerprinted path, if a fingerprint is present (otherwise
# the original path is returned), while the second element indicates
# whether the original filename included a valid fingerprint (by
# Dash convention)
fingerprinting_metadata <- checkFingerprint(filename)

filename <- fingerprinting_metadata[[1]]
has_fingerprint <- fingerprinting_metadata[[2]] == TRUE

dep_list <- c(private$dependencies_internal,
private$dependencies,
private$dependencies_user)
Expand All@@ -413,7 +444,6 @@ Dash <- R6::R6Class(
clean_dependencies(dep_list)
)


# return warning if a dependency goes unmatched, since the page
# will probably fail to render properly anyway without it
if (length(dep_pkg$rpkg_path) == 0) {
Expand All@@ -424,16 +454,44 @@ Dash <- R6::R6Class(
response$body <- NULL
response$status <- 404L
} else {
# need to check for debug mode, don't cache, don't etag
# if debug mode is not active
dep_path <- system.file(dep_pkg$rpkg_path,
package = dep_pkg$rpkg_name)

response$body <- readLines(dep_path,
warn = FALSE,
encoding = "UTF-8")
response$status <- 200L

if (!private$debug && has_fingerprint) {
response$status <- 200L
response$set_header('Cache-Control',
sprintf('public, max-age=%s',
31536000) # 1 year
)
} else if (!private$debug && !has_fingerprint) {
modified <- as.character(as.integer(file.mtime(dep_path)))

response$set_header('ETag', modified)

request_etag <- request$headers[["If-None-Match"]]

if (!is.null(request_etag) && modified == request_etag) {
response$body <- NULL
response$status <- 304L
} else {
response$status <- 200L
}
} else {
response$status <- 200L
}

response$type <- get_mimetype(filename)
}

if (private$compress && length(response$body) > 0)
response <- tryCompress(request, response)

TRUE
})

Expand DownExpand Up@@ -476,14 +534,20 @@ Dash <- R6::R6Class(
response$body <- readLines(asset_path,
warn = FALSE,
encoding = "UTF-8")

if (private$compress && length(response$body) > 0) {
response <- tryCompress(request, response)
}
} else {
file_handle <- file(asset_path, "rb")
file_size <- file.size(asset_path)

response$body <- readBin(file_handle,
raw(),
file.size(asset_path))
file_size)
close(file_handle)
}

response$status <- 200L
}
TRUE
Expand All@@ -501,8 +565,13 @@ Dash <- R6::R6Class(
file.size(asset_path))
close(file_handle)

response$set_header('Cache-Control',
sprintf('public, max-age=%s',
'31536000')
)
response$type <- 'image/x-icon'
response$status <- 200L

TRUE
})

Expand All@@ -512,6 +581,9 @@ Dash <- R6::R6Class(
response$body <- private$.index
response$status <- 200L
response$type <- 'html'

if (private$compress)
response <- tryCompress(request, response)
TRUE
})

Expand DownExpand Up@@ -540,6 +612,7 @@ Dash <- R6::R6Class(
response$body <- to_JSON(resp)
response$status <- 200L
response$type <- 'json'

# reset the field for the next reloading operation
private$modified_since_reload <- list()
TRUE
Expand DownExpand Up@@ -830,13 +903,15 @@ Dash <- R6::R6Class(
# private fields defined on initiation
name = NULL,
serve_locally = NULL,
eager_loading = NULL,
meta_tags = NULL,
assets_folder = NULL,
assets_url_path = NULL,
assets_ignore = NULL,
routes_pathname_prefix = NULL,
requests_pathname_prefix = NULL,
suppress_callback_exceptions = NULL,
compress = NULL,
asset_map = NULL,
css = NULL,
scripts = NULL,
Expand DownExpand Up@@ -1188,7 +1263,24 @@ Dash <- R6::R6Class(
css_deps <- render_dependencies(css_deps,
local = private$serve_locally,
prefix=self$config$requests_pathname_prefix)


# ensure that no dependency has both async and dynamic set
if (any(
vapply(depsAll, function(dep)
length(intersect(c("dynamic", "async"), names(dep))) > 1,
logical(1)
)
)
)
stop("Can't have both 'dynamic' and 'async' in a Dash dependency; please correct and reload.", call. = FALSE)

# remove dependencies which are dynamic from the script list
# to avoid placing them into the index
depsAll <- depsAll[!vapply(depsAll,
isDynamic,
logical(1),
eager_loading = private$eager_loading)]

# scripts go after dash-renderer dependencies (i.e., React),
# but before dash-renderer itself
scripts_deps <- compact(lapply(depsAll, function(dep) {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Implement support for asynchronous loading in Dash for R by rpkyle · Pull Request #157 · plotly/dashR · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
0d7c2b1
add eager_loading parameter
Nov 1, 2019
ef59eeb
:pencil2: add fields for eager_loading
Nov 1, 2019
f36c77a
:name_badge: add buildFingerprint
Nov 7, 2019
a869c4f
:name_badge: add checkFingerprint
Nov 7, 2019
46157f7
temporarily disable percy
rpkyle Dec 3, 2019
bd3ae5e
:see_no_evil: use path instead of mypath
Dec 4, 2019
1bf6158
:truck: dep path resolution into fn for :camel:
Dec 5, 2019
fa4c4bd
:pencil2: Dash-friendly ext/filename helpers
Dec 5, 2019
f351b8b
use getDependencyPath, + :paw_prints:/Etag support
Dec 5, 2019
a4e6754
updates to support async
Dec 13, 2019
436632e
Merge branch 'dev' into 134-async
rpkyle Dec 15, 2019
bae3256
:see_no_evil: fix misplaced paren
Dec 15, 2019
c04b8b4
fix variable name and parens
Dec 15, 2019
fd65186
:see_no_evil: fix misplaced paren
Dec 15, 2019
9cc86cb
Merge branch '134-async' of github.com:plotly/dashR into 134-async
Dec 15, 2019
ac81b37
:hammer: refactor tag generation
Dec 17, 2019
0c77d32
:sparkles: properly support gz compression
Dec 17, 2019
cfced69
sanity checks for response size
Dec 17, 2019
72cb717
update remote refs
Dec 18, 2019
df5888a
:bug: post-async fixes for CSS handling
Dec 18, 2019
0370ec8
add tryCompress fn
Dec 19, 2019
2987a7a
:bug: dashLogger fails when self does not exist
Dec 19, 2019
63885ec
Update CHANGELOG.md
rpkyle Dec 19, 2019
938b304
Update CHANGELOG.md
rpkyle Dec 19, 2019
a0c6bb6
update changelog
Dec 22, 2019
bd6a2d7
fix commit hashes
Dec 22, 2019
eb9521f
update Dash help page
Dec 22, 2019
4bea651
update URL to docs
Dec 22, 2019
937715b
:bug: fix example
Dec 22, 2019
a385ede
fix example; load pkgs
Dec 22, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
# Change Log for Dash for R
All notable changes to this project will be documented in this file.

## Unreleased
## [0.2.0] - 2019-12-23
### Added
- Support for asynchronous/dynamic loading of dependencies, resource caching, and asset fingerprinting [#157](https://github.com/plotly/dashR/pull/157)
- Compression of text resources using `brotli`, `gzip`, or `deflate` [#157](https://github.com/plotly/dashR/pull/157)
- Support for adding `<meta>` tags to index [#142](https://github.com/plotly/dashR/pull/142)
- Hot reloading now supported in debug mode [#127](https://github.com/plotly/dashR/pull/127)
- Support for displaying Dash for R applications within RStudio's viewer pane when `use_viewer = TRUE`
Expand Down
16 changes: 8 additions & 8 deletions DESCRIPTION
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
Package: dash
Title: An Interface to the Dash Ecosystem for Authoring Reactive Web Applications
Version: 0.1.0
Version: 0.2.0
Authors@R: c(person("Chris", "Parmer", role = c("aut"), email = "chris@plot.ly"), person("Ryan Patrick", "Kyle", role = c("aut", "cre"), comment = c(ORCID = "0000-0001-5829-9867"), email = "ryan@plot.ly"), person("Carson", "Sievert", role = c("aut"), comment = c(ORCID = "0000-0002-4958-2844")), person(family = "Plotly Technologies", role = "cph"))
Description: A framework for building analytical web applications, Dash offers a pleasant and productive development experience. No JavaScript required.
Depends:
R (>= 3.0.2)
Imports:
dashHtmlComponents (== 1.0.0),
dashCoreComponents (== 1.0.0),
dashTable (== 4.0.2),
dashHtmlComponents (== 1.0.2),
dashCoreComponents (== 1.6.0),
dashTable (== 4.5.1),
R6,
fiery (> 1.0.0),
routr (> 0.2.0),
plotly,
reqres,
reqres (>= 0.2.3),
jsonlite,
htmltools,
assertthat,
Expand All@@ -31,9 +31,9 @@ Collate:
'imports.R'
'print.R'
'internal.R'
Remotes: plotly/dash-html-components@17da1f4,
plotly/dash-core-components@cc1e654,
plotly/dash-table@042ad65
Remotes: plotly/dash-html-components@55c3884,
plotly/dash-core-components@c107e0f,
plotly/dash-table@3058bd5
License: MIT + file LICENSE
Encoding: UTF-8
LazyData: true
Expand Down
108 changes: 100 additions & 8 deletions R/dash.R
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,7 @@
#' server = fiery::Fire$new(),
#' assets_folder = 'assets',
#' assets_url_path = '/assets',
#' eager_loading = FALSE,
#' assets_ignore = '',
#' serve_locally = TRUE,
#' meta_tags = NULL,
Expand All@@ -30,6 +31,7 @@
#' .css files will be loaded immediately unless excluded by `assets_ignore`,
#' and other files such as images will be served if requested. Default is `assets`. \cr
#' `assets_url_path` \tab \tab Character. Specify the URL path for asset serving. Default is `assets`. \cr
#' `eager_loading` \tab \tab Logical. Controls whether asynchronous resources are prefetched (if `TRUE`) or loaded on-demand (if `FALSE`). \cr
#' `assets_ignore` \tab \tab Character. A regular expression, to match assets to omit from
#' immediate loading. Ignored files will still be served if specifically requested. You
#' cannot use this to prevent access to sensitive files. \cr
Expand DownExpand Up@@ -126,7 +128,10 @@
#'
#' @examples
#' \dontrun{
#' library(dashCoreComponents)
#' library(dashHtmlComponents)
#' library(dash)

#' app <- Dash$new()
#' app$layout(
#' dccInput(id = "inputID", value = "initial value", type = "text"),
Expand DownExpand Up@@ -160,13 +165,15 @@ Dash <- R6::R6Class(
server = fiery::Fire$new(),
assets_folder = 'assets',
assets_url_path = '/assets',
eager_loading = FALSE,
assets_ignore = '',
serve_locally = TRUE,
meta_tags = NULL,
routes_pathname_prefix = NULL,
requests_pathname_prefix = NULL,
external_scripts = NULL,
external_stylesheets = NULL,
compress = TRUE,
suppress_callback_exceptions = FALSE) {

# argument type checking
Expand All@@ -178,12 +185,14 @@ Dash <- R6::R6Class(
# save relevant args as private fields
private$name <- name
private$serve_locally <- serve_locally
private$eager_loading <- eager_loading
# remove leading and trailing slash(es) if present
private$assets_folder <- gsub("^/+|/+$", "", assets_folder)
# remove trailing slash in assets_url_path, if present
private$assets_url_path <- sub("/$", "", assets_url_path)
private$assets_ignore <- assets_ignore
private$suppress_callback_exceptions <- suppress_callback_exceptions
private$compress <- compress
private$app_root_path <- getAppPath()
private$app_launchtime <- as.integer(Sys.time())
private$meta_tags <- meta_tags
Expand DownExpand Up@@ -240,6 +249,9 @@ Dash <- R6::R6Class(
response$body <- to_JSON(lay, pretty = TRUE)
response$status <- 200L
response$type <- 'json'



TRUE
})

Expand All@@ -250,6 +262,7 @@ Dash <- R6::R6Class(
response$body <- to_JSON(list())
response$status <- 200L
response$type <- 'json'

return(FALSE)
}

Expand All@@ -265,6 +278,8 @@ Dash <- R6::R6Class(
response$body <- to_JSON(setNames(payload, NULL))
response$status <- 200L
response$type <- 'json'
if (private$compress)
response <- tryCompress(request, response)
TRUE
})

Expand DownExpand Up@@ -393,17 +408,33 @@ Dash <- R6::R6Class(
response$status <- 500L
private$stack_message <- NULL
}

if (private$compress)
response <- tryCompress(request, response)
TRUE
})

# This endpoint supports dynamic dependency loading
# during `_dash-update-component` -- for reference:
# https://github.com/plotly/dash/blob/1249ffbd051bfb5fdbe439612cbec7fa8fff5ab5/dash/dash.py#L488
# https://docs.python.org/3/library/pkgutil.html#pkgutil.get_data
#
# analogous to
# https://github.com/plotly/dash/blob/2d735aa250fc67b14dc8f6a337d15a16b7cbd6f8/dash/dash.py#L543-L551
dash_suite <- paste0(self$config$routes_pathname_prefix, "_dash-component-suites/:package_name/:filename")

route$add_handler("get", dash_suite, function(request, response, keys, ...) {
filename <- basename(file.path(keys$filename))

# checkFingerprint returns a list of length 2, the first element is
# the un-fingerprinted path, if a fingerprint is present (otherwise
# the original path is returned), while the second element indicates
# whether the original filename included a valid fingerprint (by
# Dash convention)
fingerprinting_metadata <- checkFingerprint(filename)

filename <- fingerprinting_metadata[[1]]
has_fingerprint <- fingerprinting_metadata[[2]] == TRUE

dep_list <- c(private$dependencies_internal,
private$dependencies,
private$dependencies_user)
Expand All@@ -413,7 +444,6 @@ Dash <- R6::R6Class(
clean_dependencies(dep_list)
)


# return warning if a dependency goes unmatched, since the page
# will probably fail to render properly anyway without it
if (length(dep_pkg$rpkg_path) == 0) {
Expand All@@ -424,16 +454,44 @@ Dash <- R6::R6Class(
response$body <- NULL
response$status <- 404L
} else {
# need to check for debug mode, don't cache, don't etag
# if debug mode is not active
dep_path <- system.file(dep_pkg$rpkg_path,
package = dep_pkg$rpkg_name)

response$body <- readLines(dep_path,
warn = FALSE,
encoding = "UTF-8")
response$status <- 200L

if (!private$debug && has_fingerprint) {
response$status <- 200L
response$set_header('Cache-Control',
sprintf('public, max-age=%s',
31536000) # 1 year
)
} else if (!private$debug && !has_fingerprint) {
modified <- as.character(as.integer(file.mtime(dep_path)))

response$set_header('ETag', modified)

request_etag <- request$headers[["If-None-Match"]]

if (!is.null(request_etag) && modified == request_etag) {
response$body <- NULL
response$status <- 304L
} else {
response$status <- 200L
}
} else {
response$status <- 200L
}

response$type <- get_mimetype(filename)
}

if (private$compress && length(response$body) > 0)
response <- tryCompress(request, response)

TRUE
})

Expand DownExpand Up@@ -476,14 +534,20 @@ Dash <- R6::R6Class(
response$body <- readLines(asset_path,
warn = FALSE,
encoding = "UTF-8")

if (private$compress && length(response$body) > 0) {
response <- tryCompress(request, response)
}
} else {
file_handle <- file(asset_path, "rb")
file_size <- file.size(asset_path)

response$body <- readBin(file_handle,
raw(),
file.size(asset_path))
file_size)
close(file_handle)
}

response$status <- 200L
}
TRUE
Expand All@@ -501,8 +565,13 @@ Dash <- R6::R6Class(
file.size(asset_path))
close(file_handle)

response$set_header('Cache-Control',
sprintf('public, max-age=%s',
'31536000')
)
response$type <- 'image/x-icon'
response$status <- 200L

TRUE
})

Expand All@@ -512,6 +581,9 @@ Dash <- R6::R6Class(
response$body <- private$.index
response$status <- 200L
response$type <- 'html'

if (private$compress)
response <- tryCompress(request, response)
TRUE
})

Expand DownExpand Up@@ -540,6 +612,7 @@ Dash <- R6::R6Class(
response$body <- to_JSON(resp)
response$status <- 200L
response$type <- 'json'

# reset the field for the next reloading operation
private$modified_since_reload <- list()
TRUE
Expand DownExpand Up@@ -830,13 +903,15 @@ Dash <- R6::R6Class(
# private fields defined on initiation
name = NULL,
serve_locally = NULL,
eager_loading = NULL,
meta_tags = NULL,
assets_folder = NULL,
assets_url_path = NULL,
assets_ignore = NULL,
routes_pathname_prefix = NULL,
requests_pathname_prefix = NULL,
suppress_callback_exceptions = NULL,
compress = NULL,
asset_map = NULL,
css = NULL,
scripts = NULL,
Expand DownExpand Up@@ -1188,7 +1263,24 @@ Dash <- R6::R6Class(
css_deps <- render_dependencies(css_deps,
local = private$serve_locally,
prefix=self$config$requests_pathname_prefix)


# ensure that no dependency has both async and dynamic set
if (any(
vapply(depsAll, function(dep)
length(intersect(c("dynamic", "async"), names(dep))) > 1,
logical(1)
)
)
)
stop("Can't have both 'dynamic' and 'async' in a Dash dependency; please correct and reload.", call. = FALSE)

# remove dependencies which are dynamic from the script list
# to avoid placing them into the index
depsAll <- depsAll[!vapply(depsAll,
isDynamic,
logical(1),
eager_loading = private$eager_loading)]

# scripts go after dash-renderer dependencies (i.e., React),
# but before dash-renderer itself
scripts_deps <- compact(lapply(depsAll, function(dep) {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Implement support for asynchronous loading in Dash for R by rpkyle · Pull Request #157 · plotly/dashR · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
0d7c2b1
add eager_loading parameter
Nov 1, 2019
ef59eeb
:pencil2: add fields for eager_loading
Nov 1, 2019
f36c77a
:name_badge: add buildFingerprint
Nov 7, 2019
a869c4f
:name_badge: add checkFingerprint
Nov 7, 2019
46157f7
temporarily disable percy
rpkyle Dec 3, 2019
bd3ae5e
:see_no_evil: use path instead of mypath
Dec 4, 2019
1bf6158
:truck: dep path resolution into fn for :camel:
Dec 5, 2019
fa4c4bd
:pencil2: Dash-friendly ext/filename helpers
Dec 5, 2019
f351b8b
use getDependencyPath, + :paw_prints:/Etag support
Dec 5, 2019
a4e6754
updates to support async
Dec 13, 2019
436632e
Merge branch 'dev' into 134-async
rpkyle Dec 15, 2019
bae3256
:see_no_evil: fix misplaced paren
Dec 15, 2019
c04b8b4
fix variable name and parens
Dec 15, 2019
fd65186
:see_no_evil: fix misplaced paren
Dec 15, 2019
9cc86cb
Merge branch '134-async' of github.com:plotly/dashR into 134-async
Dec 15, 2019
ac81b37
:hammer: refactor tag generation
Dec 17, 2019
0c77d32
:sparkles: properly support gz compression
Dec 17, 2019
cfced69
sanity checks for response size
Dec 17, 2019
72cb717
update remote refs
Dec 18, 2019
df5888a
:bug: post-async fixes for CSS handling
Dec 18, 2019
0370ec8
add tryCompress fn
Dec 19, 2019
2987a7a
:bug: dashLogger fails when self does not exist
Dec 19, 2019
63885ec
Update CHANGELOG.md
rpkyle Dec 19, 2019
938b304
Update CHANGELOG.md
rpkyle Dec 19, 2019
a0c6bb6
update changelog
Dec 22, 2019
bd6a2d7
fix commit hashes
Dec 22, 2019
eb9521f
update Dash help page
Dec 22, 2019
4bea651
update URL to docs
Dec 22, 2019
937715b
:bug: fix example
Dec 22, 2019
a385ede
fix example; load pkgs
Dec 22, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
# Change Log for Dash for R
All notable changes to this project will be documented in this file.

## Unreleased
## [0.2.0] - 2019-12-23
### Added
- Support for asynchronous/dynamic loading of dependencies, resource caching, and asset fingerprinting [#157](https://github.com/plotly/dashR/pull/157)
- Compression of text resources using `brotli`, `gzip`, or `deflate` [#157](https://github.com/plotly/dashR/pull/157)
- Support for adding `<meta>` tags to index [#142](https://github.com/plotly/dashR/pull/142)
- Hot reloading now supported in debug mode [#127](https://github.com/plotly/dashR/pull/127)
- Support for displaying Dash for R applications within RStudio's viewer pane when `use_viewer = TRUE`
Expand Down
16 changes: 8 additions & 8 deletions DESCRIPTION
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
Package: dash
Title: An Interface to the Dash Ecosystem for Authoring Reactive Web Applications
Version: 0.1.0
Version: 0.2.0
Authors@R: c(person("Chris", "Parmer", role = c("aut"), email = "chris@plot.ly"), person("Ryan Patrick", "Kyle", role = c("aut", "cre"), comment = c(ORCID = "0000-0001-5829-9867"), email = "ryan@plot.ly"), person("Carson", "Sievert", role = c("aut"), comment = c(ORCID = "0000-0002-4958-2844")), person(family = "Plotly Technologies", role = "cph"))
Description: A framework for building analytical web applications, Dash offers a pleasant and productive development experience. No JavaScript required.
Depends:
R (>= 3.0.2)
Imports:
dashHtmlComponents (== 1.0.0),
dashCoreComponents (== 1.0.0),
dashTable (== 4.0.2),
dashHtmlComponents (== 1.0.2),
dashCoreComponents (== 1.6.0),
dashTable (== 4.5.1),
R6,
fiery (> 1.0.0),
routr (> 0.2.0),
plotly,
reqres,
reqres (>= 0.2.3),
jsonlite,
htmltools,
assertthat,
Expand All@@ -31,9 +31,9 @@ Collate:
'imports.R'
'print.R'
'internal.R'
Remotes: plotly/dash-html-components@17da1f4,
plotly/dash-core-components@cc1e654,
plotly/dash-table@042ad65
Remotes: plotly/dash-html-components@55c3884,
plotly/dash-core-components@c107e0f,
plotly/dash-table@3058bd5
License: MIT + file LICENSE
Encoding: UTF-8
LazyData: true
Expand Down
108 changes: 100 additions & 8 deletions R/dash.R
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,7 @@
#' server = fiery::Fire$new(),
#' assets_folder = 'assets',
#' assets_url_path = '/assets',
#' eager_loading = FALSE,
#' assets_ignore = '',
#' serve_locally = TRUE,
#' meta_tags = NULL,
Expand All@@ -30,6 +31,7 @@
#' .css files will be loaded immediately unless excluded by `assets_ignore`,
#' and other files such as images will be served if requested. Default is `assets`. \cr
#' `assets_url_path` \tab \tab Character. Specify the URL path for asset serving. Default is `assets`. \cr
#' `eager_loading` \tab \tab Logical. Controls whether asynchronous resources are prefetched (if `TRUE`) or loaded on-demand (if `FALSE`). \cr
#' `assets_ignore` \tab \tab Character. A regular expression, to match assets to omit from
#' immediate loading. Ignored files will still be served if specifically requested. You
#' cannot use this to prevent access to sensitive files. \cr
Expand DownExpand Up@@ -126,7 +128,10 @@
#'
#' @examples
#' \dontrun{
#' library(dashCoreComponents)
#' library(dashHtmlComponents)
#' library(dash)

#' app <- Dash$new()
#' app$layout(
#' dccInput(id = "inputID", value = "initial value", type = "text"),
Expand DownExpand Up@@ -160,13 +165,15 @@ Dash <- R6::R6Class(
server = fiery::Fire$new(),
assets_folder = 'assets',
assets_url_path = '/assets',
eager_loading = FALSE,
assets_ignore = '',
serve_locally = TRUE,
meta_tags = NULL,
routes_pathname_prefix = NULL,
requests_pathname_prefix = NULL,
external_scripts = NULL,
external_stylesheets = NULL,
compress = TRUE,
suppress_callback_exceptions = FALSE) {

# argument type checking
Expand All@@ -178,12 +185,14 @@ Dash <- R6::R6Class(
# save relevant args as private fields
private$name <- name
private$serve_locally <- serve_locally
private$eager_loading <- eager_loading
# remove leading and trailing slash(es) if present
private$assets_folder <- gsub("^/+|/+$", "", assets_folder)
# remove trailing slash in assets_url_path, if present
private$assets_url_path <- sub("/$", "", assets_url_path)
private$assets_ignore <- assets_ignore
private$suppress_callback_exceptions <- suppress_callback_exceptions
private$compress <- compress
private$app_root_path <- getAppPath()
private$app_launchtime <- as.integer(Sys.time())
private$meta_tags <- meta_tags
Expand DownExpand Up@@ -240,6 +249,9 @@ Dash <- R6::R6Class(
response$body <- to_JSON(lay, pretty = TRUE)
response$status <- 200L
response$type <- 'json'



TRUE
})

Expand All@@ -250,6 +262,7 @@ Dash <- R6::R6Class(
response$body <- to_JSON(list())
response$status <- 200L
response$type <- 'json'

return(FALSE)
}

Expand All@@ -265,6 +278,8 @@ Dash <- R6::R6Class(
response$body <- to_JSON(setNames(payload, NULL))
response$status <- 200L
response$type <- 'json'
if (private$compress)
response <- tryCompress(request, response)
TRUE
})

Expand DownExpand Up@@ -393,17 +408,33 @@ Dash <- R6::R6Class(
response$status <- 500L
private$stack_message <- NULL
}

if (private$compress)
response <- tryCompress(request, response)
TRUE
})

# This endpoint supports dynamic dependency loading
# during `_dash-update-component` -- for reference:
# https://github.com/plotly/dash/blob/1249ffbd051bfb5fdbe439612cbec7fa8fff5ab5/dash/dash.py#L488
# https://docs.python.org/3/library/pkgutil.html#pkgutil.get_data
#
# analogous to
# https://github.com/plotly/dash/blob/2d735aa250fc67b14dc8f6a337d15a16b7cbd6f8/dash/dash.py#L543-L551
dash_suite <- paste0(self$config$routes_pathname_prefix, "_dash-component-suites/:package_name/:filename")

route$add_handler("get", dash_suite, function(request, response, keys, ...) {
filename <- basename(file.path(keys$filename))

# checkFingerprint returns a list of length 2, the first element is
# the un-fingerprinted path, if a fingerprint is present (otherwise
# the original path is returned), while the second element indicates
# whether the original filename included a valid fingerprint (by
# Dash convention)
fingerprinting_metadata <- checkFingerprint(filename)

filename <- fingerprinting_metadata[[1]]
has_fingerprint <- fingerprinting_metadata[[2]] == TRUE

dep_list <- c(private$dependencies_internal,
private$dependencies,
private$dependencies_user)
Expand All@@ -413,7 +444,6 @@ Dash <- R6::R6Class(
clean_dependencies(dep_list)
)


# return warning if a dependency goes unmatched, since the page
# will probably fail to render properly anyway without it
if (length(dep_pkg$rpkg_path) == 0) {
Expand All@@ -424,16 +454,44 @@ Dash <- R6::R6Class(
response$body <- NULL
response$status <- 404L
} else {
# need to check for debug mode, don't cache, don't etag
# if debug mode is not active
dep_path <- system.file(dep_pkg$rpkg_path,
package = dep_pkg$rpkg_name)

response$body <- readLines(dep_path,
warn = FALSE,
encoding = "UTF-8")
response$status <- 200L

if (!private$debug && has_fingerprint) {
response$status <- 200L
response$set_header('Cache-Control',
sprintf('public, max-age=%s',
31536000) # 1 year
)
} else if (!private$debug && !has_fingerprint) {
modified <- as.character(as.integer(file.mtime(dep_path)))

response$set_header('ETag', modified)

request_etag <- request$headers[["If-None-Match"]]

if (!is.null(request_etag) && modified == request_etag) {
response$body <- NULL
response$status <- 304L
} else {
response$status <- 200L
}
} else {
response$status <- 200L
}

response$type <- get_mimetype(filename)
}

if (private$compress && length(response$body) > 0)
response <- tryCompress(request, response)

TRUE
})

Expand DownExpand Up@@ -476,14 +534,20 @@ Dash <- R6::R6Class(
response$body <- readLines(asset_path,
warn = FALSE,
encoding = "UTF-8")

if (private$compress && length(response$body) > 0) {
response <- tryCompress(request, response)
}
} else {
file_handle <- file(asset_path, "rb")
file_size <- file.size(asset_path)

response$body <- readBin(file_handle,
raw(),
file.size(asset_path))
file_size)
close(file_handle)
}

response$status <- 200L
}
TRUE
Expand All@@ -501,8 +565,13 @@ Dash <- R6::R6Class(
file.size(asset_path))
close(file_handle)

response$set_header('Cache-Control',
sprintf('public, max-age=%s',
'31536000')
)
response$type <- 'image/x-icon'
response$status <- 200L

TRUE
})

Expand All@@ -512,6 +581,9 @@ Dash <- R6::R6Class(
response$body <- private$.index
response$status <- 200L
response$type <- 'html'

if (private$compress)
response <- tryCompress(request, response)
TRUE
})

Expand DownExpand Up@@ -540,6 +612,7 @@ Dash <- R6::R6Class(
response$body <- to_JSON(resp)
response$status <- 200L
response$type <- 'json'

# reset the field for the next reloading operation
private$modified_since_reload <- list()
TRUE
Expand DownExpand Up@@ -830,13 +903,15 @@ Dash <- R6::R6Class(
# private fields defined on initiation
name = NULL,
serve_locally = NULL,
eager_loading = NULL,
meta_tags = NULL,
assets_folder = NULL,
assets_url_path = NULL,
assets_ignore = NULL,
routes_pathname_prefix = NULL,
requests_pathname_prefix = NULL,
suppress_callback_exceptions = NULL,
compress = NULL,
asset_map = NULL,
css = NULL,
scripts = NULL,
Expand DownExpand Up@@ -1188,7 +1263,24 @@ Dash <- R6::R6Class(
css_deps <- render_dependencies(css_deps,
local = private$serve_locally,
prefix=self$config$requests_pathname_prefix)


# ensure that no dependency has both async and dynamic set
if (any(
vapply(depsAll, function(dep)
length(intersect(c("dynamic", "async"), names(dep))) > 1,
logical(1)
)
)
)
stop("Can't have both 'dynamic' and 'async' in a Dash dependency; please correct and reload.", call. = FALSE)

# remove dependencies which are dynamic from the script list
# to avoid placing them into the index
depsAll <- depsAll[!vapply(depsAll,
isDynamic,
logical(1),
eager_loading = private$eager_loading)]

# scripts go after dash-renderer dependencies (i.e., React),
# but before dash-renderer itself
scripts_deps <- compact(lapply(depsAll, function(dep) {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Implement support for asynchronous loading in Dash for R by rpkyle · Pull Request #157 · plotly/dashR · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
0d7c2b1
add eager_loading parameter
Nov 1, 2019
ef59eeb
:pencil2: add fields for eager_loading
Nov 1, 2019
f36c77a
:name_badge: add buildFingerprint
Nov 7, 2019
a869c4f
:name_badge: add checkFingerprint
Nov 7, 2019
46157f7
temporarily disable percy
rpkyle Dec 3, 2019
bd3ae5e
:see_no_evil: use path instead of mypath
Dec 4, 2019
1bf6158
:truck: dep path resolution into fn for :camel:
Dec 5, 2019
fa4c4bd
:pencil2: Dash-friendly ext/filename helpers
Dec 5, 2019
f351b8b
use getDependencyPath, + :paw_prints:/Etag support
Dec 5, 2019
a4e6754
updates to support async
Dec 13, 2019
436632e
Merge branch 'dev' into 134-async
rpkyle Dec 15, 2019
bae3256
:see_no_evil: fix misplaced paren
Dec 15, 2019
c04b8b4
fix variable name and parens
Dec 15, 2019
fd65186
:see_no_evil: fix misplaced paren
Dec 15, 2019
9cc86cb
Merge branch '134-async' of github.com:plotly/dashR into 134-async
Dec 15, 2019
ac81b37
:hammer: refactor tag generation
Dec 17, 2019
0c77d32
:sparkles: properly support gz compression
Dec 17, 2019
cfced69
sanity checks for response size
Dec 17, 2019
72cb717
update remote refs
Dec 18, 2019
df5888a
:bug: post-async fixes for CSS handling
Dec 18, 2019
0370ec8
add tryCompress fn
Dec 19, 2019
2987a7a
:bug: dashLogger fails when self does not exist
Dec 19, 2019
63885ec
Update CHANGELOG.md
rpkyle Dec 19, 2019
938b304
Update CHANGELOG.md
rpkyle Dec 19, 2019
a0c6bb6
update changelog
Dec 22, 2019
bd6a2d7
fix commit hashes
Dec 22, 2019
eb9521f
update Dash help page
Dec 22, 2019
4bea651
update URL to docs
Dec 22, 2019
937715b
:bug: fix example
Dec 22, 2019
a385ede
fix example; load pkgs
Dec 22, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
# Change Log for Dash for R
All notable changes to this project will be documented in this file.

## Unreleased
## [0.2.0] - 2019-12-23
### Added
- Support for asynchronous/dynamic loading of dependencies, resource caching, and asset fingerprinting [#157](https://github.com/plotly/dashR/pull/157)
- Compression of text resources using `brotli`, `gzip`, or `deflate` [#157](https://github.com/plotly/dashR/pull/157)
- Support for adding `<meta>` tags to index [#142](https://github.com/plotly/dashR/pull/142)
- Hot reloading now supported in debug mode [#127](https://github.com/plotly/dashR/pull/127)
- Support for displaying Dash for R applications within RStudio's viewer pane when `use_viewer = TRUE`
Expand Down
16 changes: 8 additions & 8 deletions DESCRIPTION
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
Package: dash
Title: An Interface to the Dash Ecosystem for Authoring Reactive Web Applications
Version: 0.1.0
Version: 0.2.0
Authors@R: c(person("Chris", "Parmer", role = c("aut"), email = "chris@plot.ly"), person("Ryan Patrick", "Kyle", role = c("aut", "cre"), comment = c(ORCID = "0000-0001-5829-9867"), email = "ryan@plot.ly"), person("Carson", "Sievert", role = c("aut"), comment = c(ORCID = "0000-0002-4958-2844")), person(family = "Plotly Technologies", role = "cph"))
Description: A framework for building analytical web applications, Dash offers a pleasant and productive development experience. No JavaScript required.
Depends:
R (>= 3.0.2)
Imports:
dashHtmlComponents (== 1.0.0),
dashCoreComponents (== 1.0.0),
dashTable (== 4.0.2),
dashHtmlComponents (== 1.0.2),
dashCoreComponents (== 1.6.0),
dashTable (== 4.5.1),
R6,
fiery (> 1.0.0),
routr (> 0.2.0),
plotly,
reqres,
reqres (>= 0.2.3),
jsonlite,
htmltools,
assertthat,
Expand All@@ -31,9 +31,9 @@ Collate:
'imports.R'
'print.R'
'internal.R'
Remotes: plotly/dash-html-components@17da1f4,
plotly/dash-core-components@cc1e654,
plotly/dash-table@042ad65
Remotes: plotly/dash-html-components@55c3884,
plotly/dash-core-components@c107e0f,
plotly/dash-table@3058bd5
License: MIT + file LICENSE
Encoding: UTF-8
LazyData: true
Expand Down
108 changes: 100 additions & 8 deletions R/dash.R
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,7 @@
#' server = fiery::Fire$new(),
#' assets_folder = 'assets',
#' assets_url_path = '/assets',
#' eager_loading = FALSE,
#' assets_ignore = '',
#' serve_locally = TRUE,
#' meta_tags = NULL,
Expand All@@ -30,6 +31,7 @@
#' .css files will be loaded immediately unless excluded by `assets_ignore`,
#' and other files such as images will be served if requested. Default is `assets`. \cr
#' `assets_url_path` \tab \tab Character. Specify the URL path for asset serving. Default is `assets`. \cr
#' `eager_loading` \tab \tab Logical. Controls whether asynchronous resources are prefetched (if `TRUE`) or loaded on-demand (if `FALSE`). \cr
#' `assets_ignore` \tab \tab Character. A regular expression, to match assets to omit from
#' immediate loading. Ignored files will still be served if specifically requested. You
#' cannot use this to prevent access to sensitive files. \cr
Expand DownExpand Up@@ -126,7 +128,10 @@
#'
#' @examples
#' \dontrun{
#' library(dashCoreComponents)
#' library(dashHtmlComponents)
#' library(dash)

#' app <- Dash$new()
#' app$layout(
#' dccInput(id = "inputID", value = "initial value", type = "text"),
Expand DownExpand Up@@ -160,13 +165,15 @@ Dash <- R6::R6Class(
server = fiery::Fire$new(),
assets_folder = 'assets',
assets_url_path = '/assets',
eager_loading = FALSE,
assets_ignore = '',
serve_locally = TRUE,
meta_tags = NULL,
routes_pathname_prefix = NULL,
requests_pathname_prefix = NULL,
external_scripts = NULL,
external_stylesheets = NULL,
compress = TRUE,
suppress_callback_exceptions = FALSE) {

# argument type checking
Expand All@@ -178,12 +185,14 @@ Dash <- R6::R6Class(
# save relevant args as private fields
private$name <- name
private$serve_locally <- serve_locally
private$eager_loading <- eager_loading
# remove leading and trailing slash(es) if present
private$assets_folder <- gsub("^/+|/+$", "", assets_folder)
# remove trailing slash in assets_url_path, if present
private$assets_url_path <- sub("/$", "", assets_url_path)
private$assets_ignore <- assets_ignore
private$suppress_callback_exceptions <- suppress_callback_exceptions
private$compress <- compress
private$app_root_path <- getAppPath()
private$app_launchtime <- as.integer(Sys.time())
private$meta_tags <- meta_tags
Expand DownExpand Up@@ -240,6 +249,9 @@ Dash <- R6::R6Class(
response$body <- to_JSON(lay, pretty = TRUE)
response$status <- 200L
response$type <- 'json'



TRUE
})

Expand All@@ -250,6 +262,7 @@ Dash <- R6::R6Class(
response$body <- to_JSON(list())
response$status <- 200L
response$type <- 'json'

return(FALSE)
}

Expand All@@ -265,6 +278,8 @@ Dash <- R6::R6Class(
response$body <- to_JSON(setNames(payload, NULL))
response$status <- 200L
response$type <- 'json'
if (private$compress)
response <- tryCompress(request, response)
TRUE
})

Expand DownExpand Up@@ -393,17 +408,33 @@ Dash <- R6::R6Class(
response$status <- 500L
private$stack_message <- NULL
}

if (private$compress)
response <- tryCompress(request, response)
TRUE
})

# This endpoint supports dynamic dependency loading
# during `_dash-update-component` -- for reference:
# https://github.com/plotly/dash/blob/1249ffbd051bfb5fdbe439612cbec7fa8fff5ab5/dash/dash.py#L488
# https://docs.python.org/3/library/pkgutil.html#pkgutil.get_data
#
# analogous to
# https://github.com/plotly/dash/blob/2d735aa250fc67b14dc8f6a337d15a16b7cbd6f8/dash/dash.py#L543-L551
dash_suite <- paste0(self$config$routes_pathname_prefix, "_dash-component-suites/:package_name/:filename")

route$add_handler("get", dash_suite, function(request, response, keys, ...) {
filename <- basename(file.path(keys$filename))

# checkFingerprint returns a list of length 2, the first element is
# the un-fingerprinted path, if a fingerprint is present (otherwise
# the original path is returned), while the second element indicates
# whether the original filename included a valid fingerprint (by
# Dash convention)
fingerprinting_metadata <- checkFingerprint(filename)

filename <- fingerprinting_metadata[[1]]
has_fingerprint <- fingerprinting_metadata[[2]] == TRUE

dep_list <- c(private$dependencies_internal,
private$dependencies,
private$dependencies_user)
Expand All@@ -413,7 +444,6 @@ Dash <- R6::R6Class(
clean_dependencies(dep_list)
)


# return warning if a dependency goes unmatched, since the page
# will probably fail to render properly anyway without it
if (length(dep_pkg$rpkg_path) == 0) {
Expand All@@ -424,16 +454,44 @@ Dash <- R6::R6Class(
response$body <- NULL
response$status <- 404L
} else {
# need to check for debug mode, don't cache, don't etag
# if debug mode is not active
dep_path <- system.file(dep_pkg$rpkg_path,
package = dep_pkg$rpkg_name)

response$body <- readLines(dep_path,
warn = FALSE,
encoding = "UTF-8")
response$status <- 200L

if (!private$debug && has_fingerprint) {
response$status <- 200L
response$set_header('Cache-Control',
sprintf('public, max-age=%s',
31536000) # 1 year
)
} else if (!private$debug && !has_fingerprint) {
modified <- as.character(as.integer(file.mtime(dep_path)))

response$set_header('ETag', modified)

request_etag <- request$headers[["If-None-Match"]]

if (!is.null(request_etag) && modified == request_etag) {
response$body <- NULL
response$status <- 304L
} else {
response$status <- 200L
}
} else {
response$status <- 200L
}

response$type <- get_mimetype(filename)
}

if (private$compress && length(response$body) > 0)
response <- tryCompress(request, response)

TRUE
})

Expand DownExpand Up@@ -476,14 +534,20 @@ Dash <- R6::R6Class(
response$body <- readLines(asset_path,
warn = FALSE,
encoding = "UTF-8")

if (private$compress && length(response$body) > 0) {
response <- tryCompress(request, response)
}
} else {
file_handle <- file(asset_path, "rb")
file_size <- file.size(asset_path)

response$body <- readBin(file_handle,
raw(),
file.size(asset_path))
file_size)
close(file_handle)
}

response$status <- 200L
}
TRUE
Expand All@@ -501,8 +565,13 @@ Dash <- R6::R6Class(
file.size(asset_path))
close(file_handle)

response$set_header('Cache-Control',
sprintf('public, max-age=%s',
'31536000')
)
response$type <- 'image/x-icon'
response$status <- 200L

TRUE
})

Expand All@@ -512,6 +581,9 @@ Dash <- R6::R6Class(
response$body <- private$.index
response$status <- 200L
response$type <- 'html'

if (private$compress)
response <- tryCompress(request, response)
TRUE
})

Expand DownExpand Up@@ -540,6 +612,7 @@ Dash <- R6::R6Class(
response$body <- to_JSON(resp)
response$status <- 200L
response$type <- 'json'

# reset the field for the next reloading operation
private$modified_since_reload <- list()
TRUE
Expand DownExpand Up@@ -830,13 +903,15 @@ Dash <- R6::R6Class(
# private fields defined on initiation
name = NULL,
serve_locally = NULL,
eager_loading = NULL,
meta_tags = NULL,
assets_folder = NULL,
assets_url_path = NULL,
assets_ignore = NULL,
routes_pathname_prefix = NULL,
requests_pathname_prefix = NULL,
suppress_callback_exceptions = NULL,
compress = NULL,
asset_map = NULL,
css = NULL,
scripts = NULL,
Expand DownExpand Up@@ -1188,7 +1263,24 @@ Dash <- R6::R6Class(
css_deps <- render_dependencies(css_deps,
local = private$serve_locally,
prefix=self$config$requests_pathname_prefix)


# ensure that no dependency has both async and dynamic set
if (any(
vapply(depsAll, function(dep)
length(intersect(c("dynamic", "async"), names(dep))) > 1,
logical(1)
)
)
)
stop("Can't have both 'dynamic' and 'async' in a Dash dependency; please correct and reload.", call. = FALSE)

# remove dependencies which are dynamic from the script list
# to avoid placing them into the index
depsAll <- depsAll[!vapply(depsAll,
isDynamic,
logical(1),
eager_loading = private$eager_loading)]

# scripts go after dash-renderer dependencies (i.e., React),
# but before dash-renderer itself
scripts_deps <- compact(lapply(depsAll, function(dep) {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Implement support for asynchronous loading in Dash for R by rpkyle · Pull Request #157 · plotly/dashR · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
0d7c2b1
add eager_loading parameter
Nov 1, 2019
ef59eeb
:pencil2: add fields for eager_loading
Nov 1, 2019
f36c77a
:name_badge: add buildFingerprint
Nov 7, 2019
a869c4f
:name_badge: add checkFingerprint
Nov 7, 2019
46157f7
temporarily disable percy
rpkyle Dec 3, 2019
bd3ae5e
:see_no_evil: use path instead of mypath
Dec 4, 2019
1bf6158
:truck: dep path resolution into fn for :camel:
Dec 5, 2019
fa4c4bd
:pencil2: Dash-friendly ext/filename helpers
Dec 5, 2019
f351b8b
use getDependencyPath, + :paw_prints:/Etag support
Dec 5, 2019
a4e6754
updates to support async
Dec 13, 2019
436632e
Merge branch 'dev' into 134-async
rpkyle Dec 15, 2019
bae3256
:see_no_evil: fix misplaced paren
Dec 15, 2019
c04b8b4
fix variable name and parens
Dec 15, 2019
fd65186
:see_no_evil: fix misplaced paren
Dec 15, 2019
9cc86cb
Merge branch '134-async' of github.com:plotly/dashR into 134-async
Dec 15, 2019
ac81b37
:hammer: refactor tag generation
Dec 17, 2019
0c77d32
:sparkles: properly support gz compression
Dec 17, 2019
cfced69
sanity checks for response size
Dec 17, 2019
72cb717
update remote refs
Dec 18, 2019
df5888a
:bug: post-async fixes for CSS handling
Dec 18, 2019
0370ec8
add tryCompress fn
Dec 19, 2019
2987a7a
:bug: dashLogger fails when self does not exist
Dec 19, 2019
63885ec
Update CHANGELOG.md
rpkyle Dec 19, 2019
938b304
Update CHANGELOG.md
rpkyle Dec 19, 2019
a0c6bb6
update changelog
Dec 22, 2019
bd6a2d7
fix commit hashes
Dec 22, 2019
eb9521f
update Dash help page
Dec 22, 2019
4bea651
update URL to docs
Dec 22, 2019
937715b
:bug: fix example
Dec 22, 2019
a385ede
fix example; load pkgs
Dec 22, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
# Change Log for Dash for R
All notable changes to this project will be documented in this file.

## Unreleased
## [0.2.0] - 2019-12-23
### Added
- Support for asynchronous/dynamic loading of dependencies, resource caching, and asset fingerprinting [#157](https://github.com/plotly/dashR/pull/157)
- Compression of text resources using `brotli`, `gzip`, or `deflate` [#157](https://github.com/plotly/dashR/pull/157)
- Support for adding `<meta>` tags to index [#142](https://github.com/plotly/dashR/pull/142)
- Hot reloading now supported in debug mode [#127](https://github.com/plotly/dashR/pull/127)
- Support for displaying Dash for R applications within RStudio's viewer pane when `use_viewer = TRUE`
Expand Down
16 changes: 8 additions & 8 deletions DESCRIPTION
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
Package: dash
Title: An Interface to the Dash Ecosystem for Authoring Reactive Web Applications
Version: 0.1.0
Version: 0.2.0
Authors@R: c(person("Chris", "Parmer", role = c("aut"), email = "chris@plot.ly"), person("Ryan Patrick", "Kyle", role = c("aut", "cre"), comment = c(ORCID = "0000-0001-5829-9867"), email = "ryan@plot.ly"), person("Carson", "Sievert", role = c("aut"), comment = c(ORCID = "0000-0002-4958-2844")), person(family = "Plotly Technologies", role = "cph"))
Description: A framework for building analytical web applications, Dash offers a pleasant and productive development experience. No JavaScript required.
Depends:
R (>= 3.0.2)
Imports:
dashHtmlComponents (== 1.0.0),
dashCoreComponents (== 1.0.0),
dashTable (== 4.0.2),
dashHtmlComponents (== 1.0.2),
dashCoreComponents (== 1.6.0),
dashTable (== 4.5.1),
R6,
fiery (> 1.0.0),
routr (> 0.2.0),
plotly,
reqres,
reqres (>= 0.2.3),
jsonlite,
htmltools,
assertthat,
Expand All@@ -31,9 +31,9 @@ Collate:
'imports.R'
'print.R'
'internal.R'
Remotes: plotly/dash-html-components@17da1f4,
plotly/dash-core-components@cc1e654,
plotly/dash-table@042ad65
Remotes: plotly/dash-html-components@55c3884,
plotly/dash-core-components@c107e0f,
plotly/dash-table@3058bd5
License: MIT + file LICENSE
Encoding: UTF-8
LazyData: true
Expand Down
108 changes: 100 additions & 8 deletions R/dash.R
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,7 @@
#' server = fiery::Fire$new(),
#' assets_folder = 'assets',
#' assets_url_path = '/assets',
#' eager_loading = FALSE,
#' assets_ignore = '',
#' serve_locally = TRUE,
#' meta_tags = NULL,
Expand All@@ -30,6 +31,7 @@
#' .css files will be loaded immediately unless excluded by `assets_ignore`,
#' and other files such as images will be served if requested. Default is `assets`. \cr
#' `assets_url_path` \tab \tab Character. Specify the URL path for asset serving. Default is `assets`. \cr
#' `eager_loading` \tab \tab Logical. Controls whether asynchronous resources are prefetched (if `TRUE`) or loaded on-demand (if `FALSE`). \cr
#' `assets_ignore` \tab \tab Character. A regular expression, to match assets to omit from
#' immediate loading. Ignored files will still be served if specifically requested. You
#' cannot use this to prevent access to sensitive files. \cr
Expand DownExpand Up@@ -126,7 +128,10 @@
#'
#' @examples
#' \dontrun{
#' library(dashCoreComponents)
#' library(dashHtmlComponents)
#' library(dash)

#' app <- Dash$new()
#' app$layout(
#' dccInput(id = "inputID", value = "initial value", type = "text"),
Expand DownExpand Up@@ -160,13 +165,15 @@ Dash <- R6::R6Class(
server = fiery::Fire$new(),
assets_folder = 'assets',
assets_url_path = '/assets',
eager_loading = FALSE,
assets_ignore = '',
serve_locally = TRUE,
meta_tags = NULL,
routes_pathname_prefix = NULL,
requests_pathname_prefix = NULL,
external_scripts = NULL,
external_stylesheets = NULL,
compress = TRUE,
suppress_callback_exceptions = FALSE) {

# argument type checking
Expand All@@ -178,12 +185,14 @@ Dash <- R6::R6Class(
# save relevant args as private fields
private$name <- name
private$serve_locally <- serve_locally
private$eager_loading <- eager_loading
# remove leading and trailing slash(es) if present
private$assets_folder <- gsub("^/+|/+$", "", assets_folder)
# remove trailing slash in assets_url_path, if present
private$assets_url_path <- sub("/$", "", assets_url_path)
private$assets_ignore <- assets_ignore
private$suppress_callback_exceptions <- suppress_callback_exceptions
private$compress <- compress
private$app_root_path <- getAppPath()
private$app_launchtime <- as.integer(Sys.time())
private$meta_tags <- meta_tags
Expand DownExpand Up@@ -240,6 +249,9 @@ Dash <- R6::R6Class(
response$body <- to_JSON(lay, pretty = TRUE)
response$status <- 200L
response$type <- 'json'



TRUE
})

Expand All@@ -250,6 +262,7 @@ Dash <- R6::R6Class(
response$body <- to_JSON(list())
response$status <- 200L
response$type <- 'json'

return(FALSE)
}

Expand All@@ -265,6 +278,8 @@ Dash <- R6::R6Class(
response$body <- to_JSON(setNames(payload, NULL))
response$status <- 200L
response$type <- 'json'
if (private$compress)
response <- tryCompress(request, response)
TRUE
})

Expand DownExpand Up@@ -393,17 +408,33 @@ Dash <- R6::R6Class(
response$status <- 500L
private$stack_message <- NULL
}

if (private$compress)
response <- tryCompress(request, response)
TRUE
})

# This endpoint supports dynamic dependency loading
# during `_dash-update-component` -- for reference:
# https://github.com/plotly/dash/blob/1249ffbd051bfb5fdbe439612cbec7fa8fff5ab5/dash/dash.py#L488
# https://docs.python.org/3/library/pkgutil.html#pkgutil.get_data
#
# analogous to
# https://github.com/plotly/dash/blob/2d735aa250fc67b14dc8f6a337d15a16b7cbd6f8/dash/dash.py#L543-L551
dash_suite <- paste0(self$config$routes_pathname_prefix, "_dash-component-suites/:package_name/:filename")

route$add_handler("get", dash_suite, function(request, response, keys, ...) {
filename <- basename(file.path(keys$filename))

# checkFingerprint returns a list of length 2, the first element is
# the un-fingerprinted path, if a fingerprint is present (otherwise
# the original path is returned), while the second element indicates
# whether the original filename included a valid fingerprint (by
# Dash convention)
fingerprinting_metadata <- checkFingerprint(filename)

filename <- fingerprinting_metadata[[1]]
has_fingerprint <- fingerprinting_metadata[[2]] == TRUE

dep_list <- c(private$dependencies_internal,
private$dependencies,
private$dependencies_user)
Expand All@@ -413,7 +444,6 @@ Dash <- R6::R6Class(
clean_dependencies(dep_list)
)


# return warning if a dependency goes unmatched, since the page
# will probably fail to render properly anyway without it
if (length(dep_pkg$rpkg_path) == 0) {
Expand All@@ -424,16 +454,44 @@ Dash <- R6::R6Class(
response$body <- NULL
response$status <- 404L
} else {
# need to check for debug mode, don't cache, don't etag
# if debug mode is not active
dep_path <- system.file(dep_pkg$rpkg_path,
package = dep_pkg$rpkg_name)

response$body <- readLines(dep_path,
warn = FALSE,
encoding = "UTF-8")
response$status <- 200L

if (!private$debug && has_fingerprint) {
response$status <- 200L
response$set_header('Cache-Control',
sprintf('public, max-age=%s',
31536000) # 1 year
)
} else if (!private$debug && !has_fingerprint) {
modified <- as.character(as.integer(file.mtime(dep_path)))

response$set_header('ETag', modified)

request_etag <- request$headers[["If-None-Match"]]

if (!is.null(request_etag) && modified == request_etag) {
response$body <- NULL
response$status <- 304L
} else {
response$status <- 200L
}
} else {
response$status <- 200L
}

response$type <- get_mimetype(filename)
}

if (private$compress && length(response$body) > 0)
response <- tryCompress(request, response)

TRUE
})

Expand DownExpand Up@@ -476,14 +534,20 @@ Dash <- R6::R6Class(
response$body <- readLines(asset_path,
warn = FALSE,
encoding = "UTF-8")

if (private$compress && length(response$body) > 0) {
response <- tryCompress(request, response)
}
} else {
file_handle <- file(asset_path, "rb")
file_size <- file.size(asset_path)

response$body <- readBin(file_handle,
raw(),
file.size(asset_path))
file_size)
close(file_handle)
}

response$status <- 200L
}
TRUE
Expand All@@ -501,8 +565,13 @@ Dash <- R6::R6Class(
file.size(asset_path))
close(file_handle)

response$set_header('Cache-Control',
sprintf('public, max-age=%s',
'31536000')
)
response$type <- 'image/x-icon'
response$status <- 200L

TRUE
})

Expand All@@ -512,6 +581,9 @@ Dash <- R6::R6Class(
response$body <- private$.index
response$status <- 200L
response$type <- 'html'

if (private$compress)
response <- tryCompress(request, response)
TRUE
})

Expand DownExpand Up@@ -540,6 +612,7 @@ Dash <- R6::R6Class(
response$body <- to_JSON(resp)
response$status <- 200L
response$type <- 'json'

# reset the field for the next reloading operation
private$modified_since_reload <- list()
TRUE
Expand DownExpand Up@@ -830,13 +903,15 @@ Dash <- R6::R6Class(
# private fields defined on initiation
name = NULL,
serve_locally = NULL,
eager_loading = NULL,
meta_tags = NULL,
assets_folder = NULL,
assets_url_path = NULL,
assets_ignore = NULL,
routes_pathname_prefix = NULL,
requests_pathname_prefix = NULL,
suppress_callback_exceptions = NULL,
compress = NULL,
asset_map = NULL,
css = NULL,
scripts = NULL,
Expand DownExpand Up@@ -1188,7 +1263,24 @@ Dash <- R6::R6Class(
css_deps <- render_dependencies(css_deps,
local = private$serve_locally,
prefix=self$config$requests_pathname_prefix)


# ensure that no dependency has both async and dynamic set
if (any(
vapply(depsAll, function(dep)
length(intersect(c("dynamic", "async"), names(dep))) > 1,
logical(1)
)
)
)
stop("Can't have both 'dynamic' and 'async' in a Dash dependency; please correct and reload.", call. = FALSE)

# remove dependencies which are dynamic from the script list
# to avoid placing them into the index
depsAll <- depsAll[!vapply(depsAll,
isDynamic,
logical(1),
eager_loading = private$eager_loading)]

# scripts go after dash-renderer dependencies (i.e., React),
# but before dash-renderer itself
scripts_deps <- compact(lapply(depsAll, function(dep) {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); Implement support for asynchronous loading in Dash for R by rpkyle · Pull Request #157 · plotly/dashR · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
0d7c2b1
add eager_loading parameter
Nov 1, 2019
ef59eeb
:pencil2: add fields for eager_loading
Nov 1, 2019
f36c77a
:name_badge: add buildFingerprint
Nov 7, 2019
a869c4f
:name_badge: add checkFingerprint
Nov 7, 2019
46157f7
temporarily disable percy
rpkyle Dec 3, 2019
bd3ae5e
:see_no_evil: use path instead of mypath
Dec 4, 2019
1bf6158
:truck: dep path resolution into fn for :camel:
Dec 5, 2019
fa4c4bd
:pencil2: Dash-friendly ext/filename helpers
Dec 5, 2019
f351b8b
use getDependencyPath, + :paw_prints:/Etag support
Dec 5, 2019
a4e6754
updates to support async
Dec 13, 2019
436632e
Merge branch 'dev' into 134-async
rpkyle Dec 15, 2019
bae3256
:see_no_evil: fix misplaced paren
Dec 15, 2019
c04b8b4
fix variable name and parens
Dec 15, 2019
fd65186
:see_no_evil: fix misplaced paren
Dec 15, 2019
9cc86cb
Merge branch '134-async' of github.com:plotly/dashR into 134-async
Dec 15, 2019
ac81b37
:hammer: refactor tag generation
Dec 17, 2019
0c77d32
:sparkles: properly support gz compression
Dec 17, 2019
cfced69
sanity checks for response size
Dec 17, 2019
72cb717
update remote refs
Dec 18, 2019
df5888a
:bug: post-async fixes for CSS handling
Dec 18, 2019
0370ec8
add tryCompress fn
Dec 19, 2019
2987a7a
:bug: dashLogger fails when self does not exist
Dec 19, 2019
63885ec
Update CHANGELOG.md
rpkyle Dec 19, 2019
938b304
Update CHANGELOG.md
rpkyle Dec 19, 2019
a0c6bb6
update changelog
Dec 22, 2019
bd6a2d7
fix commit hashes
Dec 22, 2019
eb9521f
update Dash help page
Dec 22, 2019
4bea651
update URL to docs
Dec 22, 2019
937715b
:bug: fix example
Dec 22, 2019
a385ede
fix example; load pkgs
Dec 22, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
# Change Log for Dash for R
All notable changes to this project will be documented in this file.

## Unreleased
## [0.2.0] - 2019-12-23
### Added
- Support for asynchronous/dynamic loading of dependencies, resource caching, and asset fingerprinting [#157](https://github.com/plotly/dashR/pull/157)
- Compression of text resources using `brotli`, `gzip`, or `deflate` [#157](https://github.com/plotly/dashR/pull/157)
- Support for adding `<meta>` tags to index [#142](https://github.com/plotly/dashR/pull/142)
- Hot reloading now supported in debug mode [#127](https://github.com/plotly/dashR/pull/127)
- Support for displaying Dash for R applications within RStudio's viewer pane when `use_viewer = TRUE`
Expand Down
16 changes: 8 additions & 8 deletions DESCRIPTION
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
Package: dash
Title: An Interface to the Dash Ecosystem for Authoring Reactive Web Applications
Version: 0.1.0
Version: 0.2.0
Authors@R: c(person("Chris", "Parmer", role = c("aut"), email = "chris@plot.ly"), person("Ryan Patrick", "Kyle", role = c("aut", "cre"), comment = c(ORCID = "0000-0001-5829-9867"), email = "ryan@plot.ly"), person("Carson", "Sievert", role = c("aut"), comment = c(ORCID = "0000-0002-4958-2844")), person(family = "Plotly Technologies", role = "cph"))
Description: A framework for building analytical web applications, Dash offers a pleasant and productive development experience. No JavaScript required.
Depends:
R (>= 3.0.2)
Imports:
dashHtmlComponents (== 1.0.0),
dashCoreComponents (== 1.0.0),
dashTable (== 4.0.2),
dashHtmlComponents (== 1.0.2),
dashCoreComponents (== 1.6.0),
dashTable (== 4.5.1),
R6,
fiery (> 1.0.0),
routr (> 0.2.0),
plotly,
reqres,
reqres (>= 0.2.3),
jsonlite,
htmltools,
assertthat,
Expand All@@ -31,9 +31,9 @@ Collate:
'imports.R'
'print.R'
'internal.R'
Remotes: plotly/dash-html-components@17da1f4,
plotly/dash-core-components@cc1e654,
plotly/dash-table@042ad65
Remotes: plotly/dash-html-components@55c3884,
plotly/dash-core-components@c107e0f,
plotly/dash-table@3058bd5
License: MIT + file LICENSE
Encoding: UTF-8
LazyData: true
Expand Down
108 changes: 100 additions & 8 deletions R/dash.R
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,7 @@
#' server = fiery::Fire$new(),
#' assets_folder = 'assets',
#' assets_url_path = '/assets',
#' eager_loading = FALSE,
#' assets_ignore = '',
#' serve_locally = TRUE,
#' meta_tags = NULL,
Expand All@@ -30,6 +31,7 @@
#' .css files will be loaded immediately unless excluded by `assets_ignore`,
#' and other files such as images will be served if requested. Default is `assets`. \cr
#' `assets_url_path` \tab \tab Character. Specify the URL path for asset serving. Default is `assets`. \cr
#' `eager_loading` \tab \tab Logical. Controls whether asynchronous resources are prefetched (if `TRUE`) or loaded on-demand (if `FALSE`). \cr
#' `assets_ignore` \tab \tab Character. A regular expression, to match assets to omit from
#' immediate loading. Ignored files will still be served if specifically requested. You
#' cannot use this to prevent access to sensitive files. \cr
Expand DownExpand Up@@ -126,7 +128,10 @@
#'
#' @examples
#' \dontrun{
#' library(dashCoreComponents)
#' library(dashHtmlComponents)
#' library(dash)

#' app <- Dash$new()
#' app$layout(
#' dccInput(id = "inputID", value = "initial value", type = "text"),
Expand DownExpand Up@@ -160,13 +165,15 @@ Dash <- R6::R6Class(
server = fiery::Fire$new(),
assets_folder = 'assets',
assets_url_path = '/assets',
eager_loading = FALSE,
assets_ignore = '',
serve_locally = TRUE,
meta_tags = NULL,
routes_pathname_prefix = NULL,
requests_pathname_prefix = NULL,
external_scripts = NULL,
external_stylesheets = NULL,
compress = TRUE,
suppress_callback_exceptions = FALSE) {

# argument type checking
Expand All@@ -178,12 +185,14 @@ Dash <- R6::R6Class(
# save relevant args as private fields
private$name <- name
private$serve_locally <- serve_locally
private$eager_loading <- eager_loading
# remove leading and trailing slash(es) if present
private$assets_folder <- gsub("^/+|/+$", "", assets_folder)
# remove trailing slash in assets_url_path, if present
private$assets_url_path <- sub("/$", "", assets_url_path)
private$assets_ignore <- assets_ignore
private$suppress_callback_exceptions <- suppress_callback_exceptions
private$compress <- compress
private$app_root_path <- getAppPath()
private$app_launchtime <- as.integer(Sys.time())
private$meta_tags <- meta_tags
Expand DownExpand Up@@ -240,6 +249,9 @@ Dash <- R6::R6Class(
response$body <- to_JSON(lay, pretty = TRUE)
response$status <- 200L
response$type <- 'json'



TRUE
})

Expand All@@ -250,6 +262,7 @@ Dash <- R6::R6Class(
response$body <- to_JSON(list())
response$status <- 200L
response$type <- 'json'

return(FALSE)
}

Expand All@@ -265,6 +278,8 @@ Dash <- R6::R6Class(
response$body <- to_JSON(setNames(payload, NULL))
response$status <- 200L
response$type <- 'json'
if (private$compress)
response <- tryCompress(request, response)
TRUE
})

Expand DownExpand Up@@ -393,17 +408,33 @@ Dash <- R6::R6Class(
response$status <- 500L
private$stack_message <- NULL
}

if (private$compress)
response <- tryCompress(request, response)
TRUE
})

# This endpoint supports dynamic dependency loading
# during `_dash-update-component` -- for reference:
# https://github.com/plotly/dash/blob/1249ffbd051bfb5fdbe439612cbec7fa8fff5ab5/dash/dash.py#L488
# https://docs.python.org/3/library/pkgutil.html#pkgutil.get_data
#
# analogous to
# https://github.com/plotly/dash/blob/2d735aa250fc67b14dc8f6a337d15a16b7cbd6f8/dash/dash.py#L543-L551
dash_suite <- paste0(self$config$routes_pathname_prefix, "_dash-component-suites/:package_name/:filename")

route$add_handler("get", dash_suite, function(request, response, keys, ...) {
filename <- basename(file.path(keys$filename))

# checkFingerprint returns a list of length 2, the first element is
# the un-fingerprinted path, if a fingerprint is present (otherwise
# the original path is returned), while the second element indicates
# whether the original filename included a valid fingerprint (by
# Dash convention)
fingerprinting_metadata <- checkFingerprint(filename)

filename <- fingerprinting_metadata[[1]]
has_fingerprint <- fingerprinting_metadata[[2]] == TRUE

dep_list <- c(private$dependencies_internal,
private$dependencies,
private$dependencies_user)
Expand All@@ -413,7 +444,6 @@ Dash <- R6::R6Class(
clean_dependencies(dep_list)
)


# return warning if a dependency goes unmatched, since the page
# will probably fail to render properly anyway without it
if (length(dep_pkg$rpkg_path) == 0) {
Expand All@@ -424,16 +454,44 @@ Dash <- R6::R6Class(
response$body <- NULL
response$status <- 404L
} else {
# need to check for debug mode, don't cache, don't etag
# if debug mode is not active
dep_path <- system.file(dep_pkg$rpkg_path,
package = dep_pkg$rpkg_name)

response$body <- readLines(dep_path,
warn = FALSE,
encoding = "UTF-8")
response$status <- 200L

if (!private$debug && has_fingerprint) {
response$status <- 200L
response$set_header('Cache-Control',
sprintf('public, max-age=%s',
31536000) # 1 year
)
} else if (!private$debug && !has_fingerprint) {
modified <- as.character(as.integer(file.mtime(dep_path)))

response$set_header('ETag', modified)

request_etag <- request$headers[["If-None-Match"]]

if (!is.null(request_etag) && modified == request_etag) {
response$body <- NULL
response$status <- 304L
} else {
response$status <- 200L
}
} else {
response$status <- 200L
}

response$type <- get_mimetype(filename)
}

if (private$compress && length(response$body) > 0)
response <- tryCompress(request, response)

TRUE
})

Expand DownExpand Up@@ -476,14 +534,20 @@ Dash <- R6::R6Class(
response$body <- readLines(asset_path,
warn = FALSE,
encoding = "UTF-8")

if (private$compress && length(response$body) > 0) {
response <- tryCompress(request, response)
}
} else {
file_handle <- file(asset_path, "rb")
file_size <- file.size(asset_path)

response$body <- readBin(file_handle,
raw(),
file.size(asset_path))
file_size)
close(file_handle)
}

response$status <- 200L
}
TRUE
Expand All@@ -501,8 +565,13 @@ Dash <- R6::R6Class(
file.size(asset_path))
close(file_handle)

response$set_header('Cache-Control',
sprintf('public, max-age=%s',
'31536000')
)
response$type <- 'image/x-icon'
response$status <- 200L

TRUE
})

Expand All@@ -512,6 +581,9 @@ Dash <- R6::R6Class(
response$body <- private$.index
response$status <- 200L
response$type <- 'html'

if (private$compress)
response <- tryCompress(request, response)
TRUE
})

Expand DownExpand Up@@ -540,6 +612,7 @@ Dash <- R6::R6Class(
response$body <- to_JSON(resp)
response$status <- 200L
response$type <- 'json'

# reset the field for the next reloading operation
private$modified_since_reload <- list()
TRUE
Expand DownExpand Up@@ -830,13 +903,15 @@ Dash <- R6::R6Class(
# private fields defined on initiation
name = NULL,
serve_locally = NULL,
eager_loading = NULL,
meta_tags = NULL,
assets_folder = NULL,
assets_url_path = NULL,
assets_ignore = NULL,
routes_pathname_prefix = NULL,
requests_pathname_prefix = NULL,
suppress_callback_exceptions = NULL,
compress = NULL,
asset_map = NULL,
css = NULL,
scripts = NULL,
Expand DownExpand Up@@ -1188,7 +1263,24 @@ Dash <- R6::R6Class(
css_deps <- render_dependencies(css_deps,
local = private$serve_locally,
prefix=self$config$requests_pathname_prefix)


# ensure that no dependency has both async and dynamic set
if (any(
vapply(depsAll, function(dep)
length(intersect(c("dynamic", "async"), names(dep))) > 1,
logical(1)
)
)
)
stop("Can't have both 'dynamic' and 'async' in a Dash dependency; please correct and reload.", call. = FALSE)

# remove dependencies which are dynamic from the script list
# to avoid placing them into the index
depsAll <- depsAll[!vapply(depsAll,
isDynamic,
logical(1),
eager_loading = private$eager_loading)]

# scripts go after dash-renderer dependencies (i.e., React),
# but before dash-renderer itself
scripts_deps <- compact(lapply(depsAll, function(dep) {
Expand Down
Loading