9
9
# ' server = fiery::Fire$new(),
10
10
# ' assets_folder = 'assets',
11
11
# ' assets_url_path = '/assets',
12
+ # ' eager_loading = FALSE,
12
13
# ' assets_ignore = '',
13
14
# ' serve_locally = TRUE,
14
15
# ' meta_tags = NULL,
30
31
# ' .css files will be loaded immediately unless excluded by `assets_ignore`,
31
32
# ' and other files such as images will be served if requested. Default is `assets`. \cr
32
33
# ' `assets_url_path` \tab \tab Character. Specify the URL path for asset serving. Default is `assets`. \cr
34
+ # ' `eager_loading` \tab \tab Logical. Controls whether asynchronous resources are prefetched (if `TRUE`) or loaded on-demand (if `FALSE`). \cr
33
35
# ' `assets_ignore` \tab \tab Character. A regular expression, to match assets to omit from
34
36
# ' immediate loading. Ignored files will still be served if specifically requested. You
35
37
# ' cannot use this to prevent access to sensitive files. \cr
126
128
# '
127
129
# ' @examples
128
130
# ' \dontrun{
131
+ # ' library(dashCoreComponents)
132
+ # ' library(dashHtmlComponents)
129
133
# ' library(dash)
134
+
130
135
# ' app <- Dash$new()
131
136
# ' app$layout(
132
137
# ' dccInput(id = "inputID", value = "initial value", type = "text"),
@@ -160,13 +165,15 @@ Dash <- R6::R6Class(
160
165
server = fiery :: Fire $ new(),
161
166
assets_folder = ' assets' ,
162
167
assets_url_path = ' /assets' ,
168
+ eager_loading = FALSE ,
163
169
assets_ignore = ' ' ,
164
170
serve_locally = TRUE ,
165
171
meta_tags = NULL ,
166
172
routes_pathname_prefix = NULL ,
167
173
requests_pathname_prefix = NULL ,
168
174
external_scripts = NULL ,
169
175
external_stylesheets = NULL ,
176
+ compress = TRUE ,
170
177
suppress_callback_exceptions = FALSE ) {
171
178
172
179
# argument type checking
@@ -178,12 +185,14 @@ Dash <- R6::R6Class(
178
185
# save relevant args as private fields
179
186
private $ name <- name
180
187
private $ serve_locally <- serve_locally
188
+ private $ eager_loading <- eager_loading
181
189
# remove leading and trailing slash(es) if present
182
190
private $ assets_folder <- gsub(" ^/+|/+$" , " " , assets_folder )
183
191
# remove trailing slash in assets_url_path, if present
184
192
private $ assets_url_path <- sub(" /$" , " " , assets_url_path )
185
193
private $ assets_ignore <- assets_ignore
186
194
private $ suppress_callback_exceptions <- suppress_callback_exceptions
195
+ private $ compress <- compress
187
196
private $ app_root_path <- getAppPath()
188
197
private $ app_launchtime <- as.integer(Sys.time())
189
198
private $ meta_tags <- meta_tags
@@ -240,6 +249,9 @@ Dash <- R6::R6Class(
240
249
response $ body <- to_JSON(lay , pretty = TRUE )
241
250
response $ status <- 200L
242
251
response $ type <- ' json'
252
+
253
+
254
+
243
255
TRUE
244
256
})
245
257
@@ -250,6 +262,7 @@ Dash <- R6::R6Class(
250
262
response $ body <- to_JSON(list ())
251
263
response $ status <- 200L
252
264
response $ type <- ' json'
265
+
253
266
return (FALSE )
254
267
}
255
268
@@ -265,6 +278,8 @@ Dash <- R6::R6Class(
265
278
response $ body <- to_JSON(setNames(payload , NULL ))
266
279
response $ status <- 200L
267
280
response $ type <- ' json'
281
+ if (private $ compress )
282
+ response <- tryCompress(request , response )
268
283
TRUE
269
284
})
270
285
@@ -393,17 +408,33 @@ Dash <- R6::R6Class(
393
408
response $ status <- 500L
394
409
private $ stack_message <- NULL
395
410
}
411
+
412
+ if (private $ compress )
413
+ response <- tryCompress(request , response )
396
414
TRUE
397
415
})
398
416
399
417
# This endpoint supports dynamic dependency loading
400
418
# during `_dash-update-component` -- for reference:
401
- # https://github.com/plotly/dash/blob/1249ffbd051bfb5fdbe439612cbec7fa8fff5ab5/dash/dash.py#L488
402
419
# https://docs.python.org/3/library/pkgutil.html#pkgutil.get_data
420
+ #
421
+ # analogous to
422
+ # https://github.com/plotly/dash/blob/2d735aa250fc67b14dc8f6a337d15a16b7cbd6f8/dash/dash.py#L543-L551
403
423
dash_suite <- paste0(self $ config $ routes_pathname_prefix , " _dash-component-suites/:package_name/:filename" )
404
-
424
+
405
425
route $ add_handler(" get" , dash_suite , function (request , response , keys , ... ) {
406
426
filename <- basename(file.path(keys $ filename ))
427
+
428
+ # checkFingerprint returns a list of length 2, the first element is
429
+ # the un-fingerprinted path, if a fingerprint is present (otherwise
430
+ # the original path is returned), while the second element indicates
431
+ # whether the original filename included a valid fingerprint (by
432
+ # Dash convention)
433
+ fingerprinting_metadata <- checkFingerprint(filename )
434
+
435
+ filename <- fingerprinting_metadata [[1 ]]
436
+ has_fingerprint <- fingerprinting_metadata [[2 ]] == TRUE
437
+
407
438
dep_list <- c(private $ dependencies_internal ,
408
439
private $ dependencies ,
409
440
private $ dependencies_user )
@@ -413,7 +444,6 @@ Dash <- R6::R6Class(
413
444
clean_dependencies(dep_list )
414
445
)
415
446
416
-
417
447
# return warning if a dependency goes unmatched, since the page
418
448
# will probably fail to render properly anyway without it
419
449
if (length(dep_pkg $ rpkg_path ) == 0 ) {
@@ -424,16 +454,44 @@ Dash <- R6::R6Class(
424
454
response $ body <- NULL
425
455
response $ status <- 404L
426
456
} else {
457
+ # need to check for debug mode, don't cache, don't etag
458
+ # if debug mode is not active
427
459
dep_path <- system.file(dep_pkg $ rpkg_path ,
428
460
package = dep_pkg $ rpkg_name )
429
-
461
+
430
462
response $ body <- readLines(dep_path ,
431
463
warn = FALSE ,
432
464
encoding = " UTF-8" )
433
- response $ status <- 200L
465
+
466
+ if (! private $ debug && has_fingerprint ) {
467
+ response $ status <- 200L
468
+ response $ set_header(' Cache-Control' ,
469
+ sprintf(' public, max-age=%s' ,
470
+ 31536000 ) # 1 year
471
+ )
472
+ } else if (! private $ debug && ! has_fingerprint ) {
473
+ modified <- as.character(as.integer(file.mtime(dep_path )))
474
+
475
+ response $ set_header(' ETag' , modified )
476
+
477
+ request_etag <- request $ headers [[" If-None-Match" ]]
478
+
479
+ if (! is.null(request_etag ) && modified == request_etag ) {
480
+ response $ body <- NULL
481
+ response $ status <- 304L
482
+ } else {
483
+ response $ status <- 200L
484
+ }
485
+ } else {
486
+ response $ status <- 200L
487
+ }
488
+
434
489
response $ type <- get_mimetype(filename )
435
490
}
436
491
492
+ if (private $ compress && length(response $ body ) > 0 )
493
+ response <- tryCompress(request , response )
494
+
437
495
TRUE
438
496
})
439
497
@@ -476,14 +534,20 @@ Dash <- R6::R6Class(
476
534
response $ body <- readLines(asset_path ,
477
535
warn = FALSE ,
478
536
encoding = " UTF-8" )
537
+
538
+ if (private $ compress && length(response $ body ) > 0 ) {
539
+ response <- tryCompress(request , response )
540
+ }
479
541
} else {
480
542
file_handle <- file(asset_path , " rb" )
543
+ file_size <- file.size(asset_path )
544
+
481
545
response $ body <- readBin(file_handle ,
482
546
raw(),
483
- file.size( asset_path ) )
547
+ file_size )
484
548
close(file_handle )
485
549
}
486
-
550
+
487
551
response $ status <- 200L
488
552
}
489
553
TRUE
@@ -501,8 +565,13 @@ Dash <- R6::R6Class(
501
565
file.size(asset_path ))
502
566
close(file_handle )
503
567
568
+ response $ set_header(' Cache-Control' ,
569
+ sprintf(' public, max-age=%s' ,
570
+ ' 31536000' )
571
+ )
504
572
response $ type <- ' image/x-icon'
505
573
response $ status <- 200L
574
+
506
575
TRUE
507
576
})
508
577
@@ -512,6 +581,9 @@ Dash <- R6::R6Class(
512
581
response $ body <- private $ .index
513
582
response $ status <- 200L
514
583
response $ type <- ' html'
584
+
585
+ if (private $ compress )
586
+ response <- tryCompress(request , response )
515
587
TRUE
516
588
})
517
589
@@ -540,6 +612,7 @@ Dash <- R6::R6Class(
540
612
response $ body <- to_JSON(resp )
541
613
response $ status <- 200L
542
614
response $ type <- ' json'
615
+
543
616
# reset the field for the next reloading operation
544
617
private $ modified_since_reload <- list ()
545
618
TRUE
@@ -830,13 +903,15 @@ Dash <- R6::R6Class(
830
903
# private fields defined on initiation
831
904
name = NULL ,
832
905
serve_locally = NULL ,
906
+ eager_loading = NULL ,
833
907
meta_tags = NULL ,
834
908
assets_folder = NULL ,
835
909
assets_url_path = NULL ,
836
910
assets_ignore = NULL ,
837
911
routes_pathname_prefix = NULL ,
838
912
requests_pathname_prefix = NULL ,
839
913
suppress_callback_exceptions = NULL ,
914
+ compress = NULL ,
840
915
asset_map = NULL ,
841
916
css = NULL ,
842
917
scripts = NULL ,
@@ -1188,7 +1263,24 @@ Dash <- R6::R6Class(
1188
1263
css_deps <- render_dependencies(css_deps ,
1189
1264
local = private $ serve_locally ,
1190
1265
prefix = self $ config $ requests_pathname_prefix )
1191
-
1266
+
1267
+ # ensure that no dependency has both async and dynamic set
1268
+ if (any(
1269
+ vapply(depsAll , function (dep )
1270
+ length(intersect(c(" dynamic" , " async" ), names(dep ))) > 1 ,
1271
+ logical (1 )
1272
+ )
1273
+ )
1274
+ )
1275
+ stop(" Can't have both 'dynamic' and 'async' in a Dash dependency; please correct and reload." , call. = FALSE )
1276
+
1277
+ # remove dependencies which are dynamic from the script list
1278
+ # to avoid placing them into the index
1279
+ depsAll <- depsAll [! vapply(depsAll ,
1280
+ isDynamic ,
1281
+ logical (1 ),
1282
+ eager_loading = private $ eager_loading )]
1283
+
1192
1284
# scripts go after dash-renderer dependencies (i.e., React),
1193
1285
# but before dash-renderer itself
1194
1286
scripts_deps <- compact(lapply(depsAll , function (dep ) {
0 commit comments