golem shiny app-packages

Building a production-grade shiny app: whole-game

shiny
golem
packages
Author

Martin Frigaard

Published

June 1, 2023

This post walks through building a shiny application using the golem framework. golem is a ’an opinionated framework for building production-grade shiny applications’–I’ll explore some of the opinions (and offer my opinion on adopting these opinions).

Highly recommend

I highly recommend this.

Recommend

I recommend adopting this practice (or something very similar).

Useful

This practice is useful, but maybe not for all applications.

Tip

Additional notes or thoughts on an opinion or practice.

install.packages("devtools")
devtools::install_github("mjfrigaard/gap")

For consistency, I’ll be using the application from the RStudio’s Building Web Applications with Shiny course. These materials are a great resource if you’re new to shiny–even if you’re aren’t, it’s still worth checking out–plus it’s free!

The golem text is also a fantastic resource, but I found myself using the golem website as a great ‘quick reference.’ If you’re unfamiliar with R package development, I recommend bookmarking R packages–this is a great resource you’ll return to often.

Outline

I’ve organized the app-package development process into three areas: Start, Build, and Use.

  • Start covers the required steps to launch your golem project in the RStudio IDE, common R package files and folders, and other setup considerations.

  • Build covers the app-package development process, which includes writing and storing code, data, external resources (like CSS or JavaScript), testing, etc.

  • Use shows how to launch a golem application locally (i.e., within the RStudio IDE), common workflow tips, and anything I found confusing while building the application.

dev/ scripts

New golem apps automatically open the 01_start.R script from the dev/ folder. This is the first of three .R scripts that serve as a ‘guided tour’ of the golem framework (01_start.R, 02_dev.R, and 03_deploy.R):

golem dev/ scripts

The run_dev.R is also in the dev/ folder, but it’s for running a development version of your app (more on this later).

Development scripts: Note

If you’re familiar with R application development, you should recognize most of the items in the dev/ scripts. I recommend going through these scripts even if you’re an experienced R package developer–you can think of these as a ‘shiny app-package development checklist.’

Start

To create a new golem app from the console, enter the following:

install.packages("golem")
library(golem)
golem::create_golem(path = "gap")

If you’re creating a golem app using the New Project Wizard, the following defaults are available:

IDE wizard golem setup
Figure 1: Creating a new golem shiny app
golem comments: Highly recommend

I recommend using golem comments–they’re helpful and don’t change how the application code runs.

When the new project opens, the initial folder structure for your new golem application is below:

show/hide golem folder structure
app-name/
  ├── DESCRIPTION
  ├── NAMESPACE
  ├── R
     ├── app_config.R
     ├── app_server.R
     ├── app_ui.R
     └── run_app.R
  ├── dev
     ├── 01_start.R
     ├── 02_dev.R
     ├── 03_deploy.R
     └── run_dev.R
  ├── [app-name].Rproj
  ├── inst
     ├── app
     │   └── www
     │       └── favicon.ico
     └── golem-config.yml
  └── man
      └── run_app.Rd
  
  7 directories, 14 files

Begin 01_start.R

The dev/01_start.R file is covered in the first few sections of the golem text, but I prefer the package website as a reference because it walks through each dev/ script (with links to the golem functions).

Fill the DESCRIPTION

golem::fill_desc(): Highly recommend

The DESCRIPTION file plays and important role in R packages (probably why it’s the first item in the 01_start.R file).

all a project needs to be a package is a directory of R/ files and a DESCRIPTION file.’ - Packages Chapter of Mastering Shiny

The golem::fill_desc() arguments are a great way to guarantee your DESCRIPTION file is set up correctly.

There are three files in a new golem app-package–NAMESPACE, DESCRIPTION, and [app name].Rproj. dev/01_start.R starts by building the DESCRIPTION file with golem::fill_desc()

golem DESCRIPTION

fill_desc() is from the desc package, and the sections are entered in a key = "value" format

  • Example DESCRIPTION file contents:

    golem::fill_desc(
      pkg_name = "gap",
      pkg_title = "An example goelm app",
      pkg_description = "A working example of the golem package.",
      author_first_name = "Martin",
      author_last_name = "Frigaard",
      author_email = "mjfrigaard@pm.me",
      repo_url = NULL # The URL of the GitHub Repo (optional)
    )
    show/hide output from golem::fill_desc()
     Setting `golem_version` to 0.0.0.9000
     Setting `golem_name` to gap
     DESCRIPTION file modified
What if I need to edit the DESCRIPTION?

In dev/02_dev.R, attachment::att_amend_desc() will ‘Amend DESCRIPTION with dependencies read from package code parsing’. If attachment is not installed, use install.package('attachment')

Set {golem} options

The golem::set_golem_options() wraps a collection of golem‘s ’opinionated’ application development and configuration options.

golem::set_golem_options()
show/hide output from golem::set_golem_options()
── Setting {golem} options in `golem-config.yml` ────────────────────────────────────────────────────────────────────
 Setting `golem_name` to gap
 Setting `golem_wd` to golem::pkg_path()
You can change golem working directory with set_golem_wd('path/to/wd')
 Setting `golem_version` to 0.0.0.9000
 Setting `app_prod` to FALSE
── Setting {usethis} project as `golem_wd` ──────────────────────────────────────────────────────────────────────────
 Setting active project to '/projects/gap'

set_golem_options() is a wrapper for a collection of golem option functions (I’ve included each function and a brief description of their behavior below):

show/hide golem options
golem::set_golem_options(
  golem_name = golem::pkg_name(), # name of the app-package in DESCRIPTION
  golem_version = golem::pkg_version(), # version in DESCRIPTION
  golem_wd = golem::pkg_path(), # package root when starting a golem
  app_prod = FALSE, # production mode?
  talkative = TRUE, # Should the messages be printed to the console?
  config_file = golem::get_current_config(golem_wd) # golem-config.yml location
)

golem config file

The new config file is located in the inst/ folder.

golem::set_golem_options(): Highly recommend

To fully understand the golem framework, I recommended running this function. The functions called for each argument in golem::set_golem_options() provide some insight into how a golem app is structured (i.e., where the name and version information is stored, the location of the root directory, etc.)

Install required dev dependencies

The golem::install_dev_deps() function makes sure the following packages are installed (I’ve grouped them into categories):

Development
  • devtools (loading, documenting, installing package)

    • pkgload (i.e., devtools::load_all())
  • usethis (create package files)

  • roxygen2 (document package functions and objects)

  • pkgbuild (create a .tar.gz file)

Documentation & testing
  • attachment (deal with package dependencies)

  • desc (Parse DESCRIPTION files)

  • testthat (unit testing your code)

Internals
  • rstudioapi (interacting with RStudio IDE)

  • processx (execute and control subprocesses from R)

Files & paths
  • here (file/folder path management)

  • fs (file/folder path management)

Deploy
golem::install_dev_deps()

Below is an example with dockerfiler:

show/hide output from golem::install_dev_deps()
 The package "dockerfiler" is required.
 Would you like to install it?

1: Yes
2: No

