{golem} 0.4.0 is now available

Author

colin

Published

March 15, 2023

The new version of {golem} is available!

You can download it from your favorite CRAN repository, or by running the following command:

pak::pak(
  "thinkr-open/golem@v0.4.0"
)

What’s up with this new version?

Lighter dependency tree

About dependencies

When we released the first version of {golem} on CRAN, we decided that all {golem}-based app should depend on {golem}. This was a conscious decision, and we made it because {golem} comes with a bunch of internal functions that are used at runtime.

For example bundle_resources() links all the external resources (CSS, JS, …), is_running() detects that the current app is a {golem} app, with_golem_options() allows passing arguments to your run_app() functions (and to set a new cool option that I’ll talk about below), and others. We were very aware that this choice came with a drawback: adding a dependency to every {golem}-based app.

Dependency management is a big topic in the software engineering world, and some projects tend to go for minimizing as much as possible the number of dependencies.

They are right (to a certain extent): adding dependencies means that you can be subject to some troubles, notably if one of the packages you’re relying on gets removed from the CRAN.

Even in a contained world where you have your own CRAN-like repository, there is always the drawback of installation time: the more dependencies you have, the longer it takes to install the package, and by extension to compile a Docker image (for example). This is why we could have been tempted to imagine a different approach for {golem}: creating files inside your project that would contain everything you need. But I think that this approach can be way less safe and practical, for reasons I’ll develop below.

  1. You need dependencies for a {shiny} app

First, whatever happens, you’ll need to rely on {shiny}, as you are building a {shiny} app. And you’re likely to add a bunch of other packages along the way. So the “very small dependency tree” is a myth if you’re building a web app. {shiny} itself already has 31 dependencies.

Click to show {shiny} dependency tree
packageVersion("shiny")
[1] '1.7.4'
shiny <- pak::pkg_deps_tree("shiny")
ℹ Loading metadata database
ℹ Loading metadata database
✔ Loading metadata database ... done
✔ Loading metadata database ... done
shiny 1.7.4 ✨ ⬇ (unknown size)
├─httpuv 1.6.9 ✨ ⬇ (unknown size)
│ ├─Rcpp 1.0.10 ✨ ⬇ (unknown size)
│ ├─R6 2.5.1 ✨ ⬇ (unknown size)
│ ├─promises 1.2.0.1 ✨ ⬇ (unknown size)
│ │ ├─R6
│ │ ├─Rcpp
│ │ ├─later 1.3.0 ✨ ⬇ (unknown size)
│ │ │ ├─Rcpp
│ │ │ └─rlang 1.1.0 ✨ ⬇ (unknown size)
│ │ ├─rlang
│ │ └─magrittr 2.0.3 ✨ ⬇ (unknown size)
│ └─later
├─mime 0.12 ✨ ⬇ (unknown size)
├─jsonlite 1.8.4 ✨ ⬇ (unknown size)
├─xtable 1.8-4 ✨ ⬇ (unknown size)
├─fontawesome 0.5.0 ✨ ⬇ (unknown size)
│ ├─rlang
│ └─htmltools 0.5.5 ✨ ⬇ (unknown size)
│   ├─digest 0.6.31 ✨ ⬇ (unknown size)
│   ├─base64enc 0.1-3 ✨ ⬇ (unknown size)
│   ├─rlang
│   ├─fastmap 1.1.1 ✨ ⬇ (unknown size)
│   └─ellipsis 0.3.2 ✨ ⬇ (unknown size)
│     └─rlang
├─htmltools
├─R6
├─sourcetools 0.1.7-1 ✨ ⬇ (unknown size)
├─later
├─promises
├─crayon 1.5.2 ✨ ⬇ (unknown size)
├─rlang
├─fastmap
├─withr 2.5.0 ✨ ⬇ (unknown size)
├─commonmark 1.9.0 ✨ ⬇ (unknown size)
├─glue 1.6.2 ✨ ⬇ (unknown size)
├─bslib 0.4.2 ✨ ⬇ (unknown size)
│ ├─htmltools
│ ├─jsonlite
│ ├─sass 0.4.5 ✨ ⬇ (unknown size)
│ │ ├─fs 1.6.1 ✨ ⬇ (unknown size)
│ │ ├─rlang
│ │ ├─htmltools
│ │ ├─R6
│ │ └─rappdirs 0.3.3 ✨ ⬇ (unknown size)
│ ├─jquerylib 0.1.4 ✨ ⬇ (unknown size)
│ │ └─htmltools
│ ├─rlang
│ ├─cachem 1.0.7 ✨ ⬇ (unknown size)
│ │ ├─rlang
│ │ └─fastmap
│ ├─memoise 2.0.1 ✨ ⬇ (unknown size)
│ │ ├─rlang
│ │ └─cachem
│ ├─base64enc
│ └─mime
├─cachem
├─ellipsis
└─lifecycle 1.0.3 ✨ ⬇ (unknown size)
  ├─cli 3.6.1 ✨ ⬇ (unknown size)
  ├─glue
  └─rlang

