# install.packages('pak')
::pak('mjfrigaard/shinypak') pak
9 External files
In this chapter we’ll cover how to add external resources (i.e., files previously stored and served from the www/
folder) to your app-package, and how to store and run multiple applications.
9.1 External files
When we launch our app, we see the shiny.png
logo in www/
is not being loaded into the UI when the application is displayed:
launch_app(run = 'p')
Serving the contents of www
was previously being handled automatically by Shiny’s internal functions, but now that we’ve converted our application into a package, we’ll need to explicitly tell the application where to find these resources.1
9.1.1 Package files
While developing, we get used to accessing and interacting with our package files from the Files pane or Explorer window:
However, these aren’t the files that get bundled into an R package. We can follow the path in that is displayed when we install our package.
The Build pane when we install our package (or use Ctrl/Cmd + Shift + B) to view the installed package files:
==> R CMD INSTALL --preclean --no-multiarch --with-keep.source sap
* installing to library ‘/Users/username/Library/R/x86_64/4.4/library’
* installing *source* package ‘sap’ ...
** using staged installation
** R
** data
*** moving datasets to lazyload DB
** inst
** byte-compile and prepare package for lazy loading
** help
*** installing help indices
** building package indices
** testing if installed package can be loaded from temporary location
** testing if installed package can be loaded from final location
** testing if installed package keeps a record of temporary installation path * DONE
- 1
-
This is the location of the installed version of
sap
* Executing task: /Library/Frameworks/R.framework/Versions/4.4-x86_64/Resources/bin/R -e 'pak::local_install(upgrade = FALSE)'
R version 4.4.0 (2024-04-24) -- "Puppy Cup"
Copyright (C) 2024 The R Foundation for Statistical Computing
Platform: x86_64-apple-darwin20
R is free software and comes with ABSOLUTELY NO WARRANTY.
You are welcome to redistribute it under certain conditions.
Type 'license()' or 'licence()' for distribution details.
Natural language support but running in an English locale
R is a collaborative project with many contributors.
Type 'contributors()' for more information and
'citation()' on how to cite R or R packages in publications.
Type 'demo()' for some demos, 'help()' for on-line help, or
'help.start()' for an HTML browser interface to help.
Type 'q()' to quit R.
> pak::local_install(upgrade = FALSE)
✔ Updated metadata database: 7.52 MB in 17 files.
✔ Updating metadata database ... done
→ Will update 1 package.
→ Will download 1 package with unknown size.
+ sap 0.0.0.9000 → 0.0.0.9000 👷🏿♀️ ⬇
ℹ Getting 1 pkg with unknown size
✔ Got sap 0.0.0.9000 (source) (96 B)
ℹ Packaging sap 0.0.0.9000
✔ Packaged sap 0.0.0.9000 (17.4s)
ℹ Building sap 0.0.0.9000
✔ Built sap 0.0.0.9000 (2.4s)
✔ Installed sap 0.0.0.9000 (local) (83ms)
✔ 1 pkg + 61 deps: kept 60, upd 1, dld 1 (NA B) [51.2s]
>
> * Terminal will be reused by tasks, press any key to close it.
- 1
-
A new R session is started when we use Ctrl/Cmd + Shift + B
- 2
-
Uses local cache for updating package
- 3
- Terminal remains open for future builds
Our installed package files
Below are folder trees for our source package and our installed package:2
# source
sap/
├── DESCRIPTION
├── NAMESPACE
├── R
│ ├── data.R
│ ├── display_type.R
│ ├── mod_scatter_display.R
│ ├── mod_var_input.R
│ ├── launch_app.R
│ ├── movies_server.R
│ ├── movies_ui.R
│ └── scatter_plot.R
├── README.md
├── app.R
├── data
│ ├── movies.RData
│ └── movies.rda
├── inst
│ └── extdata
│ └── movies.fst
├── man
│ ├── display_type.Rd
│ ├── mod_scatter_display_server.Rd
│ ├── mod_scatter_display_ui.Rd
│ ├── mod_var_input_server.Rd
│ ├── mod_var_input_ui.Rd
│ ├── movies.Rd
│ ├── launch_app.Rd
│ ├── movies_server.Rd
│ ├── movies_ui.Rd
│ └── scatter_plot.Rd
├── sap.Rproj
└── www └── shiny.png
- 1
- Files/folders in both source and installed versions
- 2
- Files/folders in both source and installed versions
- 3
- Files/folders in both source and installed versions
- 4
- Files/folders in both source and installed versions
- 5
- Files/folders in both source and installed versions
sap/
├── DESCRIPTION
├── INDEX
├── Meta
│ ├── Rd.rds
│ ├── data.rds
│ ├── features.rds
│ ├── hsearch.rds
│ ├── links.rds
│ ├── nsInfo.rds
│ └── package.rds
├── NAMESPACE
├── R
│ ├── sap
│ ├── sap.rdb
│ └── sap.rdx
├── data
│ ├── Rdata.rdb
│ ├── Rdata.rds
│ └── Rdata.rdx
├── extdata
│ └── movies.fst
├── help
│ ├── AnIndex
│ ├── aliases.rds
│ ├── sap.rdb
│ ├── sap.rdx
│ └── paths.rds
└── html
├── 00Index.html └── R.css
- 1
- Files/folders in both source and installed versions
- 2
- Files/folders in both source and installed versions
- 3
- Files/folders in both source and installed versions
- 4
- Files/folders in both source and installed versions
- 5
- Files/folders in both source and installed versions
The installed version of our package includes some of the same ‘source’ files we’ve been working with (i.e., the NAMESPACE
and DESCRIPTION
), but many of these files aren’t included in the installed version (i.e., the .R
and .Rd
files, the man/
folder).
9.1.2 system.file()
Hopefully seeing the contents of an installed package demystifies what happens when we run devtools::install()
. When we want to add non-R package files to our app (like our shiny.png
logo), we store these files in the inst/
folder and access them with system.file()
.3
“The contents of the
inst/
subdirectory will be copied recursively to the installation directory. Subdirectories ofinst/
should not interfere with those used by R (currently,R/
,data/
,demo/
,exec/
,libs/
,man/
,help/
,html/
andMeta/
, and earlier versions usedlatex/
,R-ex/
).” - Writing R extensions, Package subdirectories
system.file()
gives us access to the package files on installation (i.e., the files we saw in the folder tree above). In the data chapter, we used system.file()
to access the movies.fst
file in inst/extdata/
:
::read_fst(
fstpath = system.file("extdata/", "movies.fst",
package = "sap")
)
As we can see, the movies.fst
has two locations: the ‘source’ package location, and the ‘installed’ location.
Source package files
# What we see
inst/
└── extdata/
└── movies.fst
Installed package files
# What R sees
└── extdata/
└── movies.fst
system.file()
is accessing movies.fst
from the installed location. To include the contents of www/
in our app-package, we’ll need to move www/
into inst/
, then access it’s contents with system.file()
.4
9.1.3 addResourcePath()
The addResourcePath()
function will add a “directory of static resources to Shiny’s web server.” In sap
, want to add the www
directory that includes the shiny.png
file.5
Current www
location
├── inst
│ └── extdata
│ └── movies.fst
└── www
└── shiny.png
New www
location
inst/
├── extdata/
│ └── movies.fst
└── www/
└── shiny.png
In R/movies_ui.R
, we’ll include the addResourcePath()
at the top of the tagList()
and reference the image in img()
using only the subfolder in the path:
<- function() {
movies_ui addResourcePath(
prefix = 'www',
directoryPath = system.file('www', package = 'sap'))
tagList(
fluidPage(
theme = shinythemes::shinytheme("spacelab"),
titlePanel(
div(
img(
src = "www/shiny.png",
height = 60,
width = 55,
style = "margin:10px 10px"
), "Movie Reviews"
)
),sidebarLayout(
sidebarPanel(
mod_var_input_ui("vars")
),mainPanel(
mod_scatter_display_ui("plot")
)
)
)
) }
- 1
-
Prefix (or folder name) of installed location
- 2
-
Path to installed package files
- 3
- Reference to installed package image file
After loading, documenting, and installing, the application now includes the image file.
Ctrl/Cmd + Shift + L / D / B
Launch app with the shinypak
package:
launch('09.1_inst-www')
library(sap)
launch_app(run = 'p')
The inst/
folder is very versatile and can also be used to store a variety of files we’re using in our application (CSS styling, JavaScript functions, HTML, etc). In the following sections, I’m going to demonstrate using inst/
for storing alternative UI layouts, data, and production versions of our application.
9.2 inst/bslib
We can use inst/
to store alternative files and then configure our UI function to test different layouts. During development, you might want a version of your app with the exact same functionality, but a different UI layout.6
Launch app with the shinypak
package:
launch('09.2_inst-bslib')
In the example below, I’ve included a second optional UI layout that uses a bslib
theme in movies_ui()
. The bslib
argument includes an alternate image file (stored in inst/www/bootstrap.png
):
show/hide movies_ui()
<- function(bslib = FALSE) {
movies_ui addResourcePath(
prefix = 'www',
directoryPath = system.file('www', package = 'sap'))
if (isFALSE(bslib)) {
tagList(
::page_fillable(
bslibh1("Movie Reviews"),
::layout_sidebar(
bslibsidebar =
::sidebar(
bslibtitle = tags$h4("Sidebar inputs"),
img(
src = "shiny.png",
height = 60,
width = 55,
style = "margin:10px 10px"
),mod_var_input_ui("vars")
),::card(
bslibfull_screen = TRUE,
::card_header(
bslib$h4("Scatter Plot")
tags
),::card_body(fillable = TRUE,
bslibmod_scatter_display_ui("plot")
),::card_footer(
bslib$blockquote(
tags$em(
tags$p(
tags"The data for this application comes from the ",
$a("Building web applications with Shiny",
tagshref = "https://rstudio-education.github.io/shiny-course/"
),"tutorial"
)
)
)
)
)
)
)
)else {
} tagList(
::page_fillable(
bslibtitle = "Movie Reviews (bslib)",
theme = bslib::bs_theme(
bg = "#101010",
fg = "#F6F5F5",
primary = "#EE6F57",
secondary = "#32E0C4",
success = "#FF4B5C",
base_font = sass::font_google("Ubuntu"),
heading_font = sass::font_google("Ubuntu")
),::layout_sidebar(
bslibsidebar = bslib::sidebar(
open = TRUE,
mod_var_input_ui("vars")
),::card(
bslibfull_screen = TRUE,
::card_header(
bslibimg(src = "www/bootstrap.png",
height = 80,
width = 100,
style = "margin:10px 10px")
),::card_body(fillable = TRUE,
bslibmod_scatter_display_ui("plot")
)
)
)
)
)
} }
- 1
-
Include
inst/www
resources
- 2
-
Standard
fluidPage()
- 3
-
bslib
layout
- 4
-
Reference to alternate image (
bootstrap.png
)
Ctrl/Cmd + Shift + L / D / B
This alternate version of launch_app()
uses the same modules and utility functions as the previous versions, but when bslib = TRUE
, the app displays the alternate UI layout:
launch_app(run = 'p', bslib = TRUE)
The example above was a simple, but using inst/
to hold alternate layouts that can be displayed with a single argument is a great tool when demoing versions for stakeholders.
9.3 inst/dev
It’s not uncommon to be working on an application that will display data from multiple sources. In these situations, we might want to store the development files in the inst/dev
folder.
Launch app with the shinypak
package:
launch('09.3_inst-dev')
9.3.1 tidy_movies
data
The ‘development’ application in sap
uses a tidy version of the ggplot2movies::movies
data, which we created in the data-raw/tidy_movies.R
file.7
show/hide data-raw/tidy_movies.R
## code to prepare the `tidy_movies` dataset goes here
# load packages --------------------
library(fst)
<- function(movies_data_url) {
make_tidy_ggp2_movies <- read.csv(file = movies_data_url)
movies_data # specify genre columns
<- c(
genre_cols "Action", "Animation",
"Comedy", "Drama",
"Documentary", "Romance",
"Short"
)# calculate row sum for genres
$genre_count <- rowSums(movies_data[, genre_cols])
movies_data# create aggregate 'genres' for multiple categories
$genres <- apply(
movies_dataX = movies_data[, genre_cols],
MARGIN = 1,
FUN = function(row) {
<- names(row[row == 1])
genres if (length(genres) > 0) {
return(paste(genres, collapse = ", "))
else {
} return(NA)
}
}
)# format variables
$genre_count <- as.integer(movies_data$genre_count)
movies_data$genre <- ifelse(test = movies_data$genre_count > 1,
movies_datayes = "Multiple genres",
no = movies_data$genres
)$genre <- as.factor(movies_data$genre)
movies_data$mpaa <- factor(movies_data$mpaa,
movies_datalevels = c("G", "PG", "PG-13", "R", "NC-17"),
labels = c("G", "PG", "PG-13", "R", "NC-17")
)
# reduce columns to only those in graph
c(
movies_data[, "title", "year", "length", "budget",
"rating", "votes", "mpaa", "genre_count",
"genres", "genre"
)]
}
<- make_tidy_ggp2_movies("https://raw.githubusercontent.com/hadley/ggplot2movies/master/data-raw/movies.csv")
tidy_movies
# save to inst/dev/
::write_fst(x = tidy_movies, path = "inst/dev/tidy_movies.fst") fst
9.3.2 inst/dev/R
We can place the application modules, UI, and server functions in inst/dev/R
. Both functions in the dev/
display module has been re-written to add functionality for importing the tidy_movies.fst
data file and an option to removing missing values from the graph.
Any of the functions from sap
can be used in the dev/
modules with explicit namespacing (i.e., sap::fun()
). For example, dev_mod_vars_ui()
contains choices for the columns in the tidy_movies
data, but there’s no need to re-write the mod_var_input_server()
function.8
The app.R
file will contain the call to shinyApp()
, and any other packages we’ll need to launch the application. The data and alternative image file can be placed in the root folder (with the app.R
file):
inst/dev
├── R
│ ├── devServer.R
│ ├── devUI.R
│ ├── dev_mod_scatter.R
│ └── dev_mod_vars.R
├── app.R
├── imdb.png
└── tidy_movies.fst
2 directories, 7 files
9.3.3 Launch dev
Finally, we’ll launch the development data app with it’s own standalone function, ggp2_launch_app()
. This function is similar to launch_app()
, but the appDir
argument is set to the location of the development files (which we locate with system.file()
).
Launch app with the shinypak
package:
launch('09.3_inst-dev')
show/hide R/ggp2_launch_app.R
#' Development `ggplot2movies` app standalone function
#'
#' Wrapper function for `shinyAppDir()`
#'
#' @param test logical, run in `test.mode`? Defaults to `TRUE`.
#'
#' @return shiny app
#'
#'
#' @export
<- function(options = list(), run = "w") {
ggp2_launch_app if (interactive()) {
display_type(run = run)
} shinyAppDir(
appDir = system.file("dev",
package = "sap"
),options = options
) }
After loading, documenting, and installing, we’ll run the development data app:
Ctrl/Cmd + Shift + L / D / B
ggp2_launch_app(run = 'p')
I’ve been using different colors and themes for alternative applications above. This can be a quick and easy way to differentiate the versions of your application.
9.4 inst/prod
It’s also possible to have a folder dedicated for deploying the application in an app-package.
Launch app with the shinypak
package:
launch('09.4_inst-prod')
9.4.1 prod
data
This folder can be named inst/prod/
or inst/deploy
, and it can contain a version of your application that’s ‘fit for public consumption.’ In inst/prod/app
I’ve created an app.R
file:
inst/
└── prod/
└── app
└── app.R
2 directories, 1 file
9.4.2 prod/app/app.R
app.R
includes a call to shinyApp()
with the ui and server functions (explicitly namespaced from your app-package):
show/hide prod/app/app.R
shinyApp(
ui = sap::movies_ui(bslib = TRUE),
server = sap::movies_server)
I’ll use the bslib
version from above to differentiate it from the other applications in sap
.
9.4.3 Deploying prod
Back in the root app.R
file, we can use shinyAppDir()
and system.file()
to return the app object from prod/app/app.R
:
show/hide app.R
::with_options(new = list(shiny.autoload.r = FALSE), code = {
withrif (!interactive()) {
sink(stderr(), type = "output")
tryCatch(
expr = {
# load package ----
library(sap)
},error = function(e) {
# load R/ folder ----
::load_all()
pkgload
}
)shinyAppDir(appDir =
system.file("prod/app", package = "sap"))
else {
} # load R/ folder ----
::load_all()
pkgload# create shiny object ----
::shinyApp(
shinyui = movies_ui,
server = movies_server
)
} })
- 1
-
Set option to turn off
loadSupport()
- 2
- Create shiny object from prod/app
Ctrl/Cmd + Shift + L / D / B
To deploy the app, call rsconnect::deployApp()
in the console and supply an appName
:
::deployApp(appName = 'movie-reviews-prod') rsconnect
The deployment log will look something like this:
── Preparing for deployment ─────────────────────────────────────────────────
✔ Deploying "movie-reviews-prod" using "server: shinyapps.io / username: <username>"
ℹ Creating application on server...
✔ Created application with id 12711883
ℹ Bundling 39 files: .Rbuildignore, app.R, data/movies.rda, data/movies.RData,
data-raw/tidy_movies.R, DESCRIPTION, inst/dev/app.R, inst/dev/imdb.png,
inst/dev/R/dev_mod_scatter.R, inst/dev/R/dev_mod_vars.R, inst/dev/R/devServer.R,
inst/dev/R/devUI.R, inst/dev/tidy_movies.fst, inst/extdata/movies.fst,
inst/prod/app/app.R, inst/www/bootstrap.png, inst/www/shiny.png,
man/display_type.Rd, …, R/scatter_plot.R, and README.md
ℹ Capturing R dependencies with renv
✔ Found 69 dependencies
✔ Created 1,568,327b bundle
ℹ Uploading bundle...
✔ Uploaded bundle with id 9101312
── Deploying to server ────────────────────────────────────────────────────────
Waiting for task: 1457289827
building: Processing bundle: 9101312
building: Building image: 11074678
building: Fetching packages
building: Installing packages
building: Installing files
building: Pushing image: 11074678
deploying: Starting instances
success: Stopping old instances
── Deployment complete ─────────────────────────────────────────────────────── ✔ Successfully deployed to <https://<username>.shinyapps.io/movie-reviews-prod/>
You can see a deployed version of this application here
Recap
This chapter had covered how to include external files and resources (i.e., what was previously stored in the www/
folder of a regular Shiny app project) in your app-package with addResourcePath()
and system.file()
.
We’ve also covered how to use the inst/
folder to include alternative files, development and production/deployment versions of your app. You can now launch the following applications from sap
:
Standard application with/without test mode
library(sap)
launch_app(options = list(test.mode = TRUE))
# or
launch_app(options = list(test.mode = FALSE))
inst/bslib
: an application with an alternative layout (with/without test mode)
library(sap)
launch_app(options = list(test.mode = TRUE), bslib = TRUE)
# or
launch_app(options = list(test.mode = FALSE), bslib = TRUE)
inst/dev
: an application using a ‘development’ dataset (with/without test mode)
library(sap)
ggp2_launch_app(options = list(test.mode = TRUE))
# or
ggp2_launch_app(options = list(test.mode = FALSE))
inst/prod
: An app.R
file for launching a ‘production’ version of our app.
library(sap)
::deployApp() rsconnect
In the next section, we’re going to cover testing the code in a shiny app-package.
This is a common problem developers encounter when converting shiny app into app-packages. See this popular thread on Posit Community.↩︎
fs::path_package(package = "sap")
returns the path to your installed package andfs::dir_tree()
function will print a folder tree.↩︎Read more about sub-directories to avoid in
inst/
in R Packages, 2ed.↩︎The key takeaway here is that the
inst/
subfolders and files are available unchanged in the installed version (with theinst/
folder omitted.).↩︎You can read more about adding external resources in the documentation for
addResourcePath()
.↩︎As the development of your application progresses, you can (and should) keep different versions of your application in separate Git branches. But I’ve also found using the
inst/
folder for those early stages of developing is helpful.↩︎We covered the
data-raw/
folder in the Data chapter, and you can read more about it here in R packages, 2ed↩︎This requires exporting
mod_var_input_server()
with@export
in theR/
folder.↩︎