Selection: 1
 Updated metadata database: 5.32 MB in 12 files.                         
 Updating metadata database ... done                                     
                                                                            
 Will install 1 package.
 Will download 1 CRAN package (104.29 kB).
+ dockerfiler   0.2.1  ⬇ (104.29 kB)
 Getting 1 pkg (104.29 kB)
 Got dockerfiler 0.2.1 (x86_64-apple-darwin17.0) (104.29 kB)             
 Downloaded 1 package (104.29 kB)in 1.1s                                 
 Installed dockerfiler 0.2.1  (54ms)                                    
 1 pkg + 40 deps: kept 40, added 1, dld 1 (104.29 kB) [20.2s]  
golem::install_dev_deps(): Highly recommned

I recommend using golem’s options here–it’s efficient and let’s you know if a particular package isn’t installed:

Create Common Files

The ‘Create Common Files’ section of dev/01_start.R contains many of the functions and files covered in the ‘Whole Game’ section of R packages:

  • LICENSE

    usethis::use_mit_license("Golem User")
     Adding 'MIT + file LICENSE' to License
     Writing 'LICENSE'
     Writing 'LICENSE.md'
     Adding '^LICENSE\\.md$' to '.Rbuildignore'

golem LICENSE file
  • README.Rmd

    usethis::use_readme_rmd()
     Writing 'README.Rmd'
     Adding '^README\\.Rmd$' to '.Rbuildignore'
     Update 'README.Rmd' to include installation instructions.
    • The README.md is built with devtools::build_readme()

      devtools::build_readme()
       Installing gap in temporary library
       Building /projects/gap/README.Rmd

golem README.Rmd file
  • CODE_OF_CONDUCT.md

    usethis::use_code_of_conduct()
     Writing 'CODE_OF_CONDUCT.md'
     Adding '^CODE_OF_CONDUCT\\.md$' to '.Rbuildignore'
     You may also want to describe the code of conduct in your README:
      ## Code of Conduct
    
    Please note that the gap project is released with a [Contributor Code of
        Conduct](https://contributor-covenant.org/version/2/1/CODE_OF_CONDUCT.html).
    By contributing to this project, you agree to abide by its terms.
      [Copied to clipboard]
    • Paste of the code of conduct in the README.md is rebuild with devtools::build_readme()

      devtools::build_readme()
       Installing gap in temporary library
       Building /projects/gap/README.Rmd

golem code of conduct file
  • Lifecycle badge

    usethis::use_lifecycle_badge("Experimental")
     Adding Lifecycle: experimental badge to 'README.Rmd'
     Re-knit 'README.Rmd' with `devtools::build_readme()`
    • Rebuild the README.md with devtools::build_readme()

      usethis::use_lifecycle_badge("Experimental")
       Installing gap in temporary library
       Building /projects/gap/README.Rmd

golem lifecycle badge
  • NEWS.md

    usethis::use_news_md(open = FALSE)
     Writing 'NEWS.md'

golem NEWS.md file
  • Git: usethis::use_git() will ask if you’d like to commit the files in your golem app to a repo of the same name:

    usethis::use_git()
     Setting active project to '/projects/gap'
     Initialising Git repo
     Adding '.Rproj.user', '.Rhistory', '.Rdata', '.httr-oauth',
      '.DS_Store', '.quarto' to '.gitignore'
    There are 16 uncommitted files:
    * '.gitignore'
    * '.here'
    * '.Rbuildignore'
    * 'CODE_OF_CONDUCT.md'
    * 'DESCRIPTION'
    * 'dev/'
    * 'gap.Rproj'
    * 'inst/'
    * 'LICENSE'
    * 'LICENSE.md'
    * ...
    Is it ok to commit them?
    
    1: Definitely
    2: Negative
    3: Not now
    
    Selection: 1
    • To initialize the Git pane, you’ll need to restart RStudio (in the next dialogue)

       Adding files
       Making a commit with message 'Initial commit'
       A restart of RStudio is required to activate the Git pane
      Restart now?
      
      1: Absolutely
      2: Negative
      3: No
      Selection: 1
Common files: Recommend

Each of these files are important for an R package, so having them consolidated in 01_start.R makes it easier to get up and running quickly. However, if you’d like to edit their contents before moving onto the next step (or you’d like information on a particular function/file), I recommend consulting R packages for this section

Init Testing Infrastructure

golem::use_recommended_tests() with set up the testthat architecture for unit tests.

golem::use_recommended_tests()

golem test files
 Setting active project to '/projects/gap'
 Adding 'testthat' to Suggests field in DESCRIPTION
 Adding '3' to Config/testthat/edition
 Creating 'tests/testthat/'
 Writing 'tests/testthat.R'
 Call `use_test()` to initialize a basic test file and open it for editing

It also adds a few words to the WORDLIST file in the inst folder:

 Adding 'spelling' to Suggests field in DESCRIPTION
 Adding 'en-US' to Language
The following words will be added to the wordlist:
 - Lifecycle
 - goelm
 - golem
Are you sure you want to update the wordlist?
1: Yes
2: No

Selection: 1
Added 3 and removed 0 words in /projects/gap/inst/WORDLIST
Updated /projects/gap/tests/spelling.R
 Run `devtools::check()` to trigger spell check
 Tests added

golem::use_recommended_tests() also provides some examples for testing the UI, server, and other golem functions:

show/hide unit tests in test-golem-recommended.R
test_that("app ui", {
  ui <- app_ui()
  golem::expect_shinytaglist(ui)
  # Check that formals have not been removed
  fmls <- formals(app_ui)
  for (i in c("request")) {
    expect_true(i %in% names(fmls))
  }
})

test_that("app server", {
  server <- app_server
  expect_type(server, "closure")
  # Check that formals have not been removed
  fmls <- formals(app_server)
  for (i in c("input", "output", "session")) {
    expect_true(i %in% names(fmls))
  }
})

test_that(
  "app_sys works",
  {
    expect_true(
      app_sys("golem-config.yml") != ""
    )
  }
)

test_that(
  "golem-config works",
  {
    config_file <- app_sys("golem-config.yml")
    skip_if(config_file == "")

    expect_true(
      get_golem_config(
        "app_prod",
        config = "production",
        file = config_file
      )
    )
    expect_false(
      get_golem_config(
        "app_prod",
        config = "dev",
        file = config_file
      )
    )
  }
)

# Configure this test to fit your need.
# testServer() function makes it possible to test code in server functions and modules, without needing to run the full Shiny application
testServer(app_server, {

  # Set and test an input
  session$setInputs(x = 2)
  expect_equal(input$x, 2)

  # Example of tests you can do on the server:
  # - Checking reactiveValues
  # expect_equal(r$lg, 'EN')
  # - Checking output
  # expect_equal(output$txt, "Text")
})

# Configure this test to fit your need
test_that(
  "app launches",
  {
    golem::expect_running(sleep = 5)
  }
)

These tests pass right out of the box, and they give a little ‘sneak preview’ of how the golem framework works.

show/hide results from unit tests in test-golem-recommended.R
==> Testing R file using 'testthat'

 Loading gap
[ FAIL 0 | WARN 0 | SKIP 0 | PASS 9 ]
Loading required package: shiny
[ FAIL 0 | WARN 0 | SKIP 1 | PASS 10 ]

── Skipped tests (1) ─────────
 interactive() is not TRUE
  (1):
  test-golem-recommended.R:72:5


Test complete
golem recommended tests: Highly recommend

I love this feature of golem apps! in a single function I have the folders required for unit testing, words added to the WORDLIST (which I almost never remember to do), and an example test with shiny’s testServer() function.

Favicon

A favicon is a the little image that shows up on your browser tab or address bar. golem has a default favicon in the inst/app/ folder:

golem favicon file
inst/
  └── app
       └── www
            └── favicon.ico


3 directories, 1 file

The inst/ folder serves a specific purpose in golem apps (and R packages), which I’ll cover more below. For our purpose, the golem::use_favicon() function can use the existing image:

golem::use_favicon(path = "inst/app/www/favicon.ico")

The output introduces another common golem function: golem_add_external_resources()

 favicon.ico created at 
/projects/gap/inst/app/www/favicon.ico
Favicon is automatically linked in app_ui via `golem_add_external_resources()`

This function is used to add external resources to your application (and will come up often during development).

Add helper functions

Most applications will have two types of code–shiny functions for running your application (app functions), and functions that do everything else (or utility functions). The golem framework further distinguishes utility functions into two types: 1) “small functions that are reused throughout the app” (with a utils_ prefix), and 2) “larger functions, which are more central to the application” (with a fct_ prefix).

