Skip to content

Commit

Permalink
Add maximum file size for bundling wasm assets (#114)
Browse files Browse the repository at this point in the history
* Add maximum file size for bundling wasm assets

* Tweak max_filesize arg use to match package_cache

* Apply suggestions from code review

Co-authored-by: Barret Schloerke <barret@posit.co>

* Handle empty SHINYLIVE_DEFAULT_MAX_FILESIZE envvar

* Print variable values in fs parse warning

---------

Co-authored-by: Barret Schloerke <barret@posit.co>
  • Loading branch information
georgestagg and schloerke authored Aug 1, 2024
1 parent 73b3b85 commit 2d4be82
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 7 deletions.
7 changes: 6 additions & 1 deletion R/export.R
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
#' part of the output app's static assets. Defaults to `TRUE`.
#' @param package_cache Cache downloaded binary WebAssembly packages. Defaults
#' to `TRUE`.
#' @param max_filesize Maximum file size for bundling of WebAssembly package
#' assets. Parsed by [fs::fs_bytes()]. Defaults to `"100M"`. The default
#' value can be changed by setting the environment variable
#' `SHINYLIVE_DEFAULT_MAX_FILESIZE`. Set to `Inf`, `NA` or `-1` to disable.
#' @param assets_version The version of the Shinylive assets to use in the
#' exported app. Defaults to [assets_version()]. Note, not all custom assets
#' versions may work with this release of \pkg{shinylive}. Please visit the
Expand Down Expand Up @@ -59,6 +63,7 @@ export <- function(
quiet = getOption("shinylive.quiet", !is_interactive()),
wasm_packages = TRUE,
package_cache = TRUE,
max_filesize = NULL,
assets_version = NULL,
template_dir = NULL,
template_params = list(),
Expand Down Expand Up @@ -194,7 +199,7 @@ export <- function(
# Copy app package dependencies as Wasm binaries
# =========================================================================
if (wasm_packages) {
download_wasm_packages(appdir, destdir, package_cache)
download_wasm_packages(appdir, destdir, package_cache, max_filesize)
}

# =========================================================================
Expand Down
49 changes: 45 additions & 4 deletions R/packages.R
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
SHINYLIVE_DEFAULT_MAX_FILESIZE <- "100MB"

# Sys env maximum filesize for asset bundling
sys_env_max_filesize <- function() {
max_fs_env <- Sys.getenv("SHINYLIVE_DEFAULT_MAX_FILESIZE")
if (max_fs_env == "") NULL else max_fs_env
}

# Resolve package list hard dependencies
resolve_dependencies <- function(pkgs, local = TRUE) {
pkg_refs <- if (local) {
Expand Down Expand Up @@ -178,7 +186,22 @@ env_download_wasm_core_packages <- function() {
strsplit(pkgs, "\\s*[ ,\n]\\s*")[[1]]
}

download_wasm_packages <- function(appdir, destdir, package_cache) {
download_wasm_packages <- function(appdir, destdir, package_cache, max_filesize) {
max_filesize_missing <- is.null(sys_env_max_filesize()) && is.null(max_filesize)
max_filesize_cli_fn <- if (max_filesize_missing) cli::cli_warn else cli::cli_abort

max_filesize <- max_filesize %||% sys_env_max_filesize() %||% SHINYLIVE_DEFAULT_MAX_FILESIZE
max_filesize <- if (is.na(max_filesize) || (max_filesize < 0)) Inf else max_filesize
max_filesize_val <- max_filesize
max_filesize <- fs::fs_bytes(max_filesize)
if (is.na(max_filesize)) {
cli::cli_warn(c(
"!" = "Could not parse `max_filesize` value: {.code {max_filesize_val}}",
"i" = "Setting to {.code {SHINYLIVE_DEFAULT_MAX_FILESIZE}}"
))
max_filesize <- fs::fs_bytes(SHINYLIVE_DEFAULT_MAX_FILESIZE)
}

# Core packages in base webR image that we don't need to download
shiny_pkgs <- c("shiny", "bslib", "renv")
shiny_pkgs <- resolve_dependencies(shiny_pkgs, local = FALSE)
Expand Down Expand Up @@ -240,13 +263,31 @@ download_wasm_packages <- function(appdir, destdir, package_cache) {
# Create package ref and lookup download URLs
meta <- prepare_wasm_metadata(pkg, prev_meta)

if (!meta$cached && length(meta$assets) > 0) {
if (!meta$cached) {
# Download Wasm binaries and copy to static assets dir
for (file in meta$assets) {
utils::download.file(file$url, fs::path(pkg_subdir, file$filename))
path <- fs::path(pkg_subdir, file$filename)
utils::download.file(file$url, path)

# Disallow this package if an asset is too large
if (fs::file_size(path) > max_filesize) {
fs::dir_delete(pkg_subdir)
meta$assets = list()
max_filesize_cli_fn(c(
"!" = "The file size of package {.pkg {pkg}} is larger than the maximum allowed file size of {.strong {max_filesize}}.",
"!" = "This package will not be included as part of the WebAssembly asset bundle.",
"i" = "Set the maximum allowed size to {.code -1}, {.code Inf}, or {.code NA} to disable this check.",
"i" = if (max_filesize_missing) "Explicitly set the maximum allowed size to treat this as an error." else NULL
))
break
}
}

meta$cached <- TRUE
meta$path <- glue::glue("packages/{pkg}/{meta$assets[[1]]$filename}")
meta$path <- NULL
if (length(meta$assets) > 0) {
meta$path <- glue::glue("packages/{pkg}/{meta$assets[[1]]$filename}")
}
}
meta
})
Expand Down
2 changes: 1 addition & 1 deletion R/quarto_ext.R
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ build_app_resources <- function(app_json) {
# Download wasm binaries ready to embed into Quarto deps
withr::with_options(
list(shinylive.quiet = TRUE),
download_wasm_packages(appdir, destdir, package_cache = TRUE)
download_wasm_packages(appdir, destdir, package_cache = TRUE, max_filesize = NULL)
)

# Enumerate R package Wasm binaries and prepare the VFS images as html deps
Expand Down
6 changes: 6 additions & 0 deletions man/export.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions tests/testthat/apps/app-utf8/app.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
library(shiny)
library(utf8)

ui <- fluidPage()
server <- function(input, output) {}

shinyApp(ui, server)
48 changes: 47 additions & 1 deletion tests/testthat/test-export.R
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,50 @@ test_that("export with template", {
index_content,
"<meta name=\"description\" content=\"My custom export template param test app\">"
)
})
})

test_that("export - include R package in wasm assets", {
maybe_skip_test()

assets_ensure()

# Ensure pkgcache metadata has been loaded
invisible(pkgcache::meta_cache_list())

# Create a temporary output directory
out_dir <- file.path(tempfile(), "out")
pkg_dir <- file.path(out_dir, "shinylive", "webr", "packages")

# A package with an external dependency
app_dir <- test_path("apps", "app-utf8")
asset_package <- c("utf8")

# Default filesize 100MB
expect_silent_unattended({
export(app_dir, out_dir)
})
expect_contains(dir(pkg_dir), c(asset_package))
unlink_path(out_dir)

# No maximum filesize
expect_silent_unattended({
export(app_dir, out_dir, max_filesize = Inf)
})
expect_contains(dir(pkg_dir), c(asset_package))
unlink_path(out_dir)

# Set a maximum filesize
expect_error({
export(app_dir, out_dir, max_filesize = "1K")
})
unlink_path(out_dir)

expect_error({
withr::with_envvar(
list("SHINYLIVE_DEFAULT_MAX_FILESIZE" = "1K"),
export(app_dir, out_dir)
)
})
unlink_path(out_dir)

})

0 comments on commit 2d4be82

Please sign in to comment.