23  golem

Published

2024-09-11

Warning

The contents for section are under development. Thank you for your patience.


  • The golem framework has excellent documentation in the Engineering Production-Grade Shiny Apps book and the package website.

  • New golem app-packages come ‘pre-packaged’ with UI, server, and standalone app functions

  • dev/ scripts help guide app setup, development, and deployment

  • Development is assisted by variety of add_ and use_ functions for creating modules, utility functions, js, css, etc

    • add_* functions include a with_test argument for creating tests along with new modules and utility functions
  • All golem package functions and files have consistent naming conventions (mod_, fct_, utils_, etc.)

  • golem ‘gives away’ UI and server utility functions with boilerplate tests (for free!)


This chapter walks through building a version of the sap with the golem framework. The resulting app-package (gap) is in the 18_golem branch.

install.packages("golem")
library(golem)

The version/description of golem used in this Shiny app-package is:

Package Version Title Description
golem 0.5.1 A Framework for Robust Shiny Applications An opinionated framework for building a production-ready 'Shiny' application. This package contains a series of tools for building a robust 'Shiny' application from start to finish.

I’ve created the shinypak R package In an effort to make each section accessible and easy to follow:

Install shinypak using pak (or remotes):

# install.packages('pak')
pak::pak('mjfrigaard/shinypak')

Review the chapters in each section:

library(shinypak)
list_apps(regex = 'golem')
## # A tibble: 1 × 2
##   branch   last_updated       
##   <chr>    <dttm>             
## 1 18_golem 2024-08-23 07:47:08

Launch an app:

launch(app = "18_golem")

After checking out the 18_golem branch, be sure to load, document, and install the application.



Ctrl/Cmd + Shift + L / D / B

23.1 gap (a golem app-package)

gap exports the movies data and the standalone app function, run_app().

library(gap)
gap::run_app()

Launch app with the shinypak package:

launch('18_golem')
(a) gap movies app
Figure 23.1: After loading, documenting, and installing gap, launch the movies with run_app()

In the sections below, I’ll note various features and workflows that differ from standard package (or app-package) development.

23.2 dev scripts

New golem apps have a dev/ folder with scripts for setting up your application, development, and launching/deploying.

dev
├── 01_start.R
├── 02_dev.R
└── 03_deploy.R

The package website has each script available in vignettes: 01_start, 02_dev, 03_deploy

23.3 Set up

The dev/01_start.R file opens when the new golem app-package launches. Following the steps in 01_start.R sets up the following files, folders, and options in your golem app-package:

  • DESCRIPTION: golem has a custom function for entering many of the fields we covered in the Packages chapter 1

  • golem has functions that automatically set multiple options in the golem-config.yml 2 and package dependencies 3

  • usethis functions are called for a LICENSE4, creating (and building5) a README6 a CODE_OF_CONDUCT.md7, adding a Lifecycle badge8, and the NEWS.md file.9 Many of these functions and files are covered in the Whole App Game chapter.

  • If you aren’t using Git, there’s an option to initiate a Git repo 10

  • The testing infrastructure is set up using the custom golem::use_recommended_tests() function, which creates the necessary testthat folders we covered in the Test suite chapter 11, but also adds a collection of boilerplate tests.

  • golem apps also have custom functions for creating a series of UI 12 and server 13 utility functions (and their accompanying tests).

23.4 Development

After setting up the golem app-package with dev/01_start.R, the dev/02_dev.R file opens and contains the following functions/options for developing your application.

  • Package dependencies: imports are managed with the attachment package14
  • Code files: new code files in golem apps can be created with a variety of helper functions.

    • Modules: add_module() adds a .R file with a mod_ prefix (an optional test can be included with the with_test = TRUE argument)

    • Utility functions: utility functions can be added with add_utils() or add_fct() (also include the with_test option for tests). golem_utils_* files contain commonly used UI and server functions.

    • The primary app UI and server functions are stored in R/app_ui.R and R/app_server.R.

    • The standalone app function is stored in R/run_app.R.

    • R/_disable_autoload.R disables shiny::loadSupport() (an option we covered in the Launch chapter)

    • R/app_config.R contains configuration functions:

      • app_sys() is a wrapper for system.file()

      • get_golem_config() reads environment variables (i.e., GOLEM_CONFIG_ACTIVE) and the contents of inst/golem-config.yml:
R
├── _disable_autoload.R
├── app_config.R
├── app_server.R
├── app_ui.R
├── data.R
├── golem_utils_server.R
├── golem_utils_ui.R
├── mod_scatter_display.R
├── mod_var_input.R
├── utils_mod_scatter_display.R
├── utils_tests.R
└── run_app.R

1 directory, 11 files
1
Turn off loadSupport()
2
Configure app functions
3
App UI and server functions
4
Data documentation
5
golem utility functions
6
Scatter plot module
7
Variable input module
8
Scatter plot utility function (scatter_plot())
9
Test utility function (test_logger())
10
Standalone app function
  • External files: adding external files is handled with golem_add_external_resources(), which uses the same methods we covered in the External files chapter.15

    • WORDLIST: includes the word ‘golem’ and is an artifact from spellcheck argument in use_recommended_tests(). 16

    • app/ contains the application files. 17

    • extdata/ contains the external data files. 18

    • golem-config.yml is used to set various configuration options. 19
inst
├── WORDLIST
├── app
   └── www
       ├── favicon.ico
       ├── golem-hex.png
       └── shiny.png
├── extdata
   ├── movies.RData
   └── tidy_movies.fst
└── golem-config.yml

4 directories, 7 files
  • Data: the data in golem app-packages function like the data folder and files in a standard R package we covered in the Data chapter.

    • data/: contains the movies.rda file used in the application

    • data-raw/: movies.R reads in data-raw/movies.RData and creates data/movies.rda

      • tidy_movies.R reads in the ggplot2movies::movies data and creates the inst/extdata/tidy_movies.fst data.
data
└── movies.rda

1 directory, 1 file
data-raw
├── movies.R
├── movies.RData
└── tidy_movies.R

1 directory, 2 files
  • Documentation: the roxygen2 documentation in golem app-package files comes with boilerplate tags and descriptions similar to those covered in the Documentation chapter.

    • man/: By default, modules created with add_module() aren’t exported 20

    • vignettes/: Package vignettes in golem app-packages operate like vignettes in standard R packages.
man
├── app_server.Rd
├── app_ui.Rd
├── golem_add_external_resources.Rd
├── mod_scatter_display_server.Rd
├── mod_scatter_display_ui.Rd
├── mod_plot_ui.Rd
├── mod_var_input_server.Rd
├── mod_var_input_ui.Rd
├── movies.Rd
├── run_app.Rd
├── scatter_plot.Rd
└── test_logger.Rd

1 directory, 12 files
1
Primary UI and server functions (‘pre-packaged’ in golem apps)
2
External resources utility function (‘pre-packaged’ in golem apps)
3
Modules
4
Data documentation
5
The ‘pre-packaged’ standalone app function (only export from golem apps)
6
Plot utility function (exported from gap)
7
Test utility function
vignettes/
└── tests_and_coverage.Rmd

1 directory, 1 file

23.5 Tests

golem applications provide a lot of boilerplate tests.

tests
├── README.md
├── spelling.R
├── testthat
   ├── _snaps
   ├── fixtures
   │   ├── make-tidy_ggp2_movies.R
   │   └── tidy_ggp2_movies.rds
   ├── helper.R
   ├── setup-shinytest2.R
   ├── test-app-feature-01.R
   ├── test-golem-recommended.R
   ├── test-golem_utils_server.R
   ├── test-golem_utils_ui.R
   ├── test-mod_scatter_display.R
   ├── test-mod_var_input.R
   ├── test-shinytest2.R
   └── test-utils_mod_scatter_display.R
└── testthat.R