Documenting & exporting functions: NOTE

The functions in golem_utils_ui.R and golem_utils_server.R are created with the @noRd tag, which prevents the .Rd file generation. If you’d like the functions available outside of the package, you can use @export. If you’d like ‘a manual page created but not present in the function index’, use @keywords internal. See the examples I’ve created here and here

The descriptions below are from the text and provide examples for the two types of utility functions:

utils_ functions:…the hexmake app has two of these files, R/utils_ui.R and R/utils_server.R, in which you will find small functions that are reused throughout the app.

fct_ functions:…in hexmake, you will find R/fct_mongo.R, which is used to handle all the things related to connecting and interacting with the Mongodb database.

use_utils_ui()

The golem::use_utils_ui() function will add a collection of utility functions for the UI. Including with_test = TRUE will add a test for these functions.

golem UI utility functions & tests
golem::use_utils_ui(with_test = TRUE)
 File created at /projects/gap/R/golem_utils_ui.R
 Utils UI added
 File created at /projects/gap/tests/testthat/test-golem_utils_ui.R
 Tests on utils_server added
use_utils_server()

golem also includes a set of functions for the application server (or server modules), golem::use_utils_server(). The with_test = TRUE will also add a test to the tests/testthat/ folder:

golem::use_utils_server(with_test = TRUE)
 File created at /projects/gap/R/golem_utils_server.R
 Utils server added
 File created at /projects/gap/tests/testthat/test-golem_utils_server.R
 Tests on utils_server added

golem server utility functions & tests
golem utility functions: Recommend

I consider these functions optional, but the examples in each file have a broad enough application that you’ll probably discover something helpful for your golem application. The with_test argument also provides more examples of unit tests for your application functions, so you’re likely to find something useful!

End 01_start.R

This is the final function in the dev/01_start.R file. In the next golem dev script (dev/02_dev.R), I’ll cover development of your golem application.

Build

The dev/02_dev.R file is appropriately titled, ‘Engineering’, and unlike the functions in the first script, these functions will be used repeatedly for creating files in the R/ and inst/ folders.

Begin 02_dev.R

App files

Let’s start by examining the contents of the R/ folder in our new golem application:

golem application files
R/
├── app_config.R
├── app_server.R
├── app_ui.R
└── run_app.R

1 directory, 4 files

golem applications structure shiny apps into three files: R/app_ui.R, R/app_server.R, and R/run_app.R.

  • The R/app_ui.R and R/app_server.R scripts are golem’s version of ui.R and server.R

  • R/run_app.R is a standalone app function, and

  • R/app_config.R is used to set/get golem configuration settings (which we will cover more below).

app_ui.R

app_ui.R wraps the UI functions in shiny::tagList() (you’ll see this function in shiny UI module functions, too).

show/hide app_ui()
app_ui <- function(request) {
  tagList(
    # Leave this function for adding external resources
    golem_add_external_resources(),
    # Your application UI logic
    fluidPage(
      h1("gap")
    )
  )
}

app_ui() also contains a call to golem_add_external_resources(), which we used above to add the favicon image.

show/hide golem_add_external_resources()
golem_add_external_resources <- function() {
  add_resource_path(
    "www",
    app_sys("app/www")
  )

  tags$head(
    favicon(),
    bundle_resources(
      path = app_sys("app/www"),
      app_title = "gap"
    )
    # Add here other external resources
    # for example, you can add shinyalert::useShinyalert()
  )
}
app_server.R

The contents of app_server.R file looks similar to a standard shiny server function:

show/hide app_server()
app_server <- function(input, output, session) {
  # Your application server logic
}
app_config.R

The app_config.R file contains the “internal mechanics for golem, notably for referring to values in the inst/ folder, and to get values from the config file in the inst/ folder”. Two functions drive the internal mechanics of your golem app: app_sys() and get_golem_config()

  • app_sys() is a wrapper around the system.file() function, and it’s used to “quickly refer to the files inside the inst/ folder”
show/hide app_sys()
app_sys <- function(...) {
  system.file(..., package = "gap")
}
  • get_golem_config() is where you’ll set golem configuration options (covered here in the text).
show/hide get_golem_config()
get_golem_config <- function(
  value,
  config = Sys.getenv(
    "GOLEM_CONFIG_ACTIVE",
    Sys.getenv(
      "R_CONFIG_ACTIVE",
      "default"
    )
  ),
  use_parent = TRUE,
  # Modify this if your config file is somewhere else
  file = app_sys("golem-config.yml")
) {
  config::get(
    value = value,
    config = config,
    file = file,
    use_parent = use_parent
  )
}

get_golem_config() reads the inst/golem-config.yml configuration file:

default:
  golem_name: gap
  golem_version: 0.0.0.9000
  app_prod: no
production:
  app_prod: yes
dev:
  golem_wd: !expr here::here()

golem-config.yml gives me access to the app version, name, and (development) working directory. This file is designed to add “production-only elements” and be “shareable across golem projects

run_app.R

run_app.R is the exported function I’ll use to run my golem app after loading/documenting/installing the package:

devtools::load_all(".")
 Loading gap
devtools::document()
 Updating gap documentation
 Loading gap

Restarting R session...
library(gap)
gap::run_app()
R/app_config.R: Tip

The great thing about golem applications is that despite having a somewhat overwhelming amount of code and options, most of these can be ignored until you need to use them. R/app_config.R is a great example of this. The get_golem_config() function is a powerful tool for deploying apps in production, but you can get started developing your application without diving into the details.

Dependencies