Key:  ✨ new |  ⬇ download
  1. Updating your code base

On a larger scale, adding scripts to your app for functions that can come from a dependency is an issue in terms of security and maintenance. For example, let’s imagine that tomorrow, we discover a security breach in {shiny}, or in the way {golem} bundles the resources, or any other package.

How can we be sure that all your apps are updated once we’ve built a patch? Yep, updating the packages. The solution of adding files would mean that you would have to go inside all your applications and update the files, with all the issues that come with this process (time-consuming, the risk of forgetting to update, typos…)

Dev dependencies vs runtime dependencies in {golem}

That being said, it’s true that previous versions of {golem} used to have too many hard dependencies, creating issues when sending the apps to production. In prod, many of {golem} dependencies were useless, and you don’t need them at runtime.

That’s because {golem} has two types of dependencies: dev dependencies (the packages you’re using when developing with {golem}) and runtime dependencies (the ones used when the application runs).

In version 0.3.3 of {golem}, we started removing dev dependencies. The first to go was {dockerfiler}, and now with 0.4.0 we have moved all the dev dependencies to the Suggests section of the DESCRIPTION. What that means is that if you’re using {golem} in a fresh R distribution, the dependency tree is way lighter, as you can see below. 65 dependencies for 0.3.5, and 37 for 0.4.0.

