::create_package("lap") usethis
The leprechaun
framework
leprechaun
apps are built much like standard R packages (with devtools
and usethis
), but they’re designed with the intention of being a ‘leaner and smaller’ version of golem
:1
“it generates code and does not make itself a dependency of the application you build; this means applications are leaner, and smaller”
Getting started
Create a leprechaun
app just like you would a new R package:
✔ Creating '../projects/lap/'
✔ Setting active project to '/Users/mjfrigaard/projects/lap'
✔ Creating 'R/'
✔ Writing 'DESCRIPTION'
✔ Writing 'NAMESPACE'
✔ Writing 'lap.Rproj'
✔ Adding '^lap\\.Rproj$' to '.Rbuildignore'
✔ Adding '.Rproj.user' to '.gitignore'
✔ Adding '^\\.Rproj\\.user$' to '.Rbuildignore'
✔ Opening '/Users/mjfrigaard/projects/lap/' in new RStudio session
After the new project opens, install and load the leprechaun
package, then run leprechaun::scaffold()
:2
install.packages("leprechaun")
library(leprechaun)
::scaffold(ui = "fluidPage") leprechaun
Package files
leprechaun::scaffold()
results in the following folder tree:
├── DESCRIPTION
├── NAMESPACE
├── R/
│ ├── _disable_autoload.R
│ ├── assets.R
│ ├── hello.R
│ ├── input-handlers.R
│ ├── leprechaun-utils.R
│ ├── run.R
│ ├── server.R
│ ├── ui.R
│ └── zzz.R
├── inst/
│ ├── assets
│ ├── dev
│ ├── img
│ └── run
│ └── app.R
├── lap.Rproj
└── man
└── hello.Rd
8 directories, 14 files
The standard R package files and folders (DESCRIPTION
, NAMESPACE
, R/
, and lap.Rproj
) are accompanied by multiple sub-folders in inst/
(recall that inst/
contents are available in the package when the package is installed).3
leprechaun
files
The initial application files are created using leprechaun::scaffold()
, which takes the following options as function arguments:
ui
controls the application layout (can be"fluidPage"
or"navbarPage"
, defaults to"navbarPage"
)bs_version
Bootstrap version (“If shiny > 1.6 is installed defaults to version 5, otherwise version 4” )overwrite
: Overwrite all files?
The output from leprechaun::scaffold()
is below:
── Scaffolding leprechaun app ─────────────────────────────────────────
── Creating lock file ──
✔ Creating .leprechaun
The .leprechaun
lock file contains the package name, version, as well as versions of bootstrap and accompanying R files:
{
"package": "lap",
"version": "1.0.0",
"bs_version": 5,
"r": {
"ui": "1.0.0",
"assets": "1.0.0",
"run": "1.0.0",
"server": "1.0.0",
"leprechaun-utils": "1.0.0",
"zzz": "1.0.0",
"inputs": "1.0.0"
}
}
leprechaun::scaffold()
will automatically add shiny
, bslib
, htmltools
and pkgload
to the DESCRIPTION
file:
── Adding dependencies ──
✔ Adding 'shiny' to Imports in DESCRIPTION
✔ Adding 'bslib' to Imports in DESCRIPTION
✔ Adding 'htmltools' to Imports in DESCRIPTION
✔ Adding 'pkgload' to Suggests in DESCRIPTION
The remaining DESCRIPTION
fields need to be entered manually (or with the desc
package). See example below:
Package: lap
Title: leprechaun app-package
Version: 0.0.0.9000
Author: John Smith <John.Smith@email.io> [aut, cre]
Maintainer: John Smith <John.Smith@email.io>
Description: A movie-review leprechaun shiny application.
License: GPL-3
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
LazyData: true
Imports:
bslib,
htmltools,
shiny
Suggests:
pkgload
- 1
-
Leave an empty line in the
DESCRIPTION
file
App code
The application code files are created in the R/
folder:
── Generating code ──
✔ Creating R/ui.R
✔ Creating R/assets.R
✔ Creating R/run.R
✔ Creating R/server.R
✔ Creating R/leprechaun-utils.R
✔ Creating R/_disable_autoload.R
✔ Creating R/zzz.R
✔ Creating R/input-handlers.R
ui.R
holds theui()
andassets()
functions.assets()
loads the resources called in theR/assets.R
file (seeserveAssets()
function above).assets.R
: contains theserveAssets()
function, which will identify the modules using CSS or JavaScript and createdependencies
, a list of metadata on the apprun.R
contains functions for running the production (run()
) and development version of the application (run_dev()
):server.R
by default createssend_message
withmake_send_message(session)
(seeR/leprechaun-utils.R
above).leprechaun-utils.R
initially contains themake_send_message()
function (which is used in theR/server.R
below)_disable_autoload.R
disables Shiny’sloadSupport()
. By default, Shiny will load “any top-level supporting.R
files in theR/
directory adjacent to theapp.R
/server.R
/ui.R
files.”zzz.R
contains.onLoad()
, a wrapper forsystem.file()
and Shiny’saddResourcePath()
and function (used for adding images to the application ininst/img/
).input-handlers.R
: containsleprechaun_handler_df()
andleprechaun_handler_list()
for “converting the input received from the WebSocket to a data.frame/list”.onAttach()
registers the two input handlers aboveregisterInputHandler()
: “When called, Shiny will use the function provided to refine the data passed back from the client (after being deserialized byjsonlite
) before making it available in the input variable of theserver.R
file”)
The initical call to devtools::document()
will create documentation for the following files and includes RoxygenNote 7.2.3
in the DESCRIPTION
:
ℹ Updating lap documentation
First time using roxygen2. Upgrading automatically...
Setting `RoxygenNote` to "7.2.3"
ℹ Loading lap
Warning: Skipping NAMESPACE
✖ It already exists and was not generated by roxygen2.
Writing serveAssets.Rd
Writing js-modules.Rd
Writing leprechaun_handler_df.Rd
Writing leprechaun_handler_list.Rd
Writing run.Rd
Writing run_dev.Rd
Writing ui.Rd
Writing assets.Rd
Warning: Skipping NAMESPACE
✖ It already exists and was not generated by roxygen2.
Documentation completed
- 1
-
The
NAMESPACE
file was initially withexportPattern("^[[:alpha:]]+")
. To override this pattern and have the NAMESPACE file generated byroxygen2
, delete theNAMESPACE
and re-rundevtools::document()
.
The inst/
folder
The inst/
folder contains the initial leprechaun
scaffolding folders and inst/run/app.R
file:
✔ Creating inst/dev
✔ Creating inst/assets
✔ Creating inst/img
✔ Creating inst/run/app.R
The
inst/run/app.R
contains calls toleprechaun::build()
andpkgload::load_all()
before running the app withrun()
show/hide inst/run/app.R
# do not deploy from this file # see leprechaun::add_app_file() ::build() leprechaun ::load_all( pkgloadpath = "../../", reset = TRUE, helpers = FALSE ) run()
inst/run/app.R
is not run directly. Theleprechaun::add_app_file()
will create anapp.R
file for your app-package.
assets
,dev
, andimg
will initially contain.gitkeep
files (a convention used by developers to force Git to include an otherwise empty directory in a repository). Each of these files will be demonstrated in the sections below.
The .leprechaun
lock file is also added to the .Rbuildignore
── Ignoring files ──
✔ Adding '^\\.leprechaun$' to '.Rbuildignore'
Building leprechaun
apps
Building leprechaun
apps is similar to developing an R package. New code is placed in the R/
folder, and application resources (CSS, SASS, JavaScript files) are added using one of the leprechaun::use_*
functions:
More assets can be added using the leprechaun::use_packer()
function.
Modules
leprechaun has an add_module()
helper function for creating modules:
To create the initial
var_input
module we’ll run:::add_module("var_input") leprechaun
- This creates
R/module_var_input.R
with functions for the UI and server portions of the Shiny module:
#' var_input UI #' #' @param id Unique id for module instance. #' #' @keywords internal <- function(id){ var_inputUI <- NS(id) ns tagList( h2("var_input") ) } #' var_input Server #' #' @param id Unique id for module instance. #' #' @keywords internal <- function(id){ var_input_server moduleServer( id,function( input, output, session ){ <- session$ns ns <- make_send_message(session) send_message # your code here } ) } # UI # var_inputUI('id') # server # var_input_server('id')
- This creates
Note the send_message <- make_send_message(session)
in var_input_server()
. We’ll cover how this is used in the JavaScript section below.
@keywords internal
The module contents are similar to golem
, but instead of using the @noRd
tag, these functions include @keywords internal
(which can be used to document your package).
The code for the var_input
and plot_display
modules are below.
- The
R/module_var_input.R
file:plot_dispay
collects the data fromvar_input
and creates the plot with the customscatter_plot()
function:
<- function(id) {
var_inputUI <- NS(id)
ns tagList(
selectInput(
inputId = ns("y"),
label = "Y-axis:",
choices = c(
"IMDB rating" = "imdb_rating",
"IMDB number of votes" = "imdb_num_votes",
"Critics Score" = "critics_score",
"Audience Score" = "audience_score",
"Runtime" = "runtime"
),selected = "audience_score"
),selectInput(
inputId = ns("x"),
label = "X-axis:",
choices = c(
"IMDB rating" = "imdb_rating",
"IMDB number of votes" = "imdb_num_votes",
"Critics Score" = "critics_score",
"Audience Score" = "audience_score",
"Runtime" = "runtime"
),selected = "imdb_rating"
),selectInput(
inputId = ns("z"),
label = "Color by:",
choices = c(
"Title Type" = "title_type",
"Genre" = "genre",
"MPAA Rating" = "mpaa_rating",
"Critics Rating" = "critics_rating",
"Audience Rating" = "audience_rating"
),selected = "mpaa_rating"
),sliderInput(
inputId = ns("alpha"),
label = "Alpha:",
min = 0, max = 1, step = 0.1,
value = 0.5
),sliderInput(
inputId = ns("size"),
label = "Size:",
min = 0, max = 5,
value = 2
),textInput(
inputId = ns("plot_title"),
label = "Plot title",
placeholder = "Enter plot title"
)
)
}
<- function(id) {
var_input_server moduleServer(id, function(input, output, session) {
<- session$ns
ns <- make_send_message(session)
send_message
# your code here
return(
reactive({
list(
"y" = input$y,
"x" = input$x,
"z" = input$z,
"alpha" = input$alpha,
"size" = input$size,
"plot_title" = input$plot_title
)
})
)
}
) }
- The
R/module_plot_display.R
file
<- function(id) {
plot_displayUI <- NS(id)
ns tagList(
$br(),
tags$blockquote(
tags$em(
tags$h6(
tags"The code for this application comes from the ",
$a("Building web applications with Shiny",
tagshref = "https://rstudio-education.github.io/shiny-course/"
),"tutorial"
)
)
),plotOutput(outputId = ns("scatterplot"))
)
}<- function(id, var_inputs) {
plot_display_server moduleServer(id, function(input, output, session) {
<- session$ns
ns <- make_send_message(session)
send_message
<- reactive({
inputs <- tools::toTitleCase(var_inputs()$plot_title)
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
)
})$scatterplot <- renderPlot({
output<- scatter_plot(
plot # data --------------------
df = movies,
x_var = inputs()$x,
y_var = inputs()$y,
col_var = inputs()$z,
alpha_var = inputs()$alpha,
size_var = inputs()$size
)+
plot ::labs(
ggplot2title = inputs()$plot_title,
x = stringr::str_replace_all(
::toTitleCase(inputs()$x), "_", " "),
toolsy = stringr::str_replace_all(
::toTitleCase(inputs()$y), "_", " ")) +
tools::theme_minimal() +
ggplot2::theme(legend.position = "bottom")
ggplot2
})
}
) }
The other components of lap
were created using the standard usethis
package development functions.
Utility functions
usethis::use_r()
was used to create the R/plot_display-utils.R
file that holds the scatter_plot()
function:
<- function(df, x_var, y_var, col_var, alpha_var, size_var) {
scatter_plot ::ggplot(data = df,
ggplot2::aes(x = .data[[x_var]],
ggplot2y = .data[[y_var]],
color = .data[[col_var]])) +
::geom_point(alpha = alpha_var, size = size_var)
ggplot2
}
After creating the modules and utility function, adding these to the UI (R/ui.R
) and server (R/server.R
) is straightforward.
UI
The R/ui.R
file will contain the fluidPage()
layout functions and module UI server functions:
#' Shiny UI
#'
#' Core UI of package.
#'
#' @param req The request object.
#'
#'
#' @keywords internal
<- function(req) {
ui fluidPage(
theme = bslib::bs_theme(version = 5),
assets(),
h1("lap"),
## New code -->
sidebarLayout(
sidebarPanel(
var_inputUI("vars")
),mainPanel(
# new image
$img(
tagssrc = "img/leprechaun.jpg",
height = "25%",
width = "25%"),
plot_displayUI("plot")
)
)## New code <--
) }
Server
The R/server.R
file contains the module server functions.
#' Server
#'
#' Core server function.
#'
#' @param input,output Input and output list objects
#' containing said registered inputs and outputs.
#' @param session Shiny session.
#'
#' @noRd
#' @keywords internal
<- function(input, output, session){
server
<- make_send_message(session)
send_message
## New code -->
<- var_input_server("vars")
selected_vars
plot_display_server("plot", var_inputs = selected_vars)
## New code <--
}
By default, the R/server.R
file contains the make_send_message()
function (which will be demonstrated below).4
Data
After calling usethis::use_data_raw('movies')
, I can use system.file()
to locate the movies.RData
file with the following code in data-raw/movies.R
:
## code to prepare `movies` dataset goes here
<- system.file('extdata/movies.RData', package = 'lap')
pth load(pth)
::use_data(movies, overwrite = TRUE) usethis
Launch
To launch the application in lap
with an app.R
file, leprechaun
has a add_app_file()
function. This creates a file similar to inst/run/app.R
:
# Launch the ShinyApp
# do not remove to keep push deploy button
# from RStudio
::load_all(
pkgloadreset = TRUE,
helpers = FALSE
)
run()
Now I can run devtools::load_all()
, devtools::document()
, restart and load the package, then run()
Unit tests
The unit tests for the scatter_plot()
utility function and the module server functions are in the tests/testthat/
folder:
tests
├── testthat
│ ├── test-module_plot_display.R
│ ├── test-module_var_input.R
│ └── test-utils_scatter_plot.R
└── testthat.R
2 directories, 4 files
leprechaun
relies on the testthat
framework for testing.5 I’ve included the BDD functions (describe()
and it()
) to make each behavior and test clear.
System tests
System tests can also be included with shinytest2
. The examples in lap
are similar to those in the golem
application.
tests/
├── testthat/
│ ├── _snaps/
│ │ ├── app-feature-01
│ │ └── shinytest2
│ ├── setup-shinytest2.R
│ ├── test-app-feature-01.R
│ └── test-shinytest2.R
└── testthat.R
5 directories, 13 files
Adding files and images
We’ll demonstrate how the inst/
folder works by adding an image to the application. Lets assume I want to add leprechaun.jpg
to my UI:
Start by adding the file to
inst/img/
:inst/ └── img/ └── leprechaun.jpg <- new image file!
Then add the
img/
path to the code to UI:$img( tagssrc = "img/leprechaun.jpg", height = "25%", width = "25%")
Once again, run devtools::load_all()
and devtools::document()
, restarting and loading the package, then run the application with run()
:
Adding resources
leprechaun
stores external code files the inst/
folder (similar to the golem
framework), but uses a combination of use_*
and build()
functions to add functionality to you app.
Using packer
To demonstrate how packer
and leprechaun
work together, we’ll walk through the JavaScript example from the package website step-by-step:
First we have to build the scaffolding for packer
by running packer::scaffold_leprechaun()
:
::scaffold_leprechaun() packer
packer::scaffold_leprechaun()
initializes thenpm
package manager for JavaScript, installswebpack
, and adds the necessary JavaScript files and folders:── Scaffolding leprechaun ────────────────────────────────────── ✔ Initialiased npm ✔ webpack, webpack-cli, webpack-merge installed with scope "dev" ✔ Added npm scripts ✔ Created srcjs directory ✔ Created srcjs/config directory ✔ Created webpack config files ── Adding files to .gitignore and .Rbuildignore ── ✔ Setting active project to '/projects/apps/sfw/_apps/lap' ✔ Adding '^srcjs$' to '.Rbuildignore' ✔ Adding '^node_modules$' to '.Rbuildignore' ✔ Adding '^package\\.json$' to '.Rbuildignore' ✔ Adding '^package-lock\\.json$' to '.Rbuildignore' ✔ Adding '^webpack\\.dev\\.js$' to '.Rbuildignore' ✔ Adding '^webpack\\.prod\\.js$' to '.Rbuildignore' ✔ Adding '^webpack\\.common\\.js$' to '.Rbuildignore' ✔ Adding 'node_modules' to '.gitignore' ── Scaffold built ── ℹ Run `bundle` to build the JavaScript files ℹ Run `leprechaun::use_packer()`
- The following files and folders will be added to the lap root folder:
show/hide packer::scaffold_leprechaun() files
├── node_modules/ │ ├── @discoveryjs │ ├── @jridgewell │ ├── @types │ ├── @webassemblyjs │ ├── @webpack-cli │ ├── @xtuc │ ├── acorn │ ├── acorn-import-assertions │ ├── ajv │ ├── ajv-keywords │ ├── browserslist │ ├── buffer-from │ ├── caniuse-lite │ ├── chrome-trace-event │ ├── clone-deep │ ├── colorette │ ├── commander │ ├── cross-spawn │ ├── electron-to-chromium │ ├── enhanced-resolve │ ├── envinfo │ ├── es-module-lexer │ ├── escalade │ ├── eslint-scope │ ├── esrecurse │ ├── estraverse │ ├── events │ ├── fast-deep-equal │ ├── fast-json-stable-stringify │ ├── fastest-levenshtein │ ├── find-up │ ├── flat │ ├── function-bind │ ├── glob-to-regexp │ ├── graceful-fs │ ├── has-flag │ ├── hasown │ ├── import-local │ ├── interpret │ ├── is-core-module │ ├── is-plain-object │ ├── isexe │ ├── isobject │ ├── jest-worker │ ├── json-parse-even-better-errors │ ├── json-schema-traverse │ ├── kind-of │ ├── loader-runner │ ├── locate-path │ ├── merge-stream │ ├── mime-db │ ├── mime-types │ ├── neo-async │ ├── node-releases │ ├── p-limit │ ├── p-locate │ ├── p-try │ ├── path-exists │ ├── path-key │ ├── path-parse │ ├── picocolors │ ├── pkg-dir │ ├── punycode │ ├── randombytes │ ├── rechoir │ ├── resolve │ ├── resolve-cwd │ ├── resolve-from │ ├── safe-buffer │ ├── schema-utils │ ├── serialize-javascript │ ├── shallow-clone │ ├── shebang-command │ ├── shebang-regex │ ├── source-map │ ├── source-map-support │ ├── supports-color │ ├── supports-preserve-symlinks-flag │ ├── tapable │ ├── terser │ ├── terser-webpack-plugin │ ├── undici-types │ ├── update-browserslist-db │ ├── uri-js │ ├── watchpack │ ├── webpack │ ├── webpack-cli │ ├── webpack-merge │ ├── webpack-sources │ ├── which │ └── wildcard ├── package-lock.json ├── package.json ├── srcjs/ │ ├── config │ ├── index.js │ └── modules ├── webpack.common.js ├── webpack.dev.js └── webpack.prod.js 106 directories, 36 files
Now that the scaffolding is in place, run leprechaun::use_packer()
:
::use_packer() leprechaun
leprechaun::use_packer()
creates theinst/dev/packer.R
and addspacker
to theDESCRIPTION
. Theinst/dev/packer.R
file contains the following:#' Bundle for Prod #' #' Bundles packer using packer. <- function(){ packer_bundle <- requireNamespace("packer", quietly = TRUE) has_packer if (!has_packer) { warning( "Requires `packer` package: `install.packages('packer')`\n", "Skipping.", call. = FALSE )return() } ::bundle() packer } packer_bundle()
The final step is to build or ‘bundle’ the JavaScript files with leprechaun::build()
::build() leprechaun
leprechaun::build()
runs the contents ofinst/dev/packer.R
to bundle the JavaScript code:✔ Running packer.R ✔ Bundled
Lets review the new files that have been added to the lap
:
In the
inst/dev/
folder, thepacker.R
file has been added, which callspacker::bundle()
inst/dev/ └── packer.R 1 directory, 1 file
In the
srcjs/
folder, themodules/message.js
andindex.js
create the alert withShiny.addCustomMessageHandler
srcjs/ ├── config │ ├── entry_points.json │ ├── externals.json │ ├── loaders.json │ ├── misc.json │ └── output_path.json ├── index.js └── modules └── message.js
// srcjs/modules/message.js export const message = (msg) => { alert(msg); }// srcjs/index.js import { message } from './modules/message.js'; import 'shiny'; // In shiny server use: // session$sendCustomMessage('show-packer', 'hello packer!') .addCustomMessageHandler('show-packer', (msg) => { Shinymessage(msg.text); })
To use the JS message scripts in srcjs/
, I add the following to R/server.R
:
<- make_send_message(session)
send_message send_message("show-packer",
text = "this message is from your R/server.R file")
After running devtools::load_all()
and devtools::document()
, the application loads with an alert:
We can also include messages from modules.
- In
R/module_plot_display.R
<- make_send_message(session)
send_message send_message("show-packer",
text = "this is a message from your plot_display module")
Read more about sending JavaScript messages here on the shiny website.
Using Sass
We can add Sass styling to our leprechaun
app using the use_sass()
helper function (this Sass example is from the package website).
- Run
leprechaun::use_sass()
:
::use_sass() leprechaun
- This will add files to
assets/
anddev/
and we see the following messages:
✔ Creating scss
✔ Creating inst/dev/sass.R
✔ Adding 'sass' to Suggests in DESCRIPTION
✔ Adding '^scss$' to '.Rbuildignore'
! This requires `leprechaun::build()` or the `leprechaun::build_roclet`
- Below are the new files in
scss/
:
scss
├── _core.scss
└── main.scss
1 directory, 2 files
The scss/
folder created by leprechaun::use_sass()
includes _core.scss
and main.scss
.
_core.scss
: the original file is below
html{.error {
color: red
} }
- We’ll change the
color:
fromred
to green (#38B44A
) using$accent: #38B44A;
$accent: #38B44A;
html{
h1 {color: $accent;
} }
- Then run
leprechaun::build()
::build() leprechaun
✔ Running packer.R
✔ Bundled
✔ Running sass.R
The
inst/dev/sass.R
file contains asass_build()
functionsass_build()
looks in thescss/
folder formain.scss
and creates theinst/assets/style.min.css
file.
#' Build CSS #' #' Build the sass <- function() { sass_build <- requireNamespace("sass", quietly = TRUE) has_sass if (!has_sass) { warning( "Requires `sass` package: `install.packages('sass')`\n", "Skipping.", call. = FALSE )return() } <- sass::sass( output ::sass_file( sass"scss/main.scss" ),cache = NULL, options = sass::sass_options( output_style = "compressed" ),output = "inst/assets/style.min.css" )invisible(output) } sass_build()
Once again, I run devtools::load_all()
, devtools::document()
, install and restart, then load the package and run()
lap
with new Sass
leprechaun::build()
The assets/
folder contains the files generated by the .R
scripts in the dev/
folder.
“Do not call this function from within the app. It helps build things, not run them.” -
build.md
guide
Contents of
inst/assets/
:inst/assets/ ├── index.js └── style.min.css 1 directory, 2 files
Contents of
inst/dev/
:inst/dev/ ├── packer.R └── sass.R 1 directory, 2 files
inst/dev/sass.R
createsinst/assets/style.min.css
andinst/dev/packer.R
createsinst/assets/index.js
serveAssets()
After running leprechaun::use_sass()
and leprechaun::build()
, we’ll check the serveAssets()
function:
:::serveAssets() lap
[[1]]
List of 10
$ name : chr "lap"
$ version : chr "0.0.0.9000"
$ src :List of 1
..$ file: chr "."
$ meta : NULL
$ script : Named chr "assets/index.js"
..- attr(*, "names")= chr "file"
$ stylesheet: Named chr [1:2] "assets/style.min.css" "html/R.css"
..- attr(*, "names")= chr [1:2] "file" "file"
$ head : NULL
$ attachment: NULL
$ package : chr "lap"
$ all_files : logi TRUE
- attr(*, "class")= chr "html_dependency"
This shows that stylesheet
has been updated with "assets/style.min.css"
and script
has been updated with "assets/index.js"
(these files are loaded into the application when it runs).
Configure
leprechaun
app configuration files use the config package (similar to golem
). leprechaun
doesn’t assume I’ll be using a config.yml
file, but we can easily add one with leprechaun::use_config()
.
use_config()
adds ainst/config.yml
andR/config.R
The default value in the
config.yml
files isproduction: true
, which can be read usingconfig_read()
inR/config.R
.config_read()
$production [1] TRUE
- Values can be added to
inst/config.yml
using the config file format, then theCONFIG_FILE
can be set as an environmental variable
- Values can be added to
Dependencies
lap
depends on shiny
, but not leprechaun
.
::local_dev_deps_explain(
pakdeps = "shiny",
root = "_apps/lap")
-> shiny
lap
-> shinytest2 -> shiny lap
::local_dev_deps_explain(
pakdeps = "leprechaun",
root = "_apps/lap")
x leprechaun
However, adding functionality and features with packer
and the use_*
functions can add dependencies to your leprechaun
app:
::local_dev_deps_explain(
pakdeps = "packer",
root = "_apps/lap")
-> packer lap
::local_dev_deps_explain(
pakdeps = "sass",
root = "_apps/lap")
-> bslib -> sass
lap
-> shiny -> bslib -> sass
lap
-> packer -> htmlwidgets -> rmarkdown ->
lap
-> sass
bslib
-> sass
lap
-> shinytest2 -> rmarkdown -> bslib ->
lap
sass
-> shinytest2 -> shiny -> bslib -> sass lap
The final folder tree for lap
(a leprechaun
app-package) is below.
├── DESCRIPTION
├── NAMESPACE
├── R/
│ ├── _disable_autoload.R
│ ├── assets.R
│ ├── config.R
│ ├── endpoint-utils.R
│ ├── html-utils.R
│ ├── input-handlers.R
│ ├── leprechaun-utils.R
│ ├── module_plot_display.R
│ ├── module_var_input.R
│ ├── run.R
│ ├── server.R
│ ├── ui.R
│ ├── utils-js.R
│ ├── utils_scatter_plot.R
│ └── zzz.R
├── README.md
├── app.R
├── data/
│ └── movies.rda
├── data-raw/
│ └── movies.R
├── inst/
│ ├── assets
│ ├── config.yml
│ ├── dev
│ ├── extdata
│ ├── img
│ └── run
├── lap.Rproj
├── node_modules/
├── package-lock.json
├── package.json
├── scss/
│ ├── _core.scss
│ └── main.scss
├── srcjs/
│ ├── config
│ ├── index.js
│ ├── leprechaun-utils.js
│ └── modules
├── tests/
│ ├── testthat
│ └── testthat.R
├── webpack.common.js
├── webpack.dev.js
└── webpack.prod.js
109 directories, 63 files
- 1
-
leprechaun
apps are packages, so theinst/
folders are available to the application at runtime. - 2
-
Files and folders created with
packer::scaffold_leprechaun()
The output from system.file(".", package = "lap")
has been passed to fs::dir_tree()
below:
├── DESCRIPTION
├── INDEX
├── Meta/
│ ├── Rd.rds
│ ├── data.rds
│ ├── features.rds
│ ├── hsearch.rds
│ ├── links.rds
│ ├── nsInfo.rds
│ └── package.rds
├── NAMESPACE
├── R/
│ ├── lap
│ ├── lap.rdb
│ └── lap.rdx
├── assets/
│ ├── index.js
│ └── style.min.css
├── data/
│ ├── Rdata.rdb
│ ├── Rdata.rds
│ └── Rdata.rdx
├── dev/
│ ├── packer.R
│ └── sass.R
├── extdata/
│ └── movies.RData
├── help/
│ ├── AnIndex
│ ├── aliases.rds
│ ├── lap.rdb
│ ├── lap.rdx
│ └── paths.rds
├── html/
│ ├── 00Index.html
│ └── R.css
├── img/
│ └── leprechaun.jpg
└── run/
└── app.R
As we can see, the files and folders created with packer::scaffold_leprechaun()
won’t be installed with the source code.
Recap
leprechaun
delivers on its promise to be a ‘leaner and smaller’ version of golem.
Most of the features in golem
are also accessible in leprechaun
:
Adding modules:
leprechaun
’sadd_module()
function doesn’t have the consistent naming or prefixes found ingolem::add_module()
, but still reduces a lot of typing if you are creating these files manually.Adding functions: leprechaun relies on
usethis::use_r()
for adding new functionality to your applicationleprechaun
doesn’t come with any testing functions, although this can be done usingtestthat
andshinytest2
(just as we would with a standard R package).
Multiple inst/
sub-folders makes adding assets to the application easier, and leprechaun
has a long list of use_*
functions for including Sass, CSS, HTML, and JavaScript. The package website has examples for getting started and adding multiple resources, but unfortunately the function reference had limited documentation.
Below is an overview of the features/functions in the leprechaun
framework:
Feature | Arguments/Options | Description/Comments |
---|---|---|
scaffold() |
|
The initial ‘setup’ function that builds the leprechaun files in R/ and inst/ |
scaffold_leprechaun() (from packer ) |
|
This function comes from the This creates the following non-standard R package folders and files:
These non-standard folders are added to the |
use_packer() |
Assumes scaffold_leprechaun() from packer has been run |
Sets up application to use
|
build() |
Returns TRUE /FALSE if build was successful. |
Used to ‘bundle’ various resources (i.e., from packer and the other use_ functions) |
use_sass() |
Creates Adds |
|
use_html_utils() |
Adds R/html-utils.R |
Adds
|
use_endpoints_utils() |
Adds R/endpoint-utils.R |
Adds
|
use_js_utils() |
Adds Adds import statement to |
Requires running leprechaun::build() to bundle .js |
use_config() |
Adds
|
Adds
|
add_module() |
Using
|
It would be nice if the modules had consistent naming (the UI function uses By default, modules aren’t exported (i.e., with Each module automatically includes:
|
add_app_file() |
Adds Includes call to |
Handy for quickly launching the app during development (and deployment). |
.onLoad() |
Used to add external files to app (images, html, etc.) | Combines Shiny’s addResourcePath() and system.file() . |
serveAssets() |
modules argument can be used to include JavaScript modules |
Adds dependencies for JavaScript, CSS, and HTML. |
Footnotes
The
leprechaun::scaffold()
defaults to anavbarPage()
, but I’ve switched to afluidPage()
for this example.↩︎We can remove
R/hello.R
andman/hello.Rd
files. These are just examples fromusethis::create_package()
.↩︎Note the
R/server.R
function is not exported from the package by default (i.e., the@noRd
tag is included with@keywords internal
).↩︎testthat
can be used for testing utility functions and module functions (as shown here).↩︎