Dependency management is a necessary evil of package development. shiny has a large ecosystem of user-written add-on packages. To use the code from add-on packages in our application, we need a way to keep track of which function belongs to which package.

golem app dependencies

The DESCRIPTION file manages package-level dependencies. The Imports field in the DESCRIPTION file specifies packages that my package uses, so the functions from these packages will be available for my package, but not for users (unless they use the :: operator or load the package themselves).

The NAMESPACE file manages function-level access. The NAMESPACE file manages the functions that are exported from my package (i.e., functions available to users who install my package), and the functions my package imports from other packages.

The golem text describes the difference between these files in the following way,

The DESCRIPTION file dictates which packages have to be installed when your application is installed

The NAMESPACE file describes how your app interacts with the R session at run time, i.e. when your application is launched

The attachment package makes it easier to manage the dependencies in your golem application. It does this by looking through the files in your package to make sure everything is properly documented in the NAMESPACE and DESCRIPTION file (note that these two files are not equivalent or connected).

The att_amend_desc() function removes a lot of the tedium involved in dependency management:

attachment::att_amend_desc()

This function adds the appropriate parameters to golem-config.yml and sets up function documentation in the DESCRIPTION file

Saving attachment parameters to yaml config file
Updating [app-name] documentation
Setting `RoxygenNote` to "7.2.3"

It loads the contents of our package (i.e. devtools::load_all()) and writes the NAMESPACE file

 Loading [app-name]
Writing NAMESPACE

It also writes the help files in the man/ folder.

Writing run_app.Rd
golem dependencies: Highly recommend

Managing the dependencies in your app is an essential part of getting your application to deploy in a production environment, and this is a very helpful addition to your package development workflow (whether you’re building an app package or standard R package).

Add modules

golem has functions for quickly creating modules and utility/helper functions in the R/ folder.

add_module()
golem::add_module(name = "name_of_module1", with_test = TRUE) 
golem::add_module(name = "name_of_module2", with_test = TRUE) 

Add helper functions

golem apps differentiates two types of helper functions: uils_ and fct_.

add_utils()

uils_ functions:small functions that might be used several times in the application” … “more ‘topic centered’, in the sense that they gather functions that relate to a specific feature of the application(+

golem::add_utils("helpers", with_test = TRUE)
add_fct()

fct_ functions:larger functions that are more central to the application” … “more used as a place to put miscellaneous functions

golem::add_fct("helpers", with_test = TRUE)

with_test = TRUE ensures these functions will also create test files in tests/

golem::add_ functions
golem modules and helper functions: Highly recommend

These functions are incredibly helpful for a variety of reasons: 1) they simplify the naming convention by automatically creating prefixes for modules (mod_[name]_ui/mod_[name]_server) and helper functions (utils_/fct_), 2) the with_test argument makes it easy to create an accompanying test file, which ensures you have tests for each function/module (and promotes test-driven development!).

External resources

dev/02_dev.R includes golem wrappers for including CSS, JavaScript, and SASS files to the inst/app/www/ folder:

JavaScript files

You can add JavaScript to your application using the golem::add_js_file("script") and golem::add_js_handler("handlers") functions.

The golem text has an entire chapter dedicated to JavaScript (which is worth reading).

App styling

You can add CSS or SASS styling to your application using the golem::add_css_file("custom") and golem::add_sass_file("custom") functions, too.

golem external resources
golem external resources: Recommend

It’s unlikely a production-grade shiny application only includes R code, and figuring out how to add other code files to your application can get complicated quickly. These functions are incredibly helpful (and they work outside of a golem applications!).

Add internal datasets

If you application uses data, you can add it to your application with the usethis functions (use_data_raw() or use_data()). I recommend reading the data section of R packages (and this section on adding data to inst/extdata).

golem app data

Locations of data in golem app-packages

inst/extdata

External data you’d like to make available in your package should be stored in inst/extdata/

This is where I’ve placed the movies file:

inst/extdata/
└── movies.RData

1 directory, 1 file
use_data_raw()

The data-raw/ folder is for the ‘data-creating script’ that was used to create the version of the data in your app-package:

  • I’ll create a raw data file for movies.
usethis::use_data_raw("movies")
  • Newly created .R scripts in use_data_raw() will have a call to use_data(). See below:

     Writing 'data-raw/movies.R'
     Modify 'data-raw/movies.R'
     Finish the data preparation script in 'data-raw/movies.R'
     Use `usethis::use_data()` to add prepared data to package
use_data()

The objects created from the .R files in data-raw/ are stored in the data/ folder (and any other data you need in your app-package).

store R objects and make them available to the user…in data/

For example, in data-raw/movies.R, I load the movies data from inst/extdata/, and save movies to the data/ folder:

## code to prepare `movies` dataset goes here
load("inst/extdata/movies.RData")
usethis::use_data(movies, overwrite = TRUE)
  • The output is below:

     Saving 'movies' to 'data/movies.rda'
     Document your data (see 'https://r-pkgs.org/data.html')

Follow this guide to document your datasets.

show/hide movies data documentation
#' Movies dataset
#'
#' A dataset containing movie rankings from IMDB and Rotten Tomatoes. Original
#' source found [here](https://rstudio-education.github.io/shiny-course/)
#'
#' @format A data frame with 651 rows and 34 variables:
#'
#' \describe{
#'   \item{title}{movie title}
#'   \item{title_type}{title type (Documentary, Feature Film, TV, Movie)}
#'   \item{genre}{movie genre (Action & Adventure, Animation,
#'                Art House & International, Comedy, Documentary, Drama, Horror,
#'                Musical & Performing Arts, Mystery & Suspense, Other,
#'                Science Fiction & Fantasy)}
#'   \item{runtime}{length of film (in minutes)}
#'   \item{mpaa_rating}{G, NC-17, PG, PG-13, R, Unrated}
#'   \item{studio}{studio movie was filmed in}
#'   \item{thtr_rel_date}{Theatre release date}
#'   \item{thtr_rel_year}{Theatre release year}
#'   \item{thtr_rel_day}{Theatre release day}
#'   \item{dvd_rel_date}{DVD release date}
#'   \item{dvd_rel_year}{DVD release year}
#'   \item{dvd_rel_month}{DVD release month}
#'   \item{dvd_rel_day}{DVD release day}
#'   \item{imdb_rating}{IMDB rating}
#'   \item{imdb_num_votes}{IMDB number of votes}
#'   \item{critics_rating}{Critics rating}
#'   \item{critics_score}{Critics score}
#'   \item{audience_rating}{Audience rating}
#'   \item{audience_score}{Audience score}
#'   \item{best_pic_nom}{Best picture nomination?}
#'   \item{best_pic_win}{Best picture win?}
#'   \item{best_actor_win}{Best actor win?}
#'   \item{best_actress_win}{Best actrss win?}
#'   \item{best_dir_win}{Best director win?}
#'   \item{top200_box}{Top 200 box-office?}
#'   \item{director}{Film director name}
#'   \item{actor1}{Actor 1 name}
#'   \item{actor2}{Actor 2 name}
#'   \item{actor3}{Actor 3 name}
#'   \item{actor4}{Actor 4 name}
#'   \item{actor5}{Actor 5 name}
#'   \item{imdb_url}{IMDB website url}
#'   \item{rt_url}{Rotten Tomatoes website url}
#' }
"movies"