Click to show {golem@v0.3.5} dependency tree
v035 <- pak::pkg_deps_tree("thinkr-open/golem@v0.3.5")
thinkr-open/golem@v0.3.5 0.3.5 ✨👷🏽🔧 ⬇ (unknown size)
├─attempt 0.3.1 ✨ ⬇ (unknown size)
│ └─rlang 1.1.0 ✨ ⬇ (unknown size)
├─cli 3.6.1 ✨ ⬇ (unknown size)
├─config 0.3.1 ✨ ⬇ (unknown size)
│ └─yaml 2.3.7 ✨ ⬇ (unknown size)
├─crayon 1.5.2 ✨ ⬇ (unknown size)
├─desc 1.4.2 ✨ ⬇ (unknown size)
│ ├─cli
│ ├─R6 2.5.1 ✨ ⬇ (unknown size)
│ └─rprojroot 2.0.3 ✨ ⬇ (unknown size)
├─fs 1.6.1 ✨ ⬇ (unknown size)
├─here 1.0.1 ✨ ⬇ (unknown size)
│ └─rprojroot
├─htmltools 0.5.5 ✨ ⬇ (unknown size)
│ ├─digest 0.6.31 ✨ ⬇ (unknown size)
│ ├─base64enc 0.1-3 ✨ ⬇ (unknown size)
│ ├─rlang
│ ├─fastmap 1.1.1 ✨ ⬇ (unknown size)
│ └─ellipsis 0.3.2 ✨ ⬇ (unknown size)
│   └─rlang
├─pkgload 1.3.2 ✨ ⬇ (unknown size)
│ ├─cli
│ ├─crayon
│ ├─desc
│ ├─fs
│ ├─glue 1.6.2 ✨ ⬇ (unknown size)
│ ├─rlang
│ ├─rprojroot
│ └─withr 2.5.0 ✨ ⬇ (unknown size)
├─roxygen2 7.2.3 ✨ ⬇ (unknown size)
│ ├─brew 1.0-8 ✨ ⬇ (unknown size)
│ ├─cli
│ ├─commonmark 1.9.0 ✨ ⬇ (unknown size)
│ ├─desc
│ ├─knitr 1.42 ✨ ⬇ (unknown size)
│ │ ├─evaluate 0.20 ✨ ⬇ (unknown size)
│ │ ├─highr 0.10 ✨ ⬇ (unknown size)
│ │ │ └─xfun 0.37 ✨ ⬇ (unknown size)
│ │ ├─yaml
│ │ └─xfun
│ ├─pkgload
│ ├─purrr 1.0.1 ✨ ⬇ (unknown size)
│ │ ├─cli
│ │ ├─lifecycle 1.0.3 ✨ ⬇ (unknown size)
│ │ │ ├─cli
│ │ │ ├─glue
│ │ │ └─rlang
│ │ ├─magrittr 2.0.3 ✨ ⬇ (unknown size)
│ │ ├─rlang
│ │ └─vctrs 0.6.1 ✨ ⬇ (unknown size)
│ │   ├─cli
│ │   ├─glue
│ │   ├─lifecycle
│ │   └─rlang
│ ├─R6
│ ├─rlang
│ ├─stringi 1.7.12 ✨ ⬇ (unknown size)
│ ├─stringr 1.5.0 ✨ ⬇ (unknown size)
│ │ ├─cli
│ │ ├─glue
│ │ ├─lifecycle
│ │ ├─magrittr
│ │ ├─rlang
│ │ ├─stringi
│ │ └─vctrs
│ ├─withr
│ └─xml2 1.3.3 ✨ ⬇ (unknown size)
├─rstudioapi 0.14 ✨ ⬇ (unknown size)
├─shiny 1.7.4 ✨ ⬇ (unknown size)
│ ├─httpuv 1.6.9 ✨ ⬇ (unknown size)
│ │ ├─Rcpp 1.0.10 ✨ ⬇ (unknown size)
│ │ ├─R6
│ │ ├─promises 1.2.0.1 ✨ ⬇ (unknown size)
│ │ │ ├─R6
│ │ │ ├─Rcpp
│ │ │ ├─later 1.3.0 ✨ ⬇ (unknown size)
│ │ │ │ ├─Rcpp
│ │ │ │ └─rlang
│ │ │ ├─rlang
│ │ │ └─magrittr
│ │ └─later
│ ├─mime 0.12 ✨ ⬇ (unknown size)
│ ├─jsonlite 1.8.4 ✨ ⬇ (unknown size)
│ ├─xtable 1.8-4 ✨ ⬇ (unknown size)
│ ├─fontawesome 0.5.0 ✨ ⬇ (unknown size)
│ │ ├─rlang
│ │ └─htmltools
│ ├─htmltools
│ ├─R6
│ ├─sourcetools 0.1.7-1 ✨ ⬇ (unknown size)
│ ├─later
│ ├─promises
│ ├─crayon
│ ├─rlang
│ ├─fastmap
│ ├─withr
│ ├─commonmark
│ ├─glue
│ ├─bslib 0.4.2 ✨ ⬇ (unknown size)
│ │ ├─htmltools
│ │ ├─jsonlite
│ │ ├─sass 0.4.5 ✨ ⬇ (unknown size)
│ │ │ ├─fs
│ │ │ ├─rlang
│ │ │ ├─htmltools
│ │ │ ├─R6
│ │ │ └─rappdirs 0.3.3 ✨ ⬇ (unknown size)
│ │ ├─jquerylib 0.1.4 ✨ ⬇ (unknown size)
│ │ │ └─htmltools
│ │ ├─rlang
│ │ ├─cachem 1.0.7 ✨ ⬇ (unknown size)
│ │ │ ├─rlang
│ │ │ └─fastmap
│ │ ├─memoise 2.0.1 ✨ ⬇ (unknown size)
│ │ │ ├─rlang
│ │ │ └─cachem
│ │ ├─base64enc
│ │ └─mime
│ ├─cachem
│ ├─ellipsis
│ └─lifecycle
├─usethis 2.1.6 ✨ ⬇ (unknown size)
│ ├─cli
│ ├─clipr 0.8.0 ✨ ⬇ (unknown size)
│ ├─crayon
│ ├─curl 5.0.0 ✨ ⬇ (unknown size)
│ ├─desc
│ ├─fs
│ ├─gert 1.9.2 ✨ ⬇ (unknown size)
│ │ ├─askpass 1.1 ✨ ⬇ (unknown size)
│ │ │ └─sys 3.4.1 ✨ ⬇ (unknown size)
│ │ ├─credentials 1.3.2 ✨ ⬇ (unknown size)
│ │ │ ├─openssl 2.0.6 ✨ ⬇ (unknown size)
│ │ │ │ └─askpass
│ │ │ ├─sys
│ │ │ ├─curl
│ │ │ ├─jsonlite
│ │ │ └─askpass
│ │ ├─openssl
│ │ ├─rstudioapi
│ │ ├─sys
│ │ └─zip 2.2.2 ✨ ⬇ (unknown size)
│ ├─gh 1.4.0 ✨ ⬇ (unknown size)
│ │ ├─cli
│ │ ├─gitcreds 0.1.2 ✨ ⬇ (unknown size)
│ │ ├─httr2 0.2.2 ✨ ⬇ (unknown size)
│ │ │ ├─cli
│ │ │ ├─curl
│ │ │ ├─glue
│ │ │ ├─magrittr
│ │ │ ├─openssl
│ │ │ ├─R6
│ │ │ ├─rappdirs
│ │ │ ├─rlang
│ │ │ └─withr
│ │ ├─ini 0.3.1 ✨ ⬇ (unknown size)
│ │ ├─jsonlite
│ │ └─rlang
│ ├─glue
│ ├─jsonlite
│ ├─lifecycle
│ ├─purrr
│ ├─rappdirs
│ ├─rlang
│ ├─rprojroot
│ ├─rstudioapi
│ ├─whisker 0.4.1 ✨ ⬇ (unknown size)
│ ├─withr
│ └─yaml
└─yaml

