install.packages('devtools')
library(devtools)
The leprechaun
framework
leprechaun
apps are built much like standard R packages (built 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”
This page covers developing a new leprechaun
application using Positron, the new IDE from Posit. The current version information is below:
Positron Version: 2025.03.0 (Universal) build 116
Code - OSS Version: 1.96.0
Commit: 7f0f93873e8b24c9f393bb3586724f4783c5e72c
Date: 2025-03-01T18:51:44.014Z
Electron: 32.2.6
Chromium: 128.0.6613.186
Node.js: 20.18.1
V8: 12.8.374.38-electron.0
OS: Darwin x64 24.3.0
Getting started
Create a leprechaun
app just like you would a new R package (install devtools
, which includes usethis
as a dependency):
Loading required package: usethis
Create a new package:
::create_package("lap") usethis
✔ 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")
::scaffold() 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/
, etc.) are accompanied by multiple sub-folders in inst/
(recall that inst/
contents are available in the package when the package is installed).3
Getting Started
The following files are part of our initial application created with leprechaun::scaffold()
.
.leprechaun
The .leprechaun
lock file contains the package name, version, as well as versions of bootstrap and accompanying R files.
DESCRIPTION
shiny
,bslib
,htmltools
andpkgload
will automatically be added to theDESCRIPTION
file- The remaining
DESCRIPTION
fields need to be entered manually (or with thedesc
package). See example below:
- The remaining
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
Leave an empty final line in the DESCRIPTION
file.
As promised, leprechaun
app-packages can be spun up quickly with minimal commands. After loading and documenting lap
, we can use the standalone app function run()
to launch the app.
Development
The development workflow for a leprechaun
application is similar to developing any app-package:
usethis
functions can be used to add R files, tests, vignettes, README.md/NEWS.md files, etc.use
devtools
to load, document, and install.
Application code
Before we create any new files, we’re going to dive into the code that’s included in a new leprechaun
app-package. Most of these files are in the R/
folder, but we can also new subfolders in inst/
.
└── R/
├── _disable_autoload.R
├── assets.R
├── input-handlers.R
├── leprechaun-utils.R
├── run.R
├── server.R
├── ui.R
└── zzz.R
└── inst/
├── assets
├── dev
├── img
└── run
└── app.R
UI and Server
ui.R
holds the ui()
and assets()
functions, and server.R
includes the core application server()
function.
App UI
#' Shiny UI
#'
#' Core UI of package.
#'
#' @param req The request object.
#'
#' @import shiny
#' @importFrom bslib bs_theme
#'
#' @keywords internal
<- function(req){
ui navbarPage(
theme = bs_theme(version = 4),
header = list(assets()),
title = "lap",
id = "main-menu",
tabPanel(
"First tab",
::h1("First tab")
shiny
),tabPanel(
"Second tab",
::h1("Second tab")
shiny
)
) }
assets()
loads the resources called in the R/assets.R
file.
#' Assets
#'
#' Includes all assets.
#' This is a convenience function that wraps
#' [serveAssets] and allows easily adding additional
#' remote dependencies (e.g.: CDN) should there be any.
#'
#' @importFrom shiny tags
#'
#' @keywords internal
<- function(){
assets list(
serveAssets(), # base assets (assets.R)
$head(
tags# Place any additional depdendencies here
# e.g.: CDN
)
) }
server()
also includes make_send_message(session)
from R/leprechaun-utils.R
.
#' 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 }
Assets
The R/assets.R
functions handle how JavaScript and CSS files are loaded and served to the browser.
serveAssets()
Scans and sorts JavaScript/CSS files and bundles everything as proper HTML dependencies that Shiny can use.
remove_modules()
Filters out the module files from the list of all JavaScript files.
get_modules()
Extracts only the module files from the list of all JavaScript files.
collapse_files()
Creates a regular expression pattern to match specific files.
Expand the code chunks below to view the functions in R/assets.R
.
show/hide serveAssets()
#' Dependencies
#'
#' @param modules JavaScript files names that require
#' the `type = module`.
#' @importFrom htmltools htmlDependency
#'
#' @keywords internal
<- function(modules = NULL) {
serveAssets # JavaScript files
<- list.files(
javascript system.file(package = "lap"),
recursive = TRUE,
pattern = ".js$"
)
<- get_modules(javascript, modules)
modules <- remove_modules(javascript, modules)
javascript
# CSS files
<- list.files(
css system.file(package = "lap"),
recursive = TRUE,
pattern = ".css$"
)
# so dependency processes correctly
names(css) <- rep("file", length(css))
names(javascript) <- rep("file", length(javascript))
# serve dependencies
<- list()
dependencies
<- htmlDependency(
standard "lap",
version = utils::packageVersion("lap"),
package = "lap",
src = ".",
script = javascript,
stylesheet = css
)<- append(dependencies, list(standard))
dependencies
if (!is.null(modules)) {
<- htmlDependency(
modules "lap-modules",
version = utils::packageVersion("lap"),
package = "lap",
src = ".",
script = modules,
meta = list(type = "module")
)<- append(dependencies, list(modules))
dependencies
}
return(dependencies)
}
show/hide remove_modules()
#' Module
#'
#' Retrieve and add modules from a vector of files.
#'
#' @param files JavaScript files
#' @param modules JavaScript files names that require
#' the `type = module`.
#' @importFrom htmltools htmlDependency
#'
#' @keywords internal
#' @name js-modules
<- function(files, modules) {
remove_modules if (is.null(modules)) {
return(files)
}
# make pattern
<- collapse_files(modules)
pattern
# remove modules
!grepl(pattern, files)]
files[ }
show/hide get_modules()
#' @rdname js-modules
#' @keywords internal
<- function(files, modules) {
get_modules if (is.null(modules)) {
return(NULL)
}
# make pattern
<- collapse_files(modules)
pattern
# remove modules
grepl(pattern, files)]
files[ }
show/hide collapse_files()
# collapse files into a pattern
<- function(files) {
collapse_files <- paste0(files, collapse = "$|")
pattern paste0(pattern, "$")
}
Standalone app function
R/run.R
contains functions to launch our Shiny application in different modes.
run()
is the main function that users will call to launch our application in normal mode (production-ready).
<- function(...){
run shinyApp(
ui = ui,
server = server,
...
) }
run_dev()
is for development purposes and launches a development version of our app (with local assets, etc.).
<- function(){
run_dev <- system.file(
file "run/app.R",
package = "lap"
)::shinyAppFile(file)
shiny }
External resources
The inst/
folder contains the initial leprechaun
scaffolding folders (assets
, dev
, img
, and run
) and a single inst/run/app.R
file.4
inst/assets
This folder is for storing front-end resources like JavaScript and CSS files (are automatically discovered by the serveAssets()
function).
inst/dev
This folder contains development-related files and tools not needed in production.
inst/img
This folder contains static image files for the application. To add images to the application, R/zzz.R
contains .onLoad()
, a wrapper for system.file()
and Shiny’s addResourcePath()
.
inst/run
The inst/run/app.R
contains calls to leprechaun::build()
and pkgload::load_all()
before running the app with run()
.5
Utilities
R/leprechaun-utils.R
leprechaun-utils.R
initially contains the make_send_message()
function (which is used in the R/server.R
above).
R/input-handlers.R
leprechaun_handler_df()
andleprechaun_handler_list()
are used 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”)
R/_disable_autoload.R
_disable_autoload.R
disables Shiny’s loadSupport()
. By default, Shiny will load “any top-level supporting .R
files in the R/
directory adjacent to the app.R
/server.R
/ui.R
files.”
Expand the code chunks below to view the functions in R/leprechaun-utils.R
and R/input-handlers.R
.
show/hide make_send_message()
#' Create a Helper to Send Messages
#'
#' Create a function to send custom messages to the front-end,
#' this function makes it such that the namespace is carried
#' along.
#' The namespace is appended as `ns`.
#' The namespace with the optional hyphen is
#' included as `ns2`.
#'
#' @param session Shiny session to derive namespace
#' @param prefix A prefix to add to all types.
#' Note that the prefix is followed by a hyphen `-`.
#'
#' @examples
#' \dontrun{
#' send_message <- make_send_message(session)
#' send_message("do-sth")
#' send_message("do-sth-else", x = 1)
#'
#' # with prefix
#' send_message <- make_send_message(session, prefix = "PREFIX")
#'
#' # this sends a mesasge of type:
#' # PREFIX-so-th
#' send_message("do-sth")
#' }
#'
#' @noRd
#' @keywords internal
<- function(session, prefix = NULL) {
make_send_message <- session$ns(NULL)
ns
<- ns
ns2 if (length(ns) > 0 && ns != "") {
<- paste0(ns2, "-")
ns2
}
function(msgId, ...) {
if (!is.null(prefix)) {
<- sprintf("%s-%s", prefix, msgId)
msgId
}
$sendCustomMessage(
session
msgId,list(
ns = ns,
ns2 = ns2,
...
)
)
} }
show/hide leprechaun_handler_df()
#' Input Dataframe
#'
#' Converts the input received from the WebSocket
#' to a data.frame.
#'
#' @param data Input data received from WebSocket.
#'
#' @keywords internal
<- function(data){
leprechaun_handler_df do.call("rbind", lapply(data))
}
show/hide leprechaun_handler_list()
#' Input List
#'
#' Forces the input received from the WebSocket
#' to a list. This should really not be needed as
#' it is handled like so by default.
#'
#' @param data Input data received from WebSocket.
#'
#' @keywords internal
<- function(data){
leprechaun_handler_list return(data)
}
show/hide .onAttach()
<- function(...) {
.onAttach ::registerInputHandler(
shiny"lap.list",
leprechaun_handler_list, force = TRUE
)
::registerInputHandler(
shiny"lap.df",
leprechaun_handler_df, force = TRUE
) }
Application code recap
UI & Server
The UI and server functions in R/ui.R
and R/server
.R are the entry points of the leprechaun
app, serving as wrappers that connect the app’s modules and centralize the UI layout and server logic (similar to golem
apps).
Utility functions
leprechaun-utils.R
contains utilities that help with communication between your R server and JavaScript front-end, and R/input-handlers.R
sets up custom data handling between your JavaScript front-end and R back-end.
Assets
The functions in R/assets.R
are called with the assets()
function in R/ui.R
, which calls serveAssets()
and includes all the front-end resources in our app. The inst/assets/
folder will contain any custom JavaScript or CSS files (or SCSS/SASS files) for styling.
External resources
The inst/img/
folder will store images, which are loaded in the .onLoad()
function in R/zzz.R
file.
Writing code
Building leprechaun
apps is similar to developing an R package. leprechaun has helper functions for adding modules6 and configuration7 files, but with fewer bells and whistles than golem
.
Modules
leprechaun has an add_module()
helper function for creating modules. We’ll use it to add the two inputs and scatter display modules.
module_scatter_display
collects the data from module_vars
and module_aes
to create the plot with the custom scatter_plot()
function:
To create the initial
var_input
module we’ll run:::add_module("vars") leprechaun
- This creates
R/module_vars.R
with functions for the UI and server portions of the Shiny module:
#' vars UI #' #' @param id Unique id for module instance. #' #' @keywords internal <- function(id){ varsUI <- NS(id) ns tagList( h2("var_input") ) } #' vars Server #' #' @param id Unique id for module instance. #' #' @keywords internal <- function(id){ vars_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
{pkgname}-package.R
The leprechaun
module contents are similar to the modules created with the golem
helper function, but instead of using the @noRd
tag, leprechaun
modules include @keywords internal
, which can be used in combination with a {pkgname}-package.R
file to document your package.
- Call
usethis::use_package_doc()
- Add the following to
{pkgname}-package.R
:
#' @keywords internal
"_PACKAGE"
The code for the vars
, aes
and scatter_display
modules are below.
show/hide module_vars.R
#' vars UI
#'
#' @param id Unique id for module instance.
#'
#' @keywords internal
<- function(id){
varsUI <- 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"
)
)
}
#' vars Server
#'
#' @param id Unique id for module instance.
#'
#' @keywords internal
<- function(id){
vars_server moduleServer(
id,function(
input,
output,
session
){
<- session$ns
ns <- make_send_message(session)
send_message
return(
reactive({
list(
"y" = input$y,
"x" = input$x,
"z" = input$z
)
})
)
}
)
}
# UI
# varsUI('id')
# server
# vars_server('id')
show/hide module_aes.R
#' aes UI
#'
#' @param id Unique id for module instance.
#'
#' @keywords internal
<- function(id){
aesUI <- NS(id)
ns tagList(
sliderInput(
inputId = ns("alpha"),
label = "Alpha:",
min = 0,
max = 1,
step = 0.1,
value = 0.7
),sliderInput(
inputId = ns("size"),
label = "Size:",
min = 0,
max = 5,
step = 0.5,
value = 3
),textInput(
inputId = ns("plot_title"),
label = "Plot title",
placeholder = "Enter plot title"
)
)
}
#' aes Server
#'
#' @param id Unique id for module instance.
#'
#' @keywords internal
<- function(id){
aes_server moduleServer(
id,function(
input,
output,
session
){
<- session$ns
ns <- make_send_message(session)
send_message
return(
reactive({
list(
"alpha" = input$alpha,
"size" = input$size,
"plot_title" = input$plot_title
)
})
)
}
)
}
# UI
# aesUI('id')
# server
# aes_server('id')
show/hide module_scatter_display.R
#' scatter_display UI
#'
#' @param id Unique id for module instance.
#'
#' @keywords internal
<- function(id){
scatter_displayUI <- NS(id)
ns tagList(
$br(),
tagsplotOutput(outputId = ns("scatterplot"))
)
}
#' scatter_display Server
#'
#' @param id Unique id for module instance.
#' @param var_inputs variable inputs
#' @param aes_inputs aesthetic inputs
#'
#' @keywords internal
<- function(id, var_inputs, aes_inputs){
scatter_display_server moduleServer(
id,function(
input,
output,
session
){
<- session$ns
ns <- make_send_message(session)
send_message
<- reactive({
inputs <- tools::toTitleCase(aes_inputs()$plot_title)
plot_title list(
x = var_inputs()$x,
y = var_inputs()$y,
z = var_inputs()$z,
alpha = aes_inputs()$alpha,
size = aes_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(tools::toTitleCase(inputs()$x), "_", " "),
y = stringr::str_replace_all(tools::toTitleCase(inputs()$y), "_", " ")
+
) ::theme_minimal() +
ggplot2::theme(legend.position = "bottom")
ggplot2
})
}
)
}
# UI
# scatter_displayUI('id')
# server
# scatter_display_server('id')
The other components of lap
were created using the standard usethis
package development functions.
Utility functions
Use usethis::use_r()
or create a new file in the R/
folder to add utility functions to lap
. Expland the code chunks below to view our plotting utility function and bslib
theme:
show/hide R/utils-scatter_plot.R
#' The scatter plot utility function
#'
#' @description A custom graphing ggplot2 function
#'
#' @return The return value, if any, from executing the utility.
#'
#' @param df `data.frame` or `tibble`
#' @param x_var string variable mapped to `x` axis
#' @param y_var string variable mapped to `y` axis
#'
#'
#' @importFrom rlang .data
#'
#' @export
#'
<- 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 }
show/hide R/lap_theme.R
#' thematic leprechaun theme
#'
#' @returns bslib theme
#'
#' @export
#'
<- bslib::bs_theme(
lap_theme bg = "#ffffff",
fg = "#1a1a1a",
primary = "#3C9D5D", # green primary
secondary = "#CCCCCC",
success = "#3C9D5D",
info = "#17A2B8",
warning = "#F0AD4E",
danger = "#D9534F",
accent = "#6C757D",
base_font = bslib::font_google("Ubuntu"),
heading_font = bslib::font_google("Raleway")
)
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 a navbarPage()
layout by default, but we’ll update this with bslib
.
show/hide R/ui.R
#' Shiny UI
#'
#' Core UI of package.
#'
#' @param req The request object.
#'
#' @import shiny
#' @importFrom bslib bs_theme
#'
#' @keywords internal
<- function(req){
ui tagList(
::page_fillable(
bslibtitle = "Movie Reviews (lap)",
theme = lap_theme,
::layout_sidebar(
bslibsidebar = bslib::sidebar(
varsUI("vars"),
aesUI("aes")
),::card(
bslibfull_screen = TRUE,
::card_body(
bslibscatter_displayUI("plot")
)
)
)
)
) }
Server
The R/server.R
file contains the three module server functions.
show/hide R/server.R
#' 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
<- vars_server("vars")
selected_vars
<- aes_server("aes")
selected_aes
scatter_display_server("plot",
var_inputs = selected_vars,
aes_inputs = selected_aes)
}
By default, the R/server.R
file contains the make_send_message()
function (which will be demonstrated below).8
After loading, documenting, and installing lap
, we can launch the application with run()
:
Adding images
To add an image to the UI, we save the file (leprechaun-logo.png
) in the inst/
folder:
Save file to inst/img/
inst/
└── img/
└── leprechaun-logo.png
Then add the img/
path to the code to UI:
::card_header(
bslib$h4(tags$em("Brought to you by ",
tags$img(
tagssrc = "img/leprechaun-logo.png",
height = 100,
width = 100,
style = "margin:10px 10px"
)
)
) )
Run devtools::load_all()
, devtools::document()
, and devtools::install(upgrade = FALSE)
, then launch the application with run()
:
Assets (JavaScript)
leprechaun
combines use_*
and build
functions to add functionality to Shiny apps. The external code files are stored in the inst/
folder.
We’ll briefly cover what this looks like with the packer
JavaScript example from the package website. Be sure to install node
and packer
.
Enter the following in the Terminal:
brew update
brew install node
Install the R package:
install.packages('packer')
# or
::pak("JohnCoene/packer") pak
packer::scaffold_leprechaun()
builds the scaffolding for packer
:9
::scaffold_leprechaun()
packer
── Scaffolding leprechaun ─────────────────────────
✔ Initialiased npm-cli, webpack-merge installed with
✔ webpack, webpack"dev"
scope
✔ Added npm scripts
✔ Created srcjs directory/config directory
✔ Created srcjs
✔ Created webpack config files
── Adding files to .gitignore and .Rbuildignore ──
"/path/to/lap".
✔ Setting active project to "^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.
✔ Adding
── Scaffold built ──
`bundle` to build the JavaScript files
ℹ Run `leprechaun::use_packer()` ℹ Run
The following files and folders will be added to the lap root folder:
show/hide node_modules/ 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
Issue: Issue with packer::scaffold_leprechaun() (or maybe leprechaun::build()?
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.
Assets (Sass)
We can add Sass styling to our leprechaun
app using the use_sass()
helper function (this Sass example is from the package website).
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
Unit tests
System tests
Configure
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 -> bslib -> sass
lap
-> sass
lap
-> shinytest2 -> rmarkdown -> bslib -> sass
lap
-> 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 you can switch to afluidPage()
or change the Bootstrap version (“If shiny > 1.6 is installed defaults to version 5, otherwise version 4” ).↩︎We can remove
R/hello.R
andman/hello.Rd
files. These are just examples fromusethis::create_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).↩︎inst/run/app.R
is not run directly. Theleprechaun::add_app_file()
will create anapp.R
file for your app-package.↩︎Add a module with
leprechaun::add_module()
.↩︎Add a configuration file with
leprechaun::use_config(quiet = FALSE)
.↩︎Note the
R/server.R
function is not exported from the package by default (i.e., the@noRd
tag is included with@keywords internal
).↩︎packer::scaffold_leprechaun()
initializes thenpm
package manager for JavaScript, installswebpack
, and adds the necessary JavaScript files and folders↩︎