The fivethirtyeight package also has great examples of documented datasets.

After loading and documenting your package, you can view structure and variable names of the movies data by entering ??movies in the Console:

(a) movies data documentation
Figure 2: Documentation for gap::movies stored in R/data.R

Tests

The tests/ folder was created in dev/01_start.R with golem::use_recommended_tests(). This function is a wrapper around usethis::use_testthat(), and it sets up the tests/ folder:

tests
├── spelling.R
├── testthat/
└── testthat.R

In dev/02_dev.R, the golem::add_module() and golem::add_utils()/golem::add_fct() functions also include a with_test = TRUE argument, which creates a test file in the tests/ folder.

These functions add two test files in tests/testthat/:

tests/testthat
├── test-golem-recommended.R
├── test-golem_utils_server.R
└── test-golem_utils_ui.R

1 directory, 3 files

These files test the functions in the files golem_utils_ui.R and golem_utils_server.R, and we can run them independently with testthat::test_file()

test_file("tests/testthat/test-golem_utils_server.R")
[ FAIL 0 | WARN 0 | SKIP 0 | PASS 13 ]
test_file("tests/testthat/test-golem_utils_ui.R")
[ FAIL 0 | WARN 0 | SKIP 0 | PASS 51 ]

Documentation

R package vignettes contain high-level, long-form documentation for your package. These R Markdown documents combine narrative text and code that describe how the ‘parts’ (functions, data, etc.) of the package behave. In app-packages, vignettes might included the following information:

For shiny app-packages, the following types of documentation might be included in the vignettes:

  1. Introduction to the package

  2. Installation guide

  3. Usage examples

  4. Description of the application and modules

  5. Application workflow

  6. Data preparation

  1. Troubleshooting

  2. FAQs

  3. Advanced usage

  4. Details on functions and datasets

  5. References and Contact information

To create a new vignette, run usethis::use_vignette("NAME OF VIGNETTE"):

usethis::use_vignette("gap")
 Setting active project to '/Users/mjfrigaard/projects/gap'
 Adding 'knitr' to Suggests field in DESCRIPTION
 Adding 'rmarkdown' to Suggests field in DESCRIPTION
 Adding 'knitr' to VignetteBuilder
 Adding 'inst/doc' to '.gitignore'
 Creating 'vignettes/'
 Adding '*.html', '*.R' to 'vignettes/.gitignore'
 Writing 'vignettes/gap.Rmd'
 Modify 'vignettes/gap.Rmd'

The vignette file opens with the title matching the argument passed to use_vignette(). To build the vignettes in your package, run:

devtools::build_vignettes()
golem package vignettes

As described in ‘Rmd, Vignettes, and documentation first’, R markdown vignettes are a perfect sandbox for building and testing shiny apps.

‘the good news is that when checking a package, i.e. when running check() from devtools (Wickham, Hester, and Chang 2020) or R CMD check, the Vignettes are going to be built, and the process will fail if at least one of the Vignettes fails to render. That way, you can use the documentation of the back-end as an extra tool for doing unit testing!’

‘One radical approach to the ’Rmd first’ philosophy is to write everything in an Rmd from the very beginning of your project: write the function code, their roxygen tags, their tests, etc., then move everything to the correct spot in the package infrastructure once you are happy with everything. And of course, when you need to add another feature to your app, open a new markdown and start the process of development and documentation again’

Code Coverage

Test code coverage measures the extent to which the test cases cover the possible execution paths in the package codebase–its a way to ensure that the tests are robust enough to verify that the code behaves as expected.

There are two functions/methods used to calculate code coverage in your application: usethis::use_coverage() and covrpage::covrpage().

usethis::use_coverage()

use_coverage() is part of the usethis package and can be run interactively during development:

usethis::use_coverage()
✔ Setting active project to '/Users/mjfrigaard/projects/gap'
covrpage::covrpage()

Install covrpage using the following:

# install.packages("remotes")
# remotes::install_github('yonicd/covrpage', 
#   force = TRUE, quiet = TRUE)
library(covrpage)

To use covrpage, run the following:

covrpage::covrpage()
covrpage::covrpage()

This sets up the README in the tests/ folder.

tests/
├── README.md <- covrpage README!
├── spelling.R
├── testthat
│   ├── test-golem-recommended.R
│   ├── test-golem_utils_server.R
│   └── test-golem_utils_ui.R
└── testthat.R

The test coverage vignette is created with use_covrpage_vignette()

covrpage::use_covrpage_vignette()
copying tests_and_coverage.Rmd into ./vignettes
adding inst/doc to .gitignore
adding knitr,rmarkdown to Suggests field in ./DESCRIPTION
adding VignetteBuilder: knitr to ./DESCRIPTION

You can view the covrpage for this app here

CI

Continuous integration can be handled with one of the GitHub Actions functions (make sure you’re using Git). See the usethis website for more information on using GitHub Actions.

If you’re using another CI management system, the following options are available.

Github Actions CI
  • Set up GitHub actions

    # GitHub Actions
    usethis::use_github_action()
    # Chose one of the three
    usethis::use_github_action_check_release()
    usethis::use_github_action_check_standard()
    usethis::use_github_action_check_full()
    # Add action for PR
    usethis::use_github_action_pr_commands()
Other CI Options
  • Travis CI

    usethis::use_travis()
    usethis::use_travis_badge()
  • AppVeyor

    usethis::use_appveyor()
    usethis::use_appveyor_badge()
  • Circle CI

    usethis::use_circleci()
    usethis::use_circleci_badge()
  • Jenkins

    usethis::use_jenkins()
  • GitLab CI

    usethis::use_gitlab_ci()

End 02_dev.R

This concludes the 02_dev.R file. It’s likely you’ll return to this file repeatedly for various functions during development, so I’d leave it in the dev/ folder for future reference.

Use

In this section, I’ll go over the functions used during application development, how to launch the application (locally, in the IDE), and the third and final dev/ script (dev/03_deploy.R), which is full of options for deploying your shiny app.

Writing code

While developing, I find the add_* functions are incredibly helpful (add_module(), add_utils(), and add_fct()). New modules functions can be created with golem::add_module("name") along with their tests and utility functions.

