Chapter 16 Packages
If you are creating a large/long-term Shiny app, I strongly recommend that you organise your app in the same way you’d organise an R package. This means three things:
All R code lives in an
There’s a function that creates your app (i.e. calls
shinyApp()with your UI and server)
DESCRIPTIONfile exists in the root directory of the app.
This structure gets your toes into the water of package development. It’s a long way from a complete package, but it’s still useful because it activates new tools that make it easier to work with larger amounts of code. The package structure will pay off further when we talk about testing in Chapter 17, because you more tools to make it easy to run the tests and to see what code is tested. In the long run, it also helps you document the pieces of complex apps, although we won’t discuss that in this book.
It’s easy to think of packages as these big complicated artefacts like Shiny, ggplot2, or dplyr. But packages can also be very simple, and at their heart, the key idea of a package is that it’s a set of conventions for organising your code and related artefacts. Here, I’ll start by showing you how to achieve the absolute minimal compliance with the standard, and then provide a few hints as to next steps.
As you start working with apps that use the package structure, you may find that you enjoy the process and want to learn more. I recommend R Packages, and if you want to learn more about the intersection of R packages and Shiny apps, I highly recommend Engineering Shiny, by Colin Fay, Sébastien Rochette, Vincent Guyader, Cervan Girard.
16.1 Converting an existing app
Converting an app to a package requires a small little work. Follow these steps to create an package. I’m assuming that your app is called
myApp and it already lives in a directory called
If it doesn’t exist already, create an
R/and wrap the existing call to
shinyApp()in a function called
If you’re deploying your app (not just running it locally), you’ll need to a add a new
app.Rthat tells the deployment server how you run your app. The easiest way is to load the code with pkgload:
(Inspired by https://engineering-shiny.org/structure.html#deploy).
usethis::use_description()to create a description file. You don’t have to even look at or touch this file (although you need to if you want to make a full package), but you need to have it to activate RStudio’s “package development mode” which provides the keyboard shortcuts we’ll use later.
Remove any calls to
shiny::loadSupport(). The package code loading process now takes care of these.
This gives your app the basic structure of a package, which enables some useful keyboard shortcuts that we’ll talk about next. It’s possible to turn your app in to a “real” package, which means that it passes the automated
R CMD check. That’s not essential, but it can be useful if you’re sharing with others.
Putting your app code into the package structure unlocks a new workflow:
Re-load all code in the app with
cmd + shift + L. This calls
devtools::load_all()which automatically saves all open files,
source()s every file in
R/, then puts your cursor in the console.
Re-run the app with
As your app grows bigger, it’s also worth remembering the keyboard shortcuts you have available to navigate your code:
Ctrl/Cmd + .will open the “fuzzy file and function finder” — type a few letters at the start of the file or function that you want to navigate to, select it with the arrow keys and then press enter. This allows you to quickly jump around your app without taking your hands off the keyboard.
When your cursor is on the name of function
F2will jump to the function definition.
R CMD check
To make a “real” package, you can use
Cmd/Ctrl + Shift + E. That calls
devtools::check() which in turn calls
R CMD check. This is R’s tool for checking compliance to the package standard. Getting
R CMD check to this is quite a lot of work and there’s little pay off in the short term. But in the long-term this will protect you against a number of potential problems, and because it ensures your app adheres to standards that R developers are familiar with, it makes it easier for others to contribute to your app.
I don’t recommend that you do this the first time, the second time, or even the third time you try out the package structure. Instead, I recommend that you get familiar with the basic structure and workflow before you take the next step to make a fully compliant package. It’s also something I’d generally reserve for important apps, particularly any app that will be deployed elsewhere.
Before you make your first full app-package, I recommend read The whole game chapter of R packages: that will give you a sense of the fuller package structure, and basic workflows that you will use. Then use the following hints to get
R CMD check passing cleanly:
Ensure that your app/directory name is a valid package name. A package name can’t contain
Remove any calls to
require()and instead replace them with a declaration in your
usethis::use_package("name")to add the required package to the
DESCRIPTION38. You’ll then need to decide whether you want to refer to each function explicitly with
::, or use
@importFrom packageName functionNameto declare the import in one place.
At a minimum, you’ll need
usethis::use_package("shiny"), and for Shiny apps, I recommend using
@import shinyto make all the functions in the Shiny package easily available. (Using
@importis not generally consider best practice, but it makes sense here).
Pick a license and then use the appropriate
use_license_function to put it in the right place. We don’t currently have a helper function to declare that the package is proprietary but we’re working on it: https://github.com/r-lib/usethis/issues/1163.
If your app contains small reference datasets put it in
inst/extdata. The advantage of
data/is that it’s stored in
.rdaformat, which is faster to load, and is loaded lazily so if it’s not used by the app, it’s not loaded in memory. Alternatively, you can put raw data in
inst/extand load it with
read.csv(system.file("exdata", "mydata.csv", package = "myApp"))or similar.
You can also change your
app.Rto use an the package. This requires that your available somewhere that your deployment machine can install from. For public work this means a CRAN or GitHub package; for private work this means something like RSPM.
The distinction between Imports and Suggests is not generally important for app packages. If you do want to make a distinction, the most useful is to use Imports for packages that need to be present on the deployment machine (in order for the app to work) and Suggests for packages that need to prsent on the development machine in order to develop the app.↩︎