Key:  ✨ new |  ⬇ download | 👷🏽 build | 🔧 compile
nrow(v035)
[1] 65
 
Click to show {golem@v0.4.0} dependency tree
v040 <- pak::pkg_deps_tree("thinkr-open/golem@v0.4.0")
thinkr-open/golem@v0.4.0 0.4.0 ✨👷🏽🔧 ⬇ (unknown size)
├─attempt 0.3.1 ✨ ⬇ (unknown size)
│ └─rlang 1.1.0 ✨ ⬇ (unknown size)
├─config 0.3.1 ✨ ⬇ (unknown size)
│ └─yaml 2.3.7 ✨ ⬇ (unknown size)
├─here 1.0.1 ✨ ⬇ (unknown size)
│ └─rprojroot 2.0.3 ✨ ⬇ (unknown size)
├─htmltools 0.5.5 ✨ ⬇ (unknown size)
│ ├─digest 0.6.31 ✨ ⬇ (unknown size)
│ ├─base64enc 0.1-3 ✨ ⬇ (unknown size)
│ ├─rlang
│ ├─fastmap 1.1.1 ✨ ⬇ (unknown size)
│ └─ellipsis 0.3.2 ✨ ⬇ (unknown size)
│   └─rlang
├─rlang
├─shiny 1.7.4 ✨ ⬇ (unknown size)
│ ├─httpuv 1.6.9 ✨ ⬇ (unknown size)
│ │ ├─Rcpp 1.0.10 ✨ ⬇ (unknown size)
│ │ ├─R6 2.5.1 ✨ ⬇ (unknown size)
│ │ ├─promises 1.2.0.1 ✨ ⬇ (unknown size)
│ │ │ ├─R6
│ │ │ ├─Rcpp
│ │ │ ├─later 1.3.0 ✨ ⬇ (unknown size)
│ │ │ │ ├─Rcpp
│ │ │ │ └─rlang
│ │ │ ├─rlang
│ │ │ └─magrittr 2.0.3 ✨ ⬇ (unknown size)
│ │ └─later
│ ├─mime 0.12 ✨ ⬇ (unknown size)
│ ├─jsonlite 1.8.4 ✨ ⬇ (unknown size)
│ ├─xtable 1.8-4 ✨ ⬇ (unknown size)
│ ├─fontawesome 0.5.0 ✨ ⬇ (unknown size)
│ │ ├─rlang
│ │ └─htmltools
│ ├─htmltools
│ ├─R6
│ ├─sourcetools 0.1.7-1 ✨ ⬇ (unknown size)
│ ├─later
│ ├─promises
│ ├─crayon 1.5.2 ✨ ⬇ (unknown size)
│ ├─rlang
│ ├─fastmap
│ ├─withr 2.5.0 ✨ ⬇ (unknown size)
│ ├─commonmark 1.9.0 ✨ ⬇ (unknown size)
│ ├─glue 1.6.2 ✨ ⬇ (unknown size)
│ ├─bslib 0.4.2 ✨ ⬇ (unknown size)
│ │ ├─htmltools
│ │ ├─jsonlite
│ │ ├─sass 0.4.5 ✨ ⬇ (unknown size)
│ │ │ ├─fs 1.6.1 ✨ ⬇ (unknown size)
│ │ │ ├─rlang
│ │ │ ├─htmltools
│ │ │ ├─R6
│ │ │ └─rappdirs 0.3.3 ✨ ⬇ (unknown size)
│ │ ├─jquerylib 0.1.4 ✨ ⬇ (unknown size)
│ │ │ └─htmltools
│ │ ├─rlang
│ │ ├─cachem 1.0.7 ✨ ⬇ (unknown size)
│ │ │ ├─rlang
│ │ │ └─fastmap
│ │ ├─memoise 2.0.1 ✨ ⬇ (unknown size)
│ │ │ ├─rlang
│ │ │ └─cachem
│ │ ├─base64enc
│ │ └─mime
│ ├─cachem
│ ├─ellipsis
│ └─lifecycle 1.0.3 ✨ ⬇ (unknown size)
│   ├─cli 3.6.1 ✨ ⬇ (unknown size)
│   ├─glue
│   └─rlang
└─yaml