add_module(name = "plot", utils = "server", with_test = TRUE)
 File created at R/mod_plot.R
 File created at R/mod_plot_utils_server.R
 File created at tests/testthat/test-mod_plot.R
  • The functions added to the R/ folder include @noRd by default (which must be removed create the .Rd files in the man/ folder)

    # UI module template -------------------
    #' test UI Function
    #'
    #' @description A shiny Module.
    #'
    #' @param id,input,output,session Internal parameters for {shiny}.
    #'
    #' @noRd <- this one!
    #'
    #' @importFrom shiny NS tagList
    # server module template ---------------
    #' test Server Functions
    #'
    #' @noRd <- and this one!
  • UI module functions end with a _ui suffix:

    show/hide mod_plot_ui()
    #' plot UI Function
    #'
    #' @param id
    #'
    #' @return shiny UI module
    #' @export mod_plot_ui
    #'
    #' @importFrom shiny NS tagList tags
    #' @importFrom shiny plotOutput verbatimTextOutput
    mod_plot_ui <- function(id) {
      ns <- shiny::NS(id)
      shiny::tagList(
        shiny::tags$br(),
        shiny::tags$blockquote(
          shiny::tags$em(
            shiny::tags$h6(
              "The code for this application comes from the ",
              shiny::tags$a("Building web applications with Shiny",
                href = "https://rstudio-education.github.io/shiny-course/"
              ),
              "tutorial"
            )
          )
        ),
        shiny::plotOutput(outputId = ns("scatterplot"))
      )
    }
  • Server module functions end with a _server suffix:

    show/hide mod_plot_server()
    #' plot Server Functions
    #'
    #' @param id module id
    #' @param var_inputs inputs from mod_var_input
    #'
    #' @return shiny server module
    #' @export mod_plot_server
    #'
    #' @importFrom shiny NS moduleServer reactive
    #' @importFrom tools toTitleCase
    #' @importFrom shiny renderPlot
    #' @importFrom stringr str_replace_all
    #' @importFrom ggplot2 labs theme_minimal theme
    mod_plot_server <- function(id, var_inputs) {
      shiny::moduleServer(id, function(input, output, session) {
        movies <- gap::movies
    
        inputs <- shiny::reactive({
          plot_title <- tools::toTitleCase(var_inputs$plot_title())
          list(
            x = var_inputs$x(),
            y = var_inputs$y(),
            z = var_inputs$z(),
            alpha = var_inputs$alpha(),
            size = var_inputs$size(),
            plot_title = plot_title
          )
        })
    
        output$scatterplot <- shiny::renderPlot({
          plot <- point_plot(
            df = movies,
            x_var = inputs()$x,
            y_var = inputs()$y,
            col_var = inputs()$z,
            alpha_var = inputs()$alpha,
            size_var = inputs()$size
          )
          plot +
            ggplot2::labs(
              title = inputs()$plot_title,
              x = stringr::str_replace_all(tools::toTitleCase(inputs()$x), "_", " "),
              y = stringr::str_replace_all(tools::toTitleCase(inputs()$y), "_", " ")
            ) +
            ggplot2::theme_minimal() +
            ggplot2::theme(legend.position = "bottom")
        })
      })
    }
    
    ## To be copied in the UI
    # mod_plot_ui("plot_1")
    
    ## To be copied in the server
    # mod_plot_server("plot_1")

See the utility function and other module in the gap app-package on GitHub below:

  1. R/mod_var.R
  2. R/mod_plot_utils_server.R
  • Include tests for new modules and functions using the with_test = TRUE argument

    tests/testthat/
                ├── _snaps
                ├── test-golem-recommended.R
                ├── test-golem_utils_server.R
                ├── test-golem_utils_ui.R
                ├── test-mod_plot.R
                ├── test-mod_plot_utils_server.R
                └── test-mod_var_input.R
    
    2 directories, 6 files
Module names: tip

Including mod in the name of module scripts and functions makes it easier to separate them from other functions in my package namespace, if I’m using tab-completion, or if I’m searching for a particular file using Ctrl + .:

Figure 3: Go to File/Function in RStudio

The updated covrpage report is available here

Adding resources

If I want to include other files (like images), I can add these files to inst/app/www/,

inst/app/www

# add icon
inst/app
      └── www/
           └── shiny.png

Then I can include the path in the UI (see example below):

# add icon
shiny::tags$img(src = "www/shiny.png")

addResourcePath()

If I wanted to include images in their own folder (like images/), I can use golem::addResourcePath() to add the name of the sub-folder to inst/app/

# add icon
golem::add_resource_path(
          prefix = 'images', 
          directoryPath = system.file('app/images',
                          package = 'gap'))

Now I can add the image file to the inst/app/www/images/ folder and include the following code in the UI:

# add icon
shiny::tags$img(src = "www/images/golem-hex.png")
  • In R/app_ui.R, the app_ui() function contains the UI layout functions (fluidPage(), sidebarLayout(), etc.), and a call to golem_add_external_resources():

    show/hide app_ui()
    #' The application User-Interface
    #'
    #' @param request Internal parameter for `{shiny}`.
    #'     DO NOT REMOVE.
    #' @import shiny
    #' @keywords internal
    app_ui <- function(request) {
      shiny::tagList(
        # Leave this function for adding external resources
        golem_add_external_resources(),
        # Your application UI logic
        shiny::fluidPage(
          shiny::tags$h1("gap"),
          shiny::sidebarLayout(
            shiny::sidebarPanel(
              mod_var_input_ui("vars")
            ),
            shiny::mainPanel(
              # add shiny hex in www/
              shiny::tags$img(src = "www/shiny.png"),
              mod_plot_ui("plot"),
              # add golem hex (in www/images/)
              shiny::fluidRow(
                shiny::tags$em(shiny::tags$h4(
                  "Brought to you by: ",
                  shiny::tags$img(src = "www/images/golem-hex.png")
                ))
              )
            )
          )
        )
      )
    }

    The golem_add_external_resources() function is below:

    golem_add_external_resources()
    # this is also included in the app_ui.R script
    golem_add_external_resources <- function() {
      add_resource_path(
        "www",
        app_sys("app/www")
      )
      tags$head(
        favicon(),
        bundle_resources(
          path = app_sys("app/www"),
          app_title = "gap"
        )
        # Add here other external resources
        # for example, you can add shinyalert::useShinyalert()
      )
    }

Now when I run devtools::load_all(), devtools::document(), install/restart, and load the package, I see the images properly rendered with the application:

golem_add_external_resources(): Highly recommend

golem app-packages take advantage of the inst/ folder to load external resources. Using golem_add_external_resources() helps ensure the external resources are added to the application and loaded when the application is deployed.

Begin 03_deploy.R

The final step in the guided tour contains functions for deploying a new application to Posit Connect or Docker (it opens automatically after completing the dev/02_dev.R)

Run checks

These functions are part of the package development process. devtools::check() should be run frequently (I run it after creating a new .R file or creating a new test).

If you plan on submitting a package to CRAN, the rhub::check_for_cran() function will create a ‘to-do’ list of CRAN comments.