4 directories, 15 files
1
Created from covrpage package
2
Created from spelling package
3
Test fixtures
4
Test helpers
5
Setting up shinytest2
6
shinytest2 feature test
7
Created with: golem::use_recommended_tests()
8
Created with: golem::use_utils_ui(with_test = TRUE)
9
Created with: golem::use_utils_server(with_test = TRUE)
10
Created with: golem::add_module(name = 'scatter_display', with_test = TRUE)
11
Created with: golem::add_module(name = 'var_input', with_test = TRUE)
12
Test recording from shinytest2::record_test()
13
Utility function test (scatter_plot())
  • Tests: The testing framework for golem app-packages is set up with golem::use_recommended_tests() in the dev/01_start.R script.21

    • A tests/README.md file is created by the covrpage package 22

    • tests/spelling.R adds functionality from the spelling package 23

    • testthat: The two golem utility function files (golem_utils_server.R, and golem_utils_ui.R) have accompanying tests files.

      • The with_test argument creates test files for modules and utility functions. 24

23.5.1 Unit tests

I’ve converted the tests from the previous chapters for the modules and utility functions in gap:

  • test-golem-recommended.R contains tests for the functions included in your new golem app (app_ui(), app_sys(), etc.)

  • test-golem_utils_server.R contains utility functions that might be useful in the server

  • test-golem_utils_ui.R contains utility functions that might be useful in the ui

  • The tests for scatter_plot() is in the test-utils_mod_scatter_display.R file.

23.5.2 Module tests

  • The communication between mod_var_input_server() and mod_scatter_display_server() are in test-mod_var_input.R and test-mod_scatter_display.R

23.5.3 System tests

The two system tests are in test-shinytest2.R and test-app-feature-01.R:

  • test-shinytest2.R is the initial resulting test from shinytest2::record_test() covered in System tests

  • test-app-feature-01.R contains feature tests

loadSupport() warning with shinytest2

After setting up shinytest2, the tests/testthat/setup-shinytest2.R file contains a call to shinytest2::load_app_env(). This runs automatically with shinytest2 tests and produces a familiar warning:

Warning message:
In shiny::loadSupport(app_dir, renv = renv, globalrenv = globalrenv) :
  Loading R/ subdirectory for Shiny application, but this directory appears
  to contain an R package. Sourcing files in R/ may cause unexpected behavior.

We covered this warning message in the Launch chapter, and it’s being addressed in a future release of shinytest2

23.6 Deployment

When you’re ready to deploy your golem app, the dev/03_deploy.R file contains a ‘pre deployment checklist’ with multiple options for deploying your application.

23.6.1 Posit platforms

  • devtools::check() and devtools::build() are called.25

  • golem has functions for RStudio Connect, shinyapps.io, and Shiny server,26 as well as options for deploying your application using rsconnect::deployApp(). 27

23.6.2 Docker

