# install.packages('pak')
::pak('mjfrigaard/shinypak') pak
1 Whole app game
This chapter is modeled on the Whole Game chapter in R Packages, 2ed. We’ll go through the development of a small shiny app-package.1 The monthAppPkg
example we’re going to be developing has been adapted from Mastering Shiny.2
1.1 A toy app-package
Rather than duplicating how the devtools
/usethis
functions are used while writing R packages, I’m going to focus on the areas that differ from Shiny application development (each section is covered in more detail in the following chapters). The final result will be a Shiny application with all the devtools
features and functionality.
1.2 use_description()
Every package needs a DESCRIPTION
–calling devtools::load_all()
without a DESCRIPTION
will result in an error. The quickest way to create a description file is with usethis::use_description()
.3
::use_description(
usethislist(Package = 'monthAppPkg',
Title = 'An example app-pkg',
Version = '0.0.0.9000',
Description = 'A shiny application built inside an R package.',
"Authors@R" = NULL,
Author = utils::person(
given = "Jane",
family = "Doe",
role = c("aut", "cre")),
Maintainer = utils::person(
given = "Jane",
family = "Doe",
email = "Jane.Doeh@email.io"),
License = "GPL-3"))
I’ve provided values to the fields
list so this file isn’t generated with the boilerplate contents. The documentation for use_description()
mentions putting this in your .Rprofile
if you develop a lot of R packages (or app-packages!).4
Every R package needs the following seven fields:5
Package: monthAppPkg
Title: An example app-pkg
Version: 0.0.0.9000
Author: Jane Doe [aut, cre]
Maintainer: Jane Doe <Jane.Doeh@email.io>
Description: A shiny application built inside an R package.
License: GPL-3
We get the following fields for free (they will be required for function documentation and dependency management).
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.2.3
1.3 use_data()
To include the stones
data in monthAppPkg
, we create a script in data-raw/
with usethis::use_data_raw("stones")
, then move the birthstones.csv
file into data-raw/
, load it into the Global Environment and pass it to usethis::use_data()
:
::use_data_raw("stones") usethis
✔ Setting active project to '/path/to/monthAppPkg'
✔ Creating 'data-raw/'
✔ Adding '^data-raw$' to '.Rbuildignore'
✔ Writing 'data-raw/stones.R'
• Modify 'data-raw/stones.R'
• Finish the data preparation script in 'data-raw/stones.R' • Use `usethis::use_data()` to add prepared data to package
Move birthstones.csv
to data-raw/birthstones.csv
:
::file_move(path = "birthstones.csv", new_path = "data-raw/birthstones.csv") fs
Contents of data-raw/stones.R
:
## code to prepare `stones` dataset goes here
library(vroom)
<- vroom::vroom("data-raw/birthstones.csv")
stones ::use_data(stones, overwrite = TRUE) usethis
Rows: 12 Columns: 2
── Column specification ──────────────────────────────────────────────────
Delimiter: ","
chr (2): month, stone
ℹ Use `spec()` to retrieve the full column specification for this data. ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
::use_data(stones) usethis
✔ Adding 'R' to Depends field in DESCRIPTION
✔ Setting LazyData to 'true' in 'DESCRIPTION'
✔ Saving 'stones' to 'data/stones.rda' • Document your data (see 'https://r-pkgs.org/data.html')
Data should be documented using roxygen2
,6 which we’ll cover in the data chapter.
1.4 use_package()
Every Shiny app-package will need to import the shiny
package as a dependency, which starts by listing it under the Imports
field the DESCRIPTION
file. We can do this using usethis::use_package()
.7
::use_package("shiny") usethis
✔ Adding 'shiny' to Imports field in DESCRIPTION • Refer to functions with `shiny::fun()`
We’re advised to use explicit namespacing (i.e., pkg::fun()
), but we can avoid this by importing Shiny’s functions into our package namespace.
The @import
tag from roxygen2
can be used to import “the entire [shiny
] namespace” into monthAppPkg
.8
1.5 load_all()
Shiny app development typically involves something like the following workflow:
- Write UI code
- Write server code
- Click Run App
- Rinse, repeat
When making the switch to app-package development, calling load_all()
is somewhat analogous to clicking on the Run App icon–you’ll do it often (more than any other devtools
or usethis
function).
::load_all() devtools
The output we’re looking for from load_all()
is straightforward:
ℹ Loading monthAppPkg
load_all()
is similar to calling library
, but it’s specifically designed to be used during package development.
1.6 use_r()
Create new .R
files under R/
using use_r()
:
::use_r("monthFeedback") usethis
✔ Setting active project to '/projects/apps/monthAppPkg' • Modify 'R/monthFeedback.R'
Both UI and server module functions are stored in R/monthFeedback.R
and R/birthstone.R
. Tests should also be created for each function.
1.7 use_test()
Create new test-
files under tests/testthat/
using using use_test()
:
::use_test("monthFeedbackServer") usethis
The first time you run use_test()
, it will detect if your package has the testthat
infrastructure (and create the necessary files if you don’t).9
Calling use_test()
adds the testthat
package to the Suggests
field in the DESCRIPTION
and includes the edition (currently 3
).
✔ Adding 'testthat' to Suggests field in DESCRIPTION ✔ Adding '3' to Config/testthat/edition
The tests/
folder will hold the necessary testing folder and files:
tests/
├── testthat/
│ ├── test-birthstoneServer.R
│ └── test-monthFeedbackServer.R
└── testthat.R
2 directories, 3 files
Tests are covered in Mastering Shiny,10 on the Shiny website,11 and in various testing packages (like shinytest
12 and shinytest2
13)
1.8 app.R
contents
The contents of app.R have been converted to a standalone app function (monthApp()
), which is stored in the R/
folder.
The new contents of app.R
includes a call to pkgload::load_all()
and monthApp()
:
::load_all(".")
pkgloadmonthApp()
pkgload
needs to be listed under Imports
in the DESCRIPTION
file (just like we did with shiny
above).
::use_package("pkgload") usethis
✔ Adding 'pkgload' to Imports field in DESCRIPTION • Refer to functions with `pkgload::fun()`
Because we’re only going to use load_all()
from pkgload
, we’ll use explicit namespacing (i.e., pkg::fun()
).14
1.8.1 use_package_doc()
The use_package_doc()
creates the R/[[name]-package].R
file, which can be used as a single location for declaring dependencies in monthAppPkg
:
::use_package_doc() usethis
✔ Setting active project to 'projects/apps/monthAppPkg'
✔ Writing 'R/monthAppPkg-package.R' • Modify 'R/monthAppPkg-package.R'
We’ll use @importFrom
to add only the load_all()
function to the NAMESPACE
.
#' @keywords internal
"_PACKAGE"
## usethis namespace: start
#' @importFrom pkgload load_all
## usethis namespace: end
NULL
1.8.2 use_build_ignore()
R packages don’t typically have an app.R
file in their root folder, so we’ll let devtools
know this file should be ignored by creating a .Rbuildignore
and include a pattern that excludes app.R
whenever the package is built.
::use_build_ignore("app.R") usethis
✔ Adding '^app\\.R$' to '.Rbuildignore'
It’s best to let use_build_ignore()
handle excluding any files or folders from your package builds because it automatically writes the correct regular expression pattern.
1.9 LICENSE
Including a LICENSE
file can be done with one of the usethis
license functions. The license file should match the License
field in the DESCRIPTION
file (in this case, it’s MIT
).
::use_mit_license() usethis
use_mit_license()
will automatically include the LICENSE.md
file in the root folder (and includes the necessary pattern in the .Rbuildignore
to exclude it from the package builds).
✔ Adding 'MIT + file LICENSE' to License
✔ Writing 'LICENSE'
✔ Writing 'LICENSE.md' ✔ Adding '^LICENSE\\.md$' to '.Rbuildignore'
1.10 document()
All of the files below R/
should include roxygen2
documentation. You can include an Roxygen skeleton in the IDE by clicking on Code > Insert Roxygen Skeleton, or using the keyboard shortcut:
Option/⌥ + Shift⇧ + Ctrl/Cmd + R
After writing the documentation for the data, modules, and standalone app function, calling devtools::document()()
generates the .Rd
files and NAMESPACE
::document() devtools
The output from document()
tells us what files have been created (and if there were any errors in them).15
ℹ Updating monthAppPkg documentation
ℹ Loading monthAppPkg
Writing NAMESPACE
Writing NAMESPACE
Writing birthstoneUI.Rd
Writing birthstoneServer.Rd
Writing monthApp.Rd
Writing monthFeedbackUI.Rd Writing monthFeedbackServer.Rd
1.10.1 NAMESPACE
The NAMESPACE
file contains the exported functions from monthAppPkg
, and the two imports (shiny
and pkgload::load_all()
):
# Generated by roxygen2: do not edit by hand
export(birthstoneServer)
export(birthstoneUI)
export(monthApp)
export(monthFeedbackServer)
export(monthFeedbackUI)
import(shiny) importFrom(pkgload,load_all)
1.11 Project options
In order to enable the Build pane and keyboard shortcuts in the IDE, we need to update our .Rproj
file. We can edit this file using file.edit()
:
file.edit("monthAppPkg.Rproj")
If monthAppPkg
was initially built as an RStudio project (i.e., not as a package), the following fields should be included at the bottom of monthAppPkg.Rproj
:
BuildType: Package
PackageUseDevtools: Yes
PackageInstallArgs: --no-multiarch --with-keep.source PackageRoxygenize: rd,collate,namespace
These options are also available under Tools > Project Options … > Build Tools
1.12 use_git()
use_git()
is performed much earlier in R Packages, 2ed, but I’ve saved it for this step because using Git will prompt the IDE to re-initialize and display the Git pane (and it will also read our new settings in the .Rproj
file).
✔ Setting active project to '/projects/apps/monthAppPkg'
✔ Initialising Git repo
✔ Adding '.Rproj.user', '.Rhistory', '.Rdata', '.httr-oauth', '.DS_Store', '.quarto' to '.gitignore'
There are 12 uncommitted files:
* '.gitignore'
* '.Rbuildignore'
* 'app.R'
* 'data/'
* 'DESCRIPTION'
* 'LICENSE'
* 'LICENSE.md'
* 'man/'
* 'monthAppPkg.Rproj'
* 'NAMESPACE'
* ...
Is it ok to commit them?
1: Absolutely not
2: Not now 3: Absolutely
We’ll agree to commit these files to Git:
Selection: 3
✔ Adding files
✔ Making a commit with message 'Initial commit'
• A restart of RStudio is required to activate the Git pane
Restart now?
1: Not now
2: Negative 3: Absolutely
Restarting RStudio will activate the Git and Build panes:
This will also activate the devtools
keyboard shortcuts:
1.12.1 devtools
keyboard shortcuts
1.12.1.1 load_all()
Shift + Ctrl/Cmd + L
1.12.1.2 document()
Shift + Ctrl/Cmd + D
1.12.1.3 install()
Shift + Ctrl/Cmd + B
1.12.1.4 test()
Shift + Ctrl/Cmd + T
1.13 install()
Now we’re ready to install monthAppPkg
with devtools::install()
, which produces see the following output in the Build pane:
==> devtools::document(roclets = c('rd', 'collate', 'namespace'))
ℹ Updating monthAppPkg documentation
ℹ Loading monthAppPkg
Documentation completed
==> R CMD INSTALL --preclean --no-multiarch --with-keep.source monthAppPkg
* installing to library ‘/path/to/Library/R/x86_64/4.2/library’
* installing *source* package ‘monthAppPkg’ ...
** using staged installation
** R
** data
*** moving datasets to lazyload DB
** 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 (monthAppPkg)
Back in the Console, RStudio will restart and call library(monthAppPkg)
:
Restarting R session...
> library(monthAppPkg)
We can now launch the app using monthApp()
monthApp()
Launch app with the shinypak
package:
launch('01_whole-app-game')
1.14 Additional files
The following sections cover additional files you should include in your ap-package (but are not required).
1.14.1 use_readme_rmd()
The README.md
file is usually the initial point of contact for users and contributors looking for information about your app-package. use_readme_rmd()
will create a README.Rmd
(i.e., the file you’ll edit), which serves as the source document for your README.md
.
::use_readme_rmd() usethis
The README.Rmd
pattern is automatically added to the .Rbuildignore
, and includes a Git ‘pre-commit’ hook:
✔ Adding '^README\\.Rmd$' to '.Rbuildignore' ✔ Writing '.git/hooks/pre-commit'
This Git behavior is designed to prevent us from making changes to the README.Rmd
and forgetting to re-render the README.md
. If you find this behavior confusing or would like to disable it, run the following commands in the Terminal:
rm .git/hooks/pre-commit
1.14.2 use_news_md()
A NEWS.md
is helpful for logging updates to your app-package and tracking release information.
::use_news_md() usethis
use_news_md()
will also prompt me to add and commit this file to the Git repository:
There is 1 uncommitted file:
* 'NEWS.md'
Is it ok to commit it?
1: Negative
2: Yeah
3: Absolutely not
Selection: 2
✔ Adding files ✔ Making a commit with message 'Add NEWS.md'
The contents of the NEWS.md
are below:
# monthAppPkg (development version)
* Initial CRAN submission.
The 2nd bullet doesn’t apply to monthAppPkg
, so I’ll remove it and re-commit/push the NEWS.md
file.
1.14.3 use_vignette()
Vignettes can be used to store detailed tutorials, explanations of core concepts, use-cases, FAQs and troubleshooting, integration with other packages, etc.
use_vignette("monthAppPkg")
The first time we call use_vignette()
will prompt usethis to add the following fields in the DESCRIPTION
:
✔ Adding 'knitr' to Suggests field in DESCRIPTION
✔ Adding 'rmarkdown' to Suggests field in DESCRIPTION ✔ Adding 'knitr' to VignetteBuilder
The following files are also included in the .gitignore
:
✔ Adding 'inst/doc' to '.gitignore'
✔ Creating 'vignettes/' ✔ Adding '*.html', '*.R' to 'vignettes/.gitignore'
1.15 Recap
In this chapter we’ve covered the steps used to create a package containing a Shiny application.
The example app comes from the Packages chapter of Mastering Shiny↩︎
I’ve stored the code for this application in the
01_whole-app-game
branch of thesap
repository (to avoid confusing it with the actual application repo for this chapter).↩︎The Whole Game chapter of R Packages, 2ed begins with the
usethis::create_package()
function, which callsusethis::use_description()
internally.↩︎At the time this was written, there are over 4,000 hits with the boilerplate value for
Description
(i.e.,"What the package does"
), which is a sign of how muchusethis
has been adopted (and how often people forget to come back and edit theirDESCRIPTION
file).↩︎Always leave an empty final line in the
DESCRIPTION
↩︎View the documented
stones
dataset here on GitHub.↩︎Whenever you use a function from another package, start by running
usethis::use_package()
to ensure it’s in theDESCRIPTION
file.↩︎I’ve included
@import shiny
above the definition of our standalone app function (R/launch_app.R
), which means I don’t need to addshiny::
when using Shiny functions belowR/
.↩︎You can also set up the
testthat
infrastructure by callingusethis::use_testthat()
↩︎The Testing chapter in Mastering Shiny covers unit tests with
testthat
,shiny::testServer()
, and theshinytest
package.↩︎See the ‘Server Function Testing’ article on the Shiny website for more information on
testServer()
↩︎Check the
shinytest
package website and video tutorial for more information on testing your app.↩︎shinytest2
is an updated verison ofshinytest
with excellent documentation and videos.↩︎We typically call
devtools::load_all()
, but usingpkgload
reduces the number of dependencies included withdevtools
. Read more aboutpkgload
in the ‘Conscious uncoupling’ ofdevtools
.↩︎The files created by
document()
rely on theroxygen2
package (and should not be edited manually).↩︎