## Run checks ----
## Check the package before sending to prod
devtools::check()
rhub::check_for_cran()
  • devtools::check(): “It’s counter-intuitive but the key to minimizing this pain is to run R CMD check more often: the sooner you find a problem, the easier it is to fix” - R packages

  • The outputs from the initial devtools::check() are below (I’ve split it up into sections)

    • Documenting
    show/hide check() Documenting output
    ══ Documenting ═══════════════════════════════════════════════════════════
     Updating gap documentation
     Loading gap
    
    ══ Building ═════════════════════════════════════════════════════════════
    Setting env vars:
     CFLAGS    : -Wall -pedantic -fdiagnostics-color=always
     CXXFLAGS  : -Wall -pedantic -fdiagnostics-color=always
     CXX11FLAGS: -Wall -pedantic -fdiagnostics-color=always
     CXX14FLAGS: -Wall -pedantic -fdiagnostics-color=always
     CXX17FLAGS: -Wall -pedantic -fdiagnostics-color=always
     CXX20FLAGS: -Wall -pedantic -fdiagnostics-color=always
    ── R CMD build ──────────────────────────────────────────────────────────
      checking for file '/projects/apps/gap/DESCRIPTION' ...
      preparing ‘gap’: (561ms)
      checking DESCRIPTION meta-information ...
      installing the package to build vignettes
      creating vignettes (5.6s)
      excluding invalid files
       Subdirectory 'R' contains invalid file names:
         ‘_disable_autoload.R’
      checking for LF line-endings in source and make files and shell scripts
      checking for empty or unneeded directories
      building ‘gap_0.0.0.9000.tar.gz’
    • Checking
    show/hide check() Checking output
    ══ Checking ══════════════════════════════════════════════════════════════
    Setting env vars:
     _R_CHECK_CRAN_INCOMING_REMOTE_               : FALSE
     _R_CHECK_CRAN_INCOMING_                      : FALSE
     _R_CHECK_FORCE_SUGGESTS_                     : FALSE
     _R_CHECK_PACKAGES_USED_IGNORE_UNUSED_IMPORTS_: FALSE
     NOT_CRAN                                     : true
    ── R CMD check ───────────────────────────────────────────────────────────
      using log directory ‘/private/var/folders/0x/x5wkbhmx0k74tncn9swz7xpr0000gn/T/RtmpYiEbLJ/file75936b43aefb/gap.Rcheck’
      using R version 4.2.3 (2023-03-15)
      using platform: x86_64-apple-darwin17.0 (64-bit)
      using session charset: UTF-8
      using options ‘--no-manual --as-cran’
      checking for file ‘gap/DESCRIPTION’
      this is package ‘gap’ version ‘0.0.0.9000’
      package encoding: UTF-8
      checking package namespace information
      checking package dependencies (2s)
      checking if this is a source package ...
      checking if there is a namespace
      checking for executable files (592ms)
      checking for hidden files and directories
      checking for portable file names
      checking for sufficient/correct file permissions
      checking whether package ‘gap’ can be installed (5s)
      checking installed package size
      checking package directory ...
      checking for future file timestamps ...
      checking ‘build’ directory
      checking DESCRIPTION meta-information ...
      checking top-level files ...
      checking for left-over files
      checking index information ...
      checking package subdirectories ...
      checking R files for non-ASCII characters ...
      checking R files for syntax errors ...
      checking whether the package can be loaded (1.2s)
      checking whether the package can be loaded with stated dependencies (1s)
      checking whether the package can be unloaded cleanly (998ms)
      checking whether the namespace can be loaded with stated dependencies (991ms)
      checking whether the namespace can be unloaded cleanly (1.2s)
      checking dependencies in R code (1.1s)
      checking S3 generic/method consistency (2.1s)
      checking replacement functions (1.1s)
      checking foreign function calls (1.1s)
      checking R code for possible problems (6.8s)
      checking Rd files (406ms)
      checking Rd metadata ...
      checking Rd line widths ...
      checking Rd cross-references ...
      checking for missing documentation entries (1.2s)
    W  checking for code/documentation mismatches (2.4s)
       Data codoc mismatches from documentation object 'movies':
       Variables in data frame 'movies'
         Code: actor1 actor2 actor3 actor4 actor5 audience_rating
               audience_score best_actor_win best_actress_win best_dir_win
               best_pic_nom best_pic_win critics_rating critics_score director
               dvd_rel_date dvd_rel_day dvd_rel_month dvd_rel_year genre
               imdb_num_votes imdb_rating imdb_url mpaa_rating rt_url runtime
               studio thtr_rel_date thtr_rel_day thtr_rel_month thtr_rel_year
               title title_type top200_box
         Docs: actor1 actor2 actor3 actor4 actor5 audience_rating
               audience_score best_actor_win best_actress_win best_dir_win
               best_pic_nom best_pic_win critics_rating critics_score director
               dvd_rel_date dvd_rel_day dvd_rel_month dvd_rel_year genre
               imdb_num_votes imdb_rating imdb_url mpaa_rating rt_url runtime
               studio thtr_rel_date thtr_rel_day thtr_rel_year title
               title_type top200_box
    
      checking Rd \usage sections (3.4s)
      checking Rd contents ...
      checking for unstated dependencies in examples ...
      checking contents of ‘data’ directory ...
      checking data for non-ASCII characters ...
      checking LazyData
      checking data for ASCII and uncompressed saves ...
      checking installed files from ‘inst/doc’
      checking files in ‘vignettes’ ...
      checking examples (2.4s)
      checking examples with --run-donttest (2.8s)
      checking for unstated dependencies in ‘tests’ ...
      checking tests ...
    • spelling.R
    show/hide check() spelling.R
      Running ‘spelling.R’
    X  Comparing ‘spelling.Rout’ to ‘spelling.Rout.save’ ...
       6,47d5
       < Potential spelling errors:
       <   WORD                   FOUND IN
       < CONFIG                 get_golem_config.Rd:17
       < Codecov                README.md:11
       < Config                 get_golem_config.Rd:5,25
       < HTLM                   with_red_star.Rd:10
       < IMDB                   movies.Rd:27,28,45,53
       < Theatre                movies.Rd:20,21,22
       < UI                     mod_plot_ui.Rd:5,13,16
       <                        mod_var_ui.Rd:5,13
       < Ventura                tests_and_coverage.Rmd:109
       < ackage                 README.md:6
       < actrss                 movies.Rd:36
       < br                     rep_br.Rd:5,10,13,16
       <                        tests_and_coverage.Rmd:82
       < config                 app_sys.Rd:15
       <                        get_golem_config.Rd:15,20,22
       <                        tests_and_coverage.Rmd:39,89
       < covr                   tests_and_coverage.Rmd:29,116
       < covrpage               tests_and_coverage.Rmd:24,117
       < darwin                 tests_and_coverage.Rmd:108
       < enurl                  tests_and_coverage.Rmd:83
       < goelm                  title:1
       < jq                     tests_and_coverage.Rmd:81
       < jquery                 jq_hide.Rd:5,13
       < li                     tests_and_coverage.Rmd:75,77
       < macOS                  tests_and_coverage.Rmd:109
       < na                     not_in.Rd:5,15
       <                        not_na.Rd:5,13
       <                        tests_and_coverage.Rmd:69
       < reactiveValues         rv.Rd:5,13
       < reactiveValuesToList   rvtl.Rd:5,13
       < rv                     tests_and_coverage.Rmd:73
       < rvtl                   tests_and_coverage.Rmd:73
       < sys                    tests_and_coverage.Rmd:88
       < tagRemoveAttributes    tests_and_coverage.Rmd:78
       < testthat               tests_and_coverage.Rmd:49,115
       < tibble                 point_plot.Rd:10
       < ui                  
       tests_and_coverage.Rmd:37,41,54,74,74,75,75,76,76,77,77,78,78,79,
       79,80,80,81,
                              81,82,82,83,83,84,84,85,85,86,92,93
       < undisplay              tests_and_coverage.Rmd:79
       <tests_and_coverage.Rmd:97   
    • testthat.R
    show/hide check() tests and coverage
      Running ‘testthat.R’ (2.9s)
      checking for unstated dependencies in vignettes ...
      checking package vignettes in ‘inst/doc’
      checking re-building of vignette outputs (1.6s)
      checking for non-standard things in the check directory
      checking for detritus in the temp directory
    
       See
         ‘/private/var/folders/0x/x5wkbhmx0k74tncn9swz7xpr0000gn/T/RtmpYiEbLJ/file75936b43aefb/gap.Rcheck/00check.log’
       for details.
    • R CMD check results
    show/hide check() R CMD check
    ── R CMD check results ──────────────────────────────────── gap 0.0.0.9000 ────
    Duration: 48.2s
    
     checking for code/documentation mismatches ... WARNING
      Data codoc mismatches from documentation object 'movies':
      Variables in data frame 'movies'
        Code: actor1 actor2 actor3 actor4 actor5 audience_rating
              audience_score best_actor_win best_actress_win best_dir_win
              best_pic_nom best_pic_win critics_rating critics_score director
              dvd_rel_date dvd_rel_day dvd_rel_month dvd_rel_year genre
              imdb_num_votes imdb_rating imdb_url mpaa_rating rt_url runtime
              studio thtr_rel_date thtr_rel_day thtr_rel_month thtr_rel_year
              title title_type top200_box
        Docs: actor1 actor2 actor3 actor4 actor5 audience_rating
              audience_score best_actor_win best_actress_win best_dir_win
              best_pic_nom best_pic_win critics_rating critics_score director
              dvd_rel_date dvd_rel_day dvd_rel_month dvd_rel_year genre
              imdb_num_votes imdb_rating imdb_url mpaa_rating rt_url runtime
              studio thtr_rel_date thtr_rel_day thtr_rel_year title
              title_type top200_box
    
    0 errors ✔ | 1 warning ✖ | 0 notes ✔
  • rhub::check_for_cran(): “run check_for_cran() and assign the result to an object” … “use the cran_summary() method to get a message that you can copy-paste in your cran-comments.md file” - rhub