golem has multiple options for creating Docker files:

  • golem::add_dockerfile() adds the following Dockerfile:

    show/hide Dockerfile
    FROM rocker/verse:4.3.2
    RUN apt-get update && apt-get install -y  libcurl4-openssl-dev libicu-dev libssl-dev libxml2-dev make pandoc zlib1g-dev && rm -rf /var/lib/apt/lists/*
    RUN mkdir -p /usr/local/lib/R/etc/ /usr/lib/R/etc/
    RUN echo "options(repos = c(CRAN = 'https://cran.rstudio.com/'), download.file.method = 'libcurl', Ncpus = 4)" | tee /usr/local/lib/R/etc/Rprofile.site | tee /usr/lib/R/etc/Rprofile.site
    RUN R -e 'install.packages("remotes")'
    RUN Rscript -e 'remotes::install_version("glue",upgrade="never", version = "1.6.2")'
    RUN Rscript -e 'remotes::install_version("rlang",upgrade="never", version = "1.1.2")'
    RUN Rscript -e 'remotes::install_version("stringr",upgrade="never", version = "1.5.1")'
    RUN Rscript -e 'remotes::install_version("knitr",upgrade="never", version = "1.45")'
    RUN Rscript -e 'remotes::install_version("waldo",upgrade="never", version = "0.5.2")'
    RUN Rscript -e 'remotes::install_version("shiny",upgrade="never", version = "1.8.0")'
    RUN Rscript -e 'remotes::install_version("rmarkdown",upgrade="never", version = "2.25")'
    RUN Rscript -e 'remotes::install_version("config",upgrade="never", version = "0.3.2")'
    RUN Rscript -e 'remotes::install_version("spelling",upgrade="never", version = "2.2.1")'
    RUN Rscript -e 'remotes::install_version("shinytest2",upgrade="never", version = "0.3.1")'
    RUN Rscript -e 'remotes::install_version("ggplot2movies",upgrade="never", version = "0.0.1")'
    RUN Rscript -e 'remotes::install_version("logger",upgrade="never", version = "0.2.2")'
    RUN Rscript -e 'remotes::install_version("golem",upgrade="never", version = "0.4.1")'
    RUN Rscript -e 'remotes::install_version("ggplot2",upgrade="never", version = "3.4.4")'
    RUN Rscript -e 'remotes::install_github("rstudio/htmltools@a8a3559edbfd9dda78418251e69273fa9dfeb9bc")'
    RUN Rscript -e 'remotes::install_github("r-lib/testthat@fe50a222c62cc8733b397690caf3b2a95856f902")'
    RUN mkdir /build_zone
    ADD . /build_zone
    WORKDIR /build_zone
    RUN R -e 'remotes::install_local(upgrade="never")'
    RUN rm -rf /build_zone
    EXPOSE 80
    CMD R -e "options('shiny.port'=80,shiny.host='0.0.0.0');library(gap);gap::run_app()"
  • golem::add_dockerfile_with_renv() creates a tmp/deploy folder and adds the following files:

deploy/
  ├── Dockerfile
  ├── Dockerfile_base
  ├── README
  ├── gap_0.0.0.9000.tar.gz
  └── renv.lock.prod
  • README

    docker build -f Dockerfile_base --progress=plain -t gap_base .
    docker build -f Dockerfile --progress=plain -t gap:latest .
    docker run -p 80:80 gap:latest
    # then go to 127.0.0.1:80
  • Dockerfile

    show/hide Dockerfile
    FROM gap_base
    COPY renv.lock.prod renv.lock
    RUN R -e 'renv::restore()'
    COPY gap_*.tar.gz /app.tar.gz
    RUN R -e 'remotes::install_local("/app.tar.gz",upgrade="never")'
    RUN rm /app.tar.gz
    EXPOSE 80
    CMD R -e "options('shiny.port'=80,shiny.host='0.0.0.0');library(gap);gap::run_app()"
  • Dockerfile_base

    show/hide Dockerfile_base
    FROM rocker/verse:4.3.2
    RUN apt-get update -y && apt-get install -y  make zlib1g-dev git libicu-dev && rm -rf /var/lib/apt/lists/*
    RUN mkdir -p /usr/local/lib/R/etc/ /usr/lib/R/etc/
    RUN echo "options(renv.config.pak.enabled = FALSE, repos = c(CRAN = 'https://cran.rstudio.com/'), download.file.method = 'libcurl', Ncpus = 4)" | tee /usr/local/lib/R/etc/Rprofile.site | tee /usr/lib/R/etc/Rprofile.site
    RUN R -e 'install.packages("remotes")'
    RUN R -e 'remotes::install_version("renv", version = "1.0.3")'
    COPY renv.lock.prod renv.lock
    RUN R -e 'renv::restore()'
  • gap_0.0.0.9000.tar.gz is a compressed version of our app-package to deploy in the Docker container.

  • renv.lock.prod is a JSON file with a list of packages used in our app-package.

You can read more details about deploying with Docker on the Shiny Frameworks supplemental website.

23.7 Summary of golem features

golems helper functions and dev scripts make application development fast–I was able to create gap quickly, and all of the supporting packages (covrpage, attachment, spelling) make the development process faster/easier:

  1. The two modules (mod_plot and mod_var) are easily created with add_module(), the utility function with add_utils()
  2. We can easily add the modules to the app_ui() and app_server()
  3. App images are moved into inst/app/www/
  4. The movies data was added to inst/extdata/, then read into data/ folder with the data-raw/movies.R file.
  5. For documentation, the attachment::att_amend_desc() function quickly captures any dependencies
  6. Finally, I loaded, documented, and installed the gap package and ran the application with gap::run_app()

If you’ve followed along with the preceding chapters, the golem framework will be familiar. In essence, golem takes many of the package development steps we’ve covered and bundles them into wrapper functions (i.e., add_module() is similar to running usethis::use_r() and usethis::use_test(), then adding an roxygen2 skeleton).

23.8 Dependencies

It’s also worth noting that using the golem framework adds golem as a dependency:

# in the 18_golem branch of sap
pak::local_deps_explain(deps = 'golem', root = ".")
gap -> golem 

23.8.1 sap dependencies

See the 09d_inst-prod branch of sap.

For comparison, this is the sap dependency tree (note that using devtools/usethis doesn’t make our app-package depend on these packages).

# in the 09d_inst-prod branch of sap
pak::local_deps_explain(deps = 'devtools', root = ".")
x devtools
# in the 09d_inst-prod branch of sap
pak::local_deps_explain(deps = 'usethis', root = ".")
x usethis

Recap

RECAP  


The golem framework is a huge time saver if you’re familiar with R package development (and you’d prefer if many of these steps were bundled and optimized for ‘production grade’ Shiny apps). However, if you’re not familiar with package development, you might end up with app-packages that have bugs you can’t diagnose or fix.

Please open an issue on GitHub


  1. Fields are filled with golem::fill_desc()↩︎

  2. Options are set with with golem::set_golem_options()↩︎

  3. Dependencies are installed with golem::install_dev_deps()↩︎

  4. Created using usethis::use_mit_license()↩︎

  5. Built using devtools::build_readme()↩︎

  6. Created using usethis::use_readme_rmd()↩︎

  7. Created using usethis::use_code_of_conduct()↩︎

  8. Created using usethis::use_lifecycle_badge()↩︎

  9. Created using usethis::use_news_md()↩︎

  10. Initialize Git using usethis::use_git()↩︎

  11. The tests/ folder and testthat files are included with golem::use_recommended_tests()↩︎

  12. Create UI utility functions using golem::use_utils_ui()↩︎

  13. Create server utility functions using golem::use_utils_server()↩︎

  14. attachment::att_amend_desc() parses the code under R/ and make sure the DESCRIPTION file is up-to-date↩︎

  15. golem_add_external_resources() is a wrapper for golem::add_resource_path(), which is a wrapper for shiny::addResourcePath() (and app_sys() is a wrapper for system.file()).↩︎

  16. The use_recommended_tests() is run in the dev/01_start.R file and if spellcheck is TRUE, creates the tests/spelling.R file and the inst/WORDLIST file.↩︎

  17. the app/ folder is used to add external resources to the application (similar to the previous versions of sap).↩︎

  18. This contains the RData file for the original movies data and the exported tidy_movies.fst file.↩︎

  19. golem apps use a golem-config.yml file for setting various options. These are initially set with set_golem_options() (and based on the config package)↩︎

  20. The noRd tag is added to module files created with add_module(), but you can export these functions by setting the export argument to TRUE. @importFrom is used to import NS() and tagList().↩︎

  21. test-golem-recommended.R contains the recommended tests for app_ui(), app_server(), app_sys(), and golem-config.yml↩︎

  22. The covrpage package is not on CRAN, but the development version always seems to work. Create the tests/README.md file with covrpage::covrpage().↩︎

  23. The spelling package will spell check vignettes, packages, etc.↩︎

  24. with_test = TRUE) adds tests in the dev/01_start.R script. Code files created with golem::add_module(), golem::add_utils(), and golem::add_fct() will also include a test file if with_test is set to TRUE.↩︎

  25. This also includes a call to rhub::check_for_cran(), which may or may not be of concern for your application.↩︎

  26. These functions will create and app.R file to launch and deploy your application.↩︎

  27. Includes boilerplate for appName, appTitle, appFiles, etc.↩︎