Key:  ✨ new |  ⬇ download | 👷🏽 build | 🔧 compile
nrow(v040)
[1] 37
 

But that’s still 35 packages, right?

Yes, but as I said before, every {shiny} app will depend on {shiny}. So how many packages does {golem} actually add to your dependency tree? In other words, how many packages does {golem} depend on that are not already a {shiny} dependency?

setdiff(
  v040$package,
  shiny$package
)
[1] "golem"     "attempt"   "config"    "here"      "rprojroot" "yaml"     

That’s 6 additional packages. {golem} & {attempt} being packages from ThinkR, and {config}, {here}, {rprojroot}, {yaml} packages from Posit open source team.

So yes, adding a dependency adds a small risk. But now it’s up to you to decide if trying to remove {golem} from your dependency tree is worth the time, compared to the time gained while developing and deploying 😅

Note that soon, we’ll work on removing {attempt} and {here} from the dependencies, but these three being

golem::install_dev_deps()

To be sure that you have all the dependencies needed when developing, {golem} now has a install_dev_deps() function. This function will check that you have everything needed to develop, and it will not be called at deployment time.

You’ll now find this function at the top of your 01_start.R script.

Maintenance mode

One new feature I’m particularly excited about is the “Maintenance mode” option.

This feature was born out of a specific need: in production, I needed to make an application go “offline”, as I knew the db was about to migrate. That implied doing some weird changes in the UI for a short period of time, before putting the app back.

That’s when the idea of a “Maintenance mode” was born, for the times when you need your application to be unavailable: database updates, API changes, etc. When this maintenance mode is turned on, your application will be paused and a specific page will be displayed to your users. And the cool thing is that it’s just an environment variable away: you don’t need to change anything in your app.

The maintenance mode will be turned on whenever the R process detects that the GOLEM_MAINTENANCE_ACTIVE environment variable is set to TRUE. It will look like this:

Big Up to my dear colleague Arthur for developing this feature!

To visualize the maintenance page locally, you can run the following code:

withr::with_envvar(
  c("GOLEM_MAINTENANCE_ACTIVE" = TRUE),
  {
    golem::run_dev()
  }
)

Note that {golem} comes with a default maintenance page, and you can replace it with your own page by passing either an html_document or a tagList to the with_golem_options function in run_app.R:

run_app <- function(
    onStart = NULL,
    options = list(),
    enableBookmarking = NULL,
    uiPattern = "/",
    ...
) {
  with_golem_options(
    app = shinyApp(
      ui = app_ui,
      server = app_server,
      onStart = onStart,
      options = options,
      enableBookmarking = enableBookmarking,
      uiPattern = uiPattern
    ),
    golem_opts = list(...),
    maintenance_page = shiny::htmlTemplate(
      filename = app_sys(
        "custom_maintenance_page.html"
      )
    )
  )
}

Other changes

Please refer to the https://github.com/ThinkR-open/golem/releases page for a list of all the news!

Thanks

We want to thank all the people who have contributed to this release since the v0.3.1, either by opening PR, feature requests, or bug report.

@agronomofiorentini, @ajmoralesa, @ALanguillaume, @Andryas, @ArthurData, @asadow, @asbates, @asiripanich, @avsolovey, @Camil88, @campbead, @Cervangirard, @ColinFay, @connorcarolan, @D3SL, @DivadNojnarg, @dmenne, @Dobrokhotov1989, @earnaud, @em-dataIntegrityEnthusiast, @erikvona, @ethantenison, @etiennebacher, @fBedecarrats, @gabrielburcea, @ggpinto, @gioneves, @guivivi, @harell, @hedjour, @henrique1008, @ilyaZar, @isaactpetersen, @ivokwee, @jamieRowen, @jl5000, @JMPivette, @Jodavid, @JohnStaples, @Jopgood, @JulianoAtto, @KittJonathan, @KoderKow, @kyleweise, @leeroyaus, @lijian007, @MargotBr, @mark-druffel, @mcsiple, @mjbroerman, @nathansquan, @ncls33, @ncullen93, @novica, @pachadotdev, @psolymos, @pythiantech, @rezasz969, @seanhardison1, @shahreyar-abeer, @statnmap, @stheil15, @svenb78, @Swechhya, @Teebusch, @teofiln, @VincentGuyader, @waiteb5, @whipson, @wurli, @yogat3ch, and @YonghuiDong.