devtools::check(): Highly recommend

It’s easy to forget some of the development workflow steps while you’re creating an app-package–devtools::check() makes it easier to ensure you’re building a robust package (and app!).

Local, CRAN or Package Manager

devtools::build() is also a regular part of the package development process. This function will source and bundle your package (learn the differences here).

# Deploy

## Local, CRAN or Package Manager ----
## This will build a tar.gz that can be installed locally,
## sent to CRAN, or to a package manager
devtools::build()

This will create a gap_0.0.0.9000.tar.gz file to share or submit to a package management system.

── R CMD build ───────────────────────────────────────────────────────────────
  checking for file ‘/projects/apps/gap/DESCRIPTION’ ...
  preparing ‘gap’: (613ms)
  checking DESCRIPTION meta-information ...
  installing the package to build vignettes
  creating vignettes (6.6s)
  excluding invalid files
   Subdirectory 'R' contains invalid file names:
     ‘_disable_autoload.R’
  checking for LF line-endings in source and make files and shell scripts
  checking for empty or unneeded directories
  building ‘gap_0.0.0.9000.tar.gz’
   
[1] "/projects/apps/gap_0.0.0.9000.tar.gz"
  • devtools::install() is another common development workflow function.
RStudio

To deploy an application with RStudio (Posit) products, use of the functions below:

## RStudio ----
## If you want to deploy on RStudio related platforms
golem::add_rstudioconnect_file()
golem::add_shinyappsio_file()
golem::add_shinyserver_file()
Docker

If you use Docker to deploy applications, you can use the following functions:

show/hide docker functions
## Docker ----
## If you want to deploy via a generic Dockerfile
golem::add_dockerfile_with_renv()

## If you want to deploy to ShinyProxy
golem::add_dockerfile_with_renv_shinyproxy()

Read more:

app.R

I’ll deploy my app using shinyapps.io, so after running golem::add_shinyappsio_file() I will see the following output and a new app.R file.

golem::add_shinyappsio_file()
── Creating _disable_autoload.R ──────────────────────────────────
 Created
 Setting active project to '/Users/mjfrigaard/projects/gap'
 Adding '^app\\.R$' to '.Rbuildignore'
 Adding '^rsconnect$' to '.Rbuildignore'
 Adding 'pkgload' to Imports field in DESCRIPTION
 Refer to functions with `pkgload::fun()`
 File created at /Users/mjfrigaard/projects/gap/app.R
To deploy, run:
 rsconnect::deployApp()

 Note that you'll need to upload the whole package to ShinyApps.io
  • View the app.R file contents below:
# Launch the ShinyApp (Do not remove this comment)
# To deploy, run: rsconnect::deployApp()
# Or use the blue button on top of this file
pkgload::load_all(export_all = FALSE, helpers = FALSE, attach_testthat = FALSE)
options( "golem.app.prod" = TRUE)
gap::run_app() # add parameters here (if any)
app.R: Highly recommend

Being able to run your application from app.R is helpful because it performs a few preliminary operations before launching:

  • pkgload::load_all(): ‘It roughly simulates what happens when a package is installed and loaded with library(), without having to first install the package

  • options('golem.app.prod' = TRUE): ‘use functions in your back-end for development purposes, that will be ignored in production

deployApp()

The rsconnect::deployApp() function will deploy the application to the shinyapps.io site (provided you have an account).

rsconnect::deployApp(appName = "gap-movies")
Preparing to deploy application...DONE
Uploading bundle for application: 9468261...DONE
Deploying bundle: 7466839 for application: 9468261 ...
Waiting for task: 1319021708
  building: Parsing manifest
  building: Building image: 8845318
  building: Installing system dependencies
  building: Fetching packages
  building: Building package: pkgload
  building: Building package: testthat
  building: Installing files
  building: Pushing image: 8845318
  deploying: Starting instances
  rollforward: Activating new instances
  success: Stopping old instances
Application successfully deployed to https://mjfrigaard.shinyapps.io/gap-movies/
rsconnect::deployApp(): Recommend

The rsconnect::deployApp() function is helpful for quickly deploying a shiny app-package. I provide the appName because gap is not long enough, but there are other useful arguments for deploying an app into production.

End 03_deploy.R

View the deployed application here.

Recap

Building an application with golem is very similar to developing an R package. The overall process remains the same: New code is placed in R/, external resources are placed in inst/, tests are stored and run from tests/testthat/, etc. The figure below displays how the golem framework works within the R package structure to create ‘production-grade shiny applications’:

(a) golem app-package
Figure 4: golem framework overview

I didn’t cover using renv with this app-package, but you can read more about this in the Using {renv} section.