install.packages("golem")
# or the dev version
remotes::install_github("Thinkr-open/golem")
library(golem)23 golem
This chapter walks through building a version of the sap with the golem framework. Install the golem package from CRAN (or the development version):
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. |
This chapter is slightly different than the others. I’ll cover the golem method for creating app-packages, not necessarily the code itself. The application I’ll use is identical to the one we’ve built in previous chapters (using the movies data, with two input modules, one display module, and a utility function for building the scatter plot).
%%{init: {'theme': 'neutral', 'themeVariables': { 'fontFamily': 'monospace', "fontSize":"14px"}}}%%
flowchart TD
User(["<strong>User</strong>"])
mod_var_input_ui["<strong>mod_var_input_ui()</strong>"]
mod_aes_input_ui["<strong>mod_aes_input_ui()</strong>"]
mod_scatter_display_ui["<strong>mod_scatter_display_ui</strong>"]
subgraph mod_scatter_display_server["<strong>mod_scatter_display_server()</strong>"]
var_inputs[\"var_inputs"\]
aes_inputs[\"aes_inputs"\]
inputs[/"inputs()"/]
scatter_plot("scatter_plot()")
end
subgraph mod_var_input_server["<strong>mod_var_input_server()</strong>"]
VarReactives[/"input$x<br>input$y<br>input$z"/]
end
subgraph mod_aes_input_server["<strong>mod_aes_input_server()</strong>"]
AesReactives[/"input$alpha<br>input$size<br>input$plot_title"/]
end
selected_vars[/"selected variables"/]
selected_aes[/"selected aesthetics"/]
User --> |"<em>Selects X, Y, and Color...</em>"|mod_var_input_ui
User --> |"<em>Selects Size, Alpha and optional Title...</em>"|mod_aes_input_ui
mod_var_input_ui --> |"<em>Collects<br>variables...</em>"|mod_var_input_server
mod_aes_input_ui --> |"<em>Collects aesthetics...</em>"|mod_aes_input_server
VarReactives ==> selected_vars
AesReactives ==> selected_aes
selected_vars ==>|"<em>Input argument for...</em>"|var_inputs
selected_aes ==>|"<em>Input argument for...</em>"|aes_inputs
var_inputs & aes_inputs --> inputs --> scatter_plot
scatter_plot -->|"<em>Renders plot...</em>"|mod_scatter_display_ui
mod_scatter_display_ui -->|"<em>Displays output...</em>"|Display(["<strong>Graph</strong>"])
style User font-size:18px
style Display font-size:18px
style mod_scatter_display_ui fill:#eee,font-size:13px,stroke-width:1px,rx:3,ry:3
style mod_scatter_display_server fill:#eee,font-size:13px,stroke-width:1px,rx:3,ry:3
style mod_var_input_server fill:#eee,font-size:14px,stroke-width:1px,rx:3,ry:3
style mod_var_input_ui fill:#eee,font-size:14px,stroke-width:1px,rx:3,ry:3
style mod_aes_input_server fill:#eee,font-size:14px,stroke-width:1px,rx:3,ry:3
style mod_aes_input_ui fill:#eee,font-size:14px,stroke-width:1px,rx:3,ry:3
style AesReactives fill:#fff,stroke:#bbb,stroke-width:2px
style VarReactives fill:#fff,stroke:#bbb,stroke-width:2px
style selected_vars fill:#fff,stroke:#000,stroke-width:2px
style selected_aes fill:#fff,stroke:#000,stroke-width:2px
style var_inputs fill:#fff,stroke:#bbb,stroke-width:2px
style aes_inputs fill:#fff,stroke:#bbb,stroke-width:2px
style inputs fill:#fff,stroke:#bbb,stroke-width:2px
style scatter_plot fill:#444,color:#FFF,stroke:#000,stroke-width:2px,rx:10,ry:10
style VarReactives font-size: 14px
style AesReactives font-size: 14px
23.1 gap (a golem app-package)
All golem apps export the standalone app function, run_app().
library(gap)
gap::run_app()Launch app with the shinypak package:
launch('23_golem')In the sections below, I’ll note various features or tools that differ from the standard devtools/usethis app-package development we’ve covered so far. For a more comprehensive overview of golem Shiny app development, check out the Engineering Production-Grade Shiny Apps book and my Shiny frameworks website.
23.2 The dev/ scripts
After creating a new golem app with golem::create_golem() or using the New Project Wizard in RStudio, you’ll see a dev/ folder with three scripts for setting up, developing, and launching/deploying your application.
dev
├── 01_start.R
├── 02_dev.R
└── 03_deploy.RThe package website has each script available in vignettes: 01_start, 02_dev, 03_deploy.
23.3 Set up
“
01_start.Rshould be filled at start.”
The dev/01_start.R file opens when the new golem app-package launches and provides steps for setting up the following files, folders, and options:
Use
golem’sfill_desc()function for entering the necessaryDESCRIPTIONfields.1golemhas functions that automatically set multiple options in thegolem-config.yml2 and package dependencies3usethisfunctions are called for:4If you aren’t using Git, there are functions for initiating and configuring your repo.11
The app testing suite is set up using
use_recommended_tests().12golemapps also have custom functions for creating a series of UI13 and server14 utility functions (which we’ll cover below).
23.4 Development
“
02_dev.Rshould be used to keep track of your development during the project.
After setting up your golem app-package, the dev/02_dev.R file contains functions and tools for developing the application.
23.4.1 The R/ folder
The dev/02_dev.R script contains the following functions for developing functions, modules, and other application code.
Primary app files
All golem apps come with primary app UI and server functions (R/app_ui.R and R/app_server.R) and a standalone app function (R/run_app.R.).
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()oradd_fct()(also includes thewith_testoption for tests).The
golem_utils_*files contain commonly used UI and server functions:use_utils_ui()creates theR/golem_utils_ui.Rfile.use_utils_server()creates theR/golem_utils_server.Rfile.
Other R files
R/_disable_autoload.Rdisablesshiny::loadSupport()(an option we covered in the Launch chapter)R/app_config.Rcontains configuration functions:
app_sys()is a wrapper forsystem.file()get_golem_config()reads environment variables (i.e.,GOLEM_CONFIG_ACTIVE) and the contents ofinst/golem-config.yml:
Below is the R/ folder after creating the modules and utility functions.
show/hide R folder
R
├── _disable_autoload.R
├── app_config.R
├── app_server.R
├── app_ui.R
├── data.R
├── golem_utils_server.R
├── golem_utils_ui.R
├── mod_aes_inputs.R
├── mod_scatter_display.R
├── mod_var_inputs.R
├── run_app.R
├── utils_gap_theme.R
├── utils_scatter_display.R
└── utils_tests.R
1 directory, 14 files- 1
-
Turn off
loadSupport()
- 2
- Configure app functions
- 3
-
App UI and server functions
- 4
-
Data documentation
- 5
-
golemutility functions
- 6
- Aesthetics input module
- 7
-
Scatter plot module
- 8
-
Variable input module
- 9
-
Standalone app function
- 10
-
bslibtheme - 11
-
Scatter plot utility function (
scatter_plot())
- 12
-
Test utility function (
test_logger())
23.4.2 The inst/ folder
The inst/ folder has a special role in golem apps. Custom functions handle external resources to ensure these files are loaded when the application launches.
External files
golem_add_external_resources()adds external files, which uses similar methods to Chapter 9.golem_add_external_resources(),golem::add_resource_path(), andapp_sys()are essentially wrappers forshiny::addResourcePath()andsystem.file().The
WORDLISTincludes the word ‘golem’ and it’s an artifact fromspellcheckargument inuse_recommended_tests().17
Non-R files
dev/02_dev.Rincludes helpers for adding JavaScript, CSS, and other files to theinst/app/www/folder:golem::add_js_file()golem::add_js_handler()golem::add_css_file()golem::add_sass_file()golem::add_any_file()
inst
├── WORDLIST
├── app
│ └── www
│ ├── favicon.ico
│ └── golem.png
├── extdata
│ └── movies.RData
└── golem-config.yml
4 directories, 5 files23.4.3 Data
Data in golem app-packages function like the data files and folders in a standard R package (we covered these in Chapter 7).
External data
inst/extdata/ contains the external data files.18
inst
└── extdata
└── movies.RData
2 directories, 1 fileRaw data
data-raw/ contains movies.R, which is used to load extdata/movies.RData and create the data/movies.rda file.
data-raw
├── movies.R
└── movies.RData
1 directory, 2 filesPackage data
data/ contains the movies.rda file used in the application.
data
└── movies.rda
1 directory, 1 file23.4.4 Documentation
The roxygen2 documentation in golem app-package files comes with boilerplate tags and descriptions similar to those covered in Chapter 5.
The man/ folder
By default, modules created with add_module() or golem’s other file creation functions will have the @noRd tag, so the man/ folder will only contain functions we’ve manually exported:19
man
├── gap_theme.Rd
├── movies.Rd
├── run_app.Rd
├── scatter_plot.Rd
└── test_logger.Rd
1 directory, 5 files- 1
-
bslibtheme for (thematic)
- 2
-
Data documentation
- 3
-
The ‘pre-packaged’ standalone app function (only export from
golemapps)
- 4
-
Plot utility function (exported from
gap)
- 5
-
Test utility function (exported from
gap)
Vignettes
Package vignettes in golem app-packages operate like vignettes in standard R packages.
vignettes/
├── shinytest2.Rmd
└── tests_and_coverage.Rmd
1 directory, 2 filesI’ve stored the shinytest2 in the vignettes/shinytest2.Rmd vignette (more on this below).
23.4.5 Dependencies
Development dependencies
The
install_dev_deps()function from thedev/01_start.Rscript installs the packages needed to develop agolemapp.Include packages to the
Importsfield in theDESCRIPTIONwithattachment::att_amend_desc(), which parses the code underR/to make sure theDESCRIPTIONfile is up-to-date.
Package dependencies
- Include add-on packages with
usethis::use_package()and usepkg::fun()in theroxygen2documentation (see Section 6.2).For example, we’ve included a
thematictheme to match thegolempackage colors.After creating our theme in
R/gap_theme.R, thegolem_add_external_resources()function inR/app_ui.Rcan include this dependency withbslib::bs_theme_dependencies().
- Modules and functions created with
golem’s file creation helpers include@importFromin theirroxygen2documentation.
23.4.6 Tests
The test suite for golem app-packages is set up in the dev/01_start.R script.20 golem’s helper functions can potentially provide a lot of boilerplate tests, because any code files created with add_module(), add_utils(), and add_fct() will also include a test file if with_test is set to TRUE.
Unit tests
test-golem-recommended.Rcontains tests for the functions included in your new golem app.22The two
golemutility function files (golem_utils_server.R, andgolem_utils_ui.R) have accompanying tests files.test-golem_utils_server.Rcontains utility functions that might be useful in the server.test-golem_utils_ui.Rcontains utility functions that might be useful in the UI.
test-utils_scatter_display.Rtests ourscatter_plot()utility function.
Module tests
test-mod_var_inputs.Randtest-mod_aes_inputs.Rtest our input modules (see Section 17.2).The communication between our modules is tested in
test-mod_scatter_display.R(see Section 17.3).
System tests
I’ve moved the two system tests to the
shinytest2vignette:test-shinytest2.Ris the initial resulting test fromshinytest2::record_test()covered in Section 18.3.test-app-feature-01.Rtests the data visualization dropdown feature (see Section 18.4).
tests/
├── README.md
├── spelling.R
├── testthat
│ ├── helper.R
│ ├── test-golem-recommended.R
│ ├── test-golem_utils_server.R
│ ├── test-golem_utils_ui.R
│ ├── test-mod_aes_inputs.R
│ ├── test-mod_scatter_display.R
│ ├── test-mod_var_inputs.R
│ └── test-utils_scatter_display.R
└── testthat.R
3 directories, 11 files- 1
-
Created from
covrpagepackage
- 2
-
Created from
spellingpackage
- 3
-
Test helpers
- 4
-
Created with:
golem::use_recommended_tests() - 5
-
Created with:
golem::use_utils_ui(with_test = TRUE) - 6
-
Created with:
golem::use_utils_server(with_test = TRUE)
- 7
-
Created with:
golem::add_module(name = 'aes_inputs',with_test = TRUE) - 8
-
Created with:
golem::add_module(name = 'scatter_display',with_test = TRUE) - 9
-
Created with:
golem::add_module(name = 'var_inputs',with_test = TRUE) - 10
-
Utility function test (
scatter_plot())
23.4.7 Code coverage
Code test coverage is handled by usethis::use_coverage() and covrpage::covrpage().23 I’ve found the code coverage functions don’t play well with long descriptions in testthat’s BDD functions or shinytest2 tests (which is why I’ve placed these functions/tests in the vignettes).
Test coverage
use_coverage()requires atype(“codecov” or “coveralls”).The
README.Rmdwill include the results fromdevtools::check(quiet = TRUE)and a detailed report on tests coverage (see below).covrpage()will create atests/README.mdfile that contains detailed information on tests and coverage.
devtools::check(quiet = TRUE)
#> ℹ Loading gap
#> ── R CMD check results ──────────────────────────── gap 0.0.0.9000 ────
#> Duration: 42.5s
#>
#> ❯ checking for future file timestamps ... NOTE
#> unable to verify current time
#>
#> ❯ checking top-level files ... NOTE
#> Non-standard file/directory found at top level:
#> ‘app.R’
#>
#> ❯ checking package subdirectories ... NOTE
#> Problems with news in ‘NEWS.md’:
#> No news entries found.
#>
#> ❯ checking R code for possible problems ... NOTE
#> mod_scatter_display_server : <anonymous>: no visible binding for global
#> variable ‘movies’
#> Undefined global functions or variables:
#> movies
#>
#> 0 errors ✔ | 0 warnings ✔ | 4 notes ✖covr::package_coverage()
#> gap Coverage: 85.27%
#> R/run_app.R: 0.00%
#> R/utils_tests.R: 53.85%
#> R/mod_scatter_display.R: 56.76%
#> R/golem_utils_server.R: 77.78%
#> R/golem_utils_ui.R: 87.94%
#> R/app_config.R: 100.00%
#> R/app_server.R: 100.00%
#> R/app_ui.R: 100.00%
#> R/mod_aes_inputs.R: 100.00%
#> R/mod_var_inputs.R: 100.00%
#> R/utils_scatter_display.R: 100.00%23.4.8 Continuous Integration (CI)
All the continuous integration (CI) functions in dev/02_dev.R are from the usethis package (see Chapter 21). Many of these functions have been deprecated and/or should be used cautiously.
GitHub Actions
use_github()has been deprecated favor ofuse_github_action().use_github_action_check_release(),use_github_action_check_standard(), anduse_github_action_pr_commands()are ‘deprecated in favor ofuse_github_action(), which can now suggest specific workflows to use.’use_github_action_check_full()is ‘overkill for most packages and is not recommended.’
Other CI Options
use_jenkins()adds a basic Jenkinsfile for R packages to the project root directory.use_circleci(),use_circleci_badge(), anduse_gitlab_ci()are ‘not actively used by the tidyverse team, and may not currently work. Use at your own risk.’
23.5 Deployment
“
03_deploy.Rshould be used once you need to deploy your app.”
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.
Running checks
devtools::check()andrhub::check_for_cran()24 will checks your app-package under the assumption it’s being submitted to CRAN.devtools::build()will create atar.gzfile (useful for downloads/installs or a package management system).
golem has functions for preparing to deploy your app to Posit products.
app.R
All of these functions create an
app.Rfile to the root of your app-package.pkgload::load_all()is called in the newapp.Rfile (so this package is added to theImportsfield in theDESCRIPTION).
# 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)R/_disable_autoload.R
These functions also add an empty R/_disable_autoload.R file to handle the loadSupport() warning.
Hidden files
Various hidden files are added (.rscignore) and included in the .Rbuildignore ("^rsconnect$" to ignore rsconnect folder, "^app\\.R$" to ignore the app.R file, etc.).
Deploy code
dev/03_deploy.R includes boilerplate code for deploying your application using rsconnect::deployApp().
rsconnect::deployApp(
appName = desc::desc_get_field("Package"),
appTitle = desc::desc_get_field("Package"),
appFiles = c(
# Add any additional files unique to your app here.
"R/",
"inst/",
"data/",
"NAMESPACE",
"DESCRIPTION",
"app.R"
),
appId = rsconnect::deployments(".")$appID,
lint = FALSE,
forceUpdate = TRUE
)golem also has multiple options for creating Docker files. I’ve included the golem::add_dockerfile_with_renv() below because we’re using renv.
Docker
add_dockerfile_with_renv()creates atmp/deployfolder and addsDockerfile,Dockerfile_base,README,gap_0.0.0.9000.tar.gz, andrenv.lock.prod(see below)gap_0.0.0.9000.tar.gzis a compressed version of our app-package to deploy in the Docker container.renv.lock.prodis a JSON file with a list of packages used in our app-package.
The tmp/deploy folder from add_dockerfile_with_renv() is below:
deploy/
├── Dockerfile
├── Dockerfile_base
├── README
├── gap_0.0.0.9000.tar.gz
└── renv.lock.prodshow/hide golem::add_dockerfile_with_renv() 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:80show/hide golem::add_dockerfile_with_renv() Dockerfile
FROM gap_base
COPY renv.lock.prod renv.lock
RUN R -e 'options(renv.config.pak.enabled = FALSE);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
USER rstudio
CMD R -e "options('shiny.port'=80,shiny.host='0.0.0.0');library(gap);gap::run_app()"show/hide golem::add_dockerfile_with_renv() Dockerfile_base
FROM rocker/verse:4.4.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()'You can read more details about deploying with Docker on the Shiny Frameworks supplemental website.
23.6 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. Below is a brief summary of the steps it took to create gap:
- Created the modules and utility function with
add_module()andadd_utils().
- Added the
bsliblayout functions and thethematictheme toapp_ui()andgolem_add_external_resources().
- Added module server logic to
app_server().
- Downloaded the
golemhex image toinst/app/www/.
- Downloaded the
movies.RDatafile intoinst/extdata/, then createddata-raw/movies.Rand createddata/movies.rda.
- Called
usethis::use_vignette()to createshinytest2.Rmdandtests_and_coverage.Rmd.
- Added the
test_logger()utility function toR/utils_tests.R.
- Revised the unit tests and moved all system tests to
vignettes/shinytest2.Rmd.
- Included code coverage in
README.Rmdwithusethis::use_coverage('codecov')and created thetests/README.mdfile withcovrpage::covrpage().
- Called
attachment::att_amend_desc()to capture any dependencies.
- Finally, I loaded, documented, and installed the
gappackage and ran the application withgap::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.7 gap dependencies
It’s also worth noting that using the golem framework adds golem as a dependency:
# in the 23_golem branch of sap
pak::local_deps_explain(deps = 'golem', root = ".")gap -> golem For comparison, if we look at the app-package from the previous branch (22_pkgdown) the dependency tree shows us that using devtools/usethis doesn’t make our app-package depend on these packages:
See the 22_pkgdown branch of sap.
# 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 usethisRecap
Options are set with with the
optionsargument ofgolem::fill_desc()(which callsgolem::set_golem_options()).↩︎Dependencies are installed with
golem::install_dev_deps()↩︎Many of these functions and files are covered in the Whole App Game chapter.↩︎
Created using
usethis::use_mit_license().↩︎Created using
usethis::use_readme_rmd().↩︎Built using
devtools::build_readme().↩︎Created using
usethis::use_code_of_conduct().↩︎Created using
usethis::use_lifecycle_badge().↩︎Created using
usethis::use_news_md().↩︎Initialize Git using
usethis::use_git()and set the remote withusethis::use_git_remote().↩︎The
tests/folder andtestthatfiles included withgolem::use_recommended_tests()are covered in the Test suite chapter.↩︎Create UI utility functions using
golem::use_utils_ui()↩︎Create server utility functions using
golem::use_utils_server()↩︎the
app/folder is used to add external resources to the application (similar to the previous versions ofsap).↩︎golemapps use agolem-config.ymlfile for setting various options. These are initially set withset_golem_options()(and based on theconfigpackage).↩︎The
use_recommended_tests()is run in thedev/01_start.Rfile and ifspellcheckisTRUE, creates thetests/spelling.Rfile and theinst/WORDLISTfile.↩︎This contains the
movies.RDatafile for the original Shiny application.↩︎The
noRdtag is added to module files created withadd_module(), but you can export these functions by setting theexportargument toTRUE.↩︎The
testthattest suite is set up withgolem::use_recommended_tests()function.↩︎The
spellingpackage will spell check vignettes, packages, etc.↩︎test-golem-recommended.Rcontains the recommended tests forapp_ui(),app_server(),app_sys(), andgolem-config.yml.↩︎The
covrpagepackage is not on CRAN, but the development version always seems to work.↩︎rhub::check_for_cran()is “deprecated and defunct”, userhub::rhubv2()instead.↩︎



