Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Customize export template and parameters #96

Merged
merged 19 commits into from
Jul 1, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
3 changes: 2 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ Imports:
rappdirs,
gadenbuie marked this conversation as resolved.
Show resolved Hide resolved
renv,
rlang,
tools
tools,
whisker
Suggests:
httpuv (>= 1.6.12),
pkgcache,
Expand Down
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ export(assets_install_link)
export(assets_remove)
export(assets_version)
export(export)
importFrom(rlang,"%||%")
importFrom(rlang,is_interactive)
58 changes: 25 additions & 33 deletions R/app_json.R
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,11 @@ read_app_files <- function(
# """
# Write index.html, edit/index.html, and app.json for an application in the destdir.
# """
#' @importFrom rlang is_interactive
write_app_json <- function(
app_info,
destdir,
html_source_dir,
gadenbuie marked this conversation as resolved.
Show resolved Hide resolved
template_params = list(),
verbose = is_interactive()
) {
verbose_print <- if (isTRUE(verbose)) message else list
Expand All @@ -173,40 +173,32 @@ write_app_json <- function(
subdir_inverse <- paste0(subdir_inverse, "/")
}

# Then iterate over the HTML files in the template directory and interpolate
# the template parameters.
template_html_files <- fs::dir_ls(html_source_dir, recurse = TRUE, glob = "**.html")
gadenbuie marked this conversation as resolved.
Show resolved Hide resolved

template_params <- rlang::dots_list(
# Forced parameters
REL_PATH = subdir_inverse,
APP_ENGINE = "r",
# User parameters
!!!template_params,
# Default parameters
title = "Shiny App",
.homonyms = "first"
)

for (copy_info in list(
list(
src = fs::path(html_source_dir, "index.html"),
dest = fs::path(app_destdir, "index.html")
),
list(
src = fs::path(html_source_dir, "edit", "index.html"),
dest = fs::path(app_destdir, "edit", "index.html")
)
)) {
# Create destination directory
fs::dir_create(fs::path_dir(copy_info$dest))

# Read file
index_content <- brio::read_file(copy_info$src)
# Replace template info
index_content <- gsub(
pattern = "{{REL_PATH}}",
replacement = subdir_inverse,
index_content,
fixed = TRUE
)

# Set wasm engine
index_content <- gsub(
pattern = "{{APP_ENGINE}}",
replacement = "r",
index_content,
fixed = TRUE
for (template_file in template_html_files) {
file_content <- brio::read_file(template_file)

file_content_interp <- whisker::whisker.render(
template = file_content,
data = template_params
)

# Save updated file contents
brio::write_file(index_content, copy_info$dest)

dest_file <- fs::path(app_destdir, fs::path_rel(template_file, html_source_dir))
fs::dir_create(fs::path_dir(dest_file))
brio::write_file(file_content_interp, dest_file)
}

app_json_output_file <- fs::path(app_destdir, "app.json")
Expand Down
43 changes: 31 additions & 12 deletions R/export.R
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,38 @@
#' @param destdir Destination directory.
#' @param subdir Subdirectory of `destdir` to write the app to.
#' @param verbose Print verbose output. Defaults to `TRUE` if running
#' interactively.
#' interactively.
#' @param wasm_packages Download and include binary WebAssembly packages as
#' part of the output app's static assets. Defaults to `TRUE`.
#' part of the output app's static assets. Defaults to `TRUE`.
#' @param package_cache Cache downloaded binary WebAssembly packages. Defaults
#' to `TRUE`.
#' to `TRUE`.
#' @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
#' [shinylive asset releases](https://github.com/posit-dev/shinylive/releases)
#' website to learn more information about the available `assets_version` values.
#' exported app. Defaults to [assets_version()]. Note, not all custom assets
#' versions may work with this release of \pkg{shinylive}. Please visit the
#' [shinylive asset releases](https://github.com/posit-dev/shinylive/releases)
#' website to learn more information about the available `assets_version`
#' values.
#' @param template_dir Path to a custom template directory to use when exporting
#' the shinylive app. The template can be copied from the shinylive assets
#' using: `fs::path(shinylive:::assets_dir(), "export_template")`.
#' @param template_params A list of parameters to pass to the template. The
#' supported parameters depends on the template being used. Custom templates
#' may support additional parameters (see `template_dir` for instructions on
#' creating a custom template or to find the current shinylive assets'
#' templates).
#'
#' With shinylive assets > 0.4.1, the default export template supports the
#' following parameters:
#'
#' 1. `title`: The title of the app. Defaults to `"Shiny app"`.
#' 2. `include_in_head`, `include_before_body`, `include_after_body`: Raw
#' HTML to be included in the `<head>`, just after the opening `<body>`,
#' or just before the closing `</body>` tag, respectively.
#' @param ... Ignored
#' @export
#' @return Nothing. The app is exported to `destdir`. Instructions for serving
#' the directory are printed to stdout.
#' @importFrom rlang is_interactive
#' @examplesIf interactive()
#' @examplesIf rlang::is_interactive()
#' app_dir <- system.file("examples", "01_hello", package = "shiny")
#' out_dir <- tempfile("shinylive-export")
#'
Expand All @@ -41,7 +57,9 @@ export <- function(
verbose = is_interactive(),
wasm_packages = TRUE,
package_cache = TRUE,
assets_version = NULL
assets_version = NULL,
template_dir = NULL,
template_params = list()
) {
verbose_print <- if (verbose) message else list
if (is.null(assets_version)) {
Expand Down Expand Up @@ -173,8 +191,9 @@ export <- function(
write_app_json(
app_info,
destdir,
html_source_dir = fs::path(assets_path, "export_template"),
verbose = verbose
html_source_dir = template_dir %||% fs::path(assets_path, "export_template"),
gadenbuie marked this conversation as resolved.
Show resolved Hide resolved
verbose = verbose,
template_params = template_params
)

verbose_print(
Expand Down
1 change: 0 additions & 1 deletion R/quarto_ext.R
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@
#' ]
#' ```
#'
#' @importFrom rlang is_interactive
quarto_ext <- function(
args = commandArgs(trailingOnly = TRUE),
...,
Expand Down
8 changes: 8 additions & 0 deletions R/shinylive-package.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#' @keywords internal
"_PACKAGE"

## usethis namespace: start
#' @importFrom rlang %||%
#' @importFrom rlang is_interactive
## usethis namespace: end
NULL
28 changes: 25 additions & 3 deletions man/export.Rd

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

35 changes: 35 additions & 0 deletions man/shinylive-package.Rd

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

11 changes: 11 additions & 0 deletions tests/testthat/apps/export_template/edit/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<title>Redirect to editable app</title>
<meta
http-equiv="refresh"
content="0;URL='../index.html?mode=editor-terminal-viewer'"
/>
</head>
<body></body>
</html>
25 changes: 25 additions & 0 deletions tests/testthat/apps/export_template/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{title}}</title>
<meta name="description" content="{{ description }}">
<script
src="./{{REL_PATH}}shinylive/load-shinylive-sw.js"
type="module"
></script>
<script type="module">
import { runExportedApp } from "./{{REL_PATH}}shinylive/shinylive.js";
runExportedApp("root", "{{APP_ENGINE}}", "{{REL_PATH}}");
</script>
<link rel="stylesheet" href="./{{REL_PATH}}shinylive/style-resets.css" />
<link rel="stylesheet" href="./{{REL_PATH}}shinylive/shinylive.css" />
{{ include_in_head }}
</head>
<body>
{{ include_before_body }}
<div style="height: 100vh; width: 100vw" id="root"></div>
{{ include_after_body }}
</body>
</html>
62 changes: 62 additions & 0 deletions tests/testthat/test-export.R
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,65 @@ test_that("export - server.R", {
c("global.R", "ui.R", "server.R")
)
})

test_that("export with template", {
maybe_skip_test()
skip_if(assets_version() <= "0.4.1")

# For local testing until next release after 0.4.1
# withr::local_envvar(list("SHINYLIVE_ASSETS_VERSION" = "0.4.1"))

assets_ensure()

path_export <- test_path("apps", "export_template")

if (FALSE) {
# Run this manually to re-initialize the export template, but you'll need to
# add the template parameters tested below.
path_export_src <- fs::path(shinylive:::assets_dir(), "export_template")
fs::dir_copy(path_export_src, path_export, overwrite = TRUE)
}

# Create a temporary directory
out_dir <- file.path(tempfile(), "out")
on.exit(unlink_path(out_dir))

app_dir <- test_path("apps", "app-r")

expect_silent({
export(
app_dir,
out_dir,
template_dir = path_export,
template_params = list(
# Included in export template for > 0.4.1
title = "Shinylive Test App",
include_before_body = "<h1>Shinylive Test App</h1>",
include_after_body = "<footer>r-shinylive</footer>",
# Included in the customized export template in test suite
description = "My custom export template param test app"
)
)
})

index_content <- brio::read_file(fs::path(out_dir, "index.html"))
expect_match(
index_content,
"<title>Shinylive Test App</title>"
)

expect_match(
index_content,
"<body>\\s+<h1>Shinylive Test App</h1>"
)

expect_match(
index_content,
"<footer>r-shinylive</footer>\\s+</body>"
)

expect_match(
index_content,
"<meta name=\"description\" content=\"My custom export template param test app\">"
)
})
Loading