install.packages("golem")
# or the dev version
::install_github("Thinkr-open/golem")
remoteslibrary(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)
::run_app() gap
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.R
The package website has each script available in vignettes: 01_start, 02_dev, 03_deploy.
23.3 Set up
“
01_start.R
should 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 necessaryDESCRIPTION
fields.1golem
has functions that automatically set multiple options in thegolem-config.yml
2 and package dependencies3usethis
functions 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()
.12golem
apps also have custom functions for creating a series of UI13 and server14 utility functions (which we’ll cover below).
23.4 Development
“
02_dev.R
should 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_test
option for tests).The
golem_utils_*
files contain commonly used UI and server functions:use_utils_ui()
creates theR/golem_utils_ui.R
file.use_utils_server()
creates theR/golem_utils_server.R
file.
Other R files
R/_disable_autoload.R
disablesshiny::loadSupport()
(an option we covered in the Launch chapter)R/app_config.R
contains 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
├── gap_theme.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_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
-
thematic
theme - 6
-
golem
utility functions
- 7
- Aesthetics input module
- 8
-
Scatter plot module
- 9
-
Variable input module
- 10
-
Standalone app function
- 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
WORDLIST
includes the word ‘golem
’ and it’s an artifact fromspellcheck
argument inuse_recommended_tests()
.17
Non-R files
dev/02_dev.R
includes 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 files
23.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 file
Raw 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 files
Package data
data/
contains the movies.rda
file used in the application.
data
└── movies.rda
1 directory, 1 file
23.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
├── movies.Rd
├── run_app.Rd
├── scatter_plot.Rd
└── test_logger.Rd
1 directory, 12 files
- 1
- Data documentation
- 2
-
The ‘pre-packaged’ standalone app function (only export from
golem
apps)
- 3
-
Plot utility function (exported from
gap
)
- 4
-
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 files
23.4.5 Dependencies
Development dependencies
The
install_dev_deps()
function from thedev/01_start.R
script installs the packages needed to develop agolem
app.Include packages to the
Imports
field in theDESCRIPTION
withattachment::att_amend_desc()
, which parses the code underR/
to make sure theDESCRIPTION
file is up-to-date.
Package dependencies
- Include add-on packages with
usethis::use_package()
and usepkg::fun()
in theroxygen2
documentation (see Section 6.3).For example, we’ve included a
thematic
theme to match thegolem
package colors.After creating our theme in
R/gap_theme.R
, thegolem_add_external_resources()
function inR/app_ui.R
can include this dependency withbslib::bs_theme_dependencies()
.
- Modules and functions created with
golem
’s file creation helpers include@importFrom
in theirroxygen2
documentation.
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.R
contains tests for the functions included in your new golem app.22The two
golem
utility function files (golem_utils_server.R
, andgolem_utils_ui.R
) have accompanying tests files.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.
test-utils_scatter_display.R
tests ourscatter_plot()
utility function.
Module tests
test-mod_var_inputs.R
andtest-mod_aes_inputs.R
test 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
shinytest2
vignette:test-shinytest2.R
is the initial resulting test fromshinytest2::record_test()
covered in Section 18.3.test-app-feature-01.R
tests 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
covrpage
package
- 2
-
Created from
spelling
package
- 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.Rmd
will include the results fromdevtools::check(quiet = TRUE)
and a detailed report on tests coverage (see below).covrpage()
will create atests/README.md
file that contains detailed information on tests and coverage.
::check(quiet = TRUE)
devtools#> ℹ 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 ✖
::package_coverage()
covr#> 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.R
should 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.gz
file (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.R
file to the root of your app-package.pkgload::load_all()
is called in the newapp.R
file (so this package is added to theImports
field 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
::load_all(export_all = FALSE,
pkgloadhelpers = FALSE,
attach_testthat = FALSE)
options( "golem.app.prod" = TRUE)
::run_app() # add parameters here (if any) gap
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()
.
::deployApp(
rsconnectappName = 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/deploy
folder and addsDockerfile
,Dockerfile_base
,README
,gap_0.0.0.9000.tar.gz
, andrenv.lock.prod
(see below)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.
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.prod
show/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:80
show/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
golem
s 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
:
- Modules and utility function were easily created with
add_module()
andadd_utils()
- The
bslib
layout functions and thethematic
theme were added toapp_ui()
andgolem_add_external_resources()
- The module server logic was added to
app_server()
- The
golem
hex image was moved addedinst/app/www/
- I downloaded the
movies.RData
file intoinst/extdata/
, then createddata-raw/movies.R
and createddata/movies.rda
.
- Called
usethis::use_vignette()
to createshinytest2.Rmd
andtests_and_coverage.Rmd
.
- Added the
test_logger()
function toR/utils_tests.R
.
- Revised the unit tests and moved the system tests to
vignettes/shinytest2.Rmd
.
- Included code coverage in
README.Rmd
withusethis::use_coverage('codecov')
and created thetests/README.md
file withcovrpage::covrpage()
.
- Called
attachment::att_amend_desc()
to capture any dependencies
- Finally, I loaded, documented, and installed the
gap
package 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
::local_deps_explain(deps = 'golem', root = ".") pak
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
::local_deps_explain(deps = 'devtools', root = ".") pak
x devtools
# in the 09d_inst-prod branch of sap
::local_deps_explain(deps = 'usethis', root = ".") pak
x usethis
Recap
Options are set with with the
options
argument 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 andtestthat
files 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
).↩︎golem
apps use agolem-config.yml
file for setting various options. These are initially set withset_golem_options()
(and based on theconfig
package).↩︎The
use_recommended_tests()
is run in thedev/01_start.R
file and ifspellcheck
isTRUE
, creates thetests/spelling.R
file and theinst/WORDLIST
file.↩︎This contains the
movies.RData
file for the original Shiny application.↩︎The
noRd
tag is added to module files created withadd_module()
, but you can export these functions by setting theexport
argument toTRUE
.↩︎The
testthat
test suite is set up withgolem::use_recommended_tests()
function.↩︎The
spelling
package will spell check vignettes, packages, etc.↩︎test-golem-recommended.R
contains the recommended tests forapp_ui()
,app_server()
,app_sys()
, andgolem-config.yml
.↩︎The
covrpage
package is not on CRAN, but the development version always seems to work.↩︎rhub::check_for_cran()
is “deprecated and defunct”, userhub::rhubv2()
instead.↩︎