| Type: | Package |
| Title: | Reproducible Code for 'Shiny' Objects |
| Version: | 0.3.0 |
| Description: | Provides functionality to extract reactive expressions from a 'shiny' application and convert them into stand-alone R scripts. This enables users to reproduce tables and visualisations outside the interactive UI, facilitating integration into static reports or automated workflows without requiring access to the original application source code. |
| License: | MIT + file LICENSE |
| URL: | https://github.com/AscentSoftware/shinyreprex, https://ascentsoftware.github.io/shinyreprex/ |
| BugReports: | https://github.com/AscentSoftware/shinyreprex/issues |
| Depends: | R (≥ 4.3.0) |
| Imports: | S7, cli, constructive, purrr, renv, rlang, styler |
| Suggests: | dplyr, knitr, rmarkdown, shiny, spelling, testthat (≥ 3.0.0) |
| VignetteBuilder: | knitr |
| Config/Needs/website: | rmarkdown, ascentsoftware/acuitydown |
| Config/roxygen2/version: | 8.1.0 |
| Config/testthat/edition: | 3 |
| Encoding: | UTF-8 |
| Language: | en-GB |
| Collate: | 'Repro_S7.R' 'S7_utils.R' 'repro_call_chunk.R' 'call_chunk_function.R' 'call_chunk_generic.R' 'call_chunk_if.R' 'call_chunk_null.R' 'call_chunk_reactive.R' 'call_chunk_reactval.R' 'call_chunk_shiny.R' 'call_chunk_subset.R' 'call_chunk_switch.R' 'call_chunk_utils.R' 'repro_chunk.R' 'chunk_call.R' 'chunk_generic.R' 'chunk_reactive.R' 'package.R' 'reactive_expression.R' 'register_reactives.R' 'reprex_lockfile.R' 'reprex_reactive.R' 'walk_packages.R' |
| NeedsCompilation: | no |
| Packaged: | 2026-09-17 17:49:52 UTC; BaldAsh |
| Author: | Ashley Baldry [aut, cre] |
| Maintainer: | Ashley Baldry <ashley.baldry@acuityanalytics.com> |
| Repository: | CRAN |
| Date/Publication: | 2026-09-17 18:10:02 UTC |
Reproducible Code
Description
An S7 object that holds the code and packages required to re-create a given reactive.
Usage
Repro(code = list(), packages = character(0), prerequisites = list())
Arguments
code |
Code chunks found in a given expression |
packages |
Packages found in the function calls in the code and/or pre-requisites |
prerequisites |
Code chunks used to generate reactive objects found in the code |
Reactive Object Check
Description
Confirm every object supplied is an unevaluated reactive, raising an error that names the calling function so the message points at the user's call.
Usage
assert_reactives(reactives, fn_name)
Arguments
reactives |
A list of objects to check |
fn_name |
Name of the calling function, used in the error message |
Value
NULL, invisibly. Called for its side effect of raising an error.
Environment of a Called Reactive
Description
Find the environment holding the reactive a call refers to, checking the expression's own environment before the environment it was passed in from.
Usage
call_reactive_env(expr, env)
Arguments
expr |
An expression to check |
env |
The environment the expression belongs to |
Value
The environment holding the reactive, or NULL if the call is not to a
reactive.
Custom S7 Classes
Description
Additional classes to include in S7 to use in repro_code and
repro_code_chunk methods:
Reactives
These variables need to be handled in a specific way to extract non-static values stored in reactive calls.
- class_reactive
The class capturing
shiny::reactive()calls- class_event_cache
The class capturing
shiny::bindCache()calls- class_event_reactive
The class capturing
shiny::bindEvent()calls- class_bind_reactive
-
The union of
class_event_cacheandclass_event_reactive
Special Functions
When determining evaluating a chunk, the function name gets attached to the class of the chunk, these are special cases that need to be handled in a non-standard way.
- class_call_function
The class capturing anonymous function definitions
- class_call_reactive
The class capturing evaluated
shiny::reactive()objects- class_call_reactval
-
The class capturing evaluated
shiny::reactiveValues()objects - class_call_reactval_setter
-
The class capturing
shiny::reactiveVal()setter calls, which are skipped with a warning as they cannot be reproduced outside of Shiny - class_call_if
The class capturing
ifcalls- class_call_switch
The class capturing
switchcalls- class_call_null
The class capturing undefined calls, such as
pkg::fn- class_call_shiny
-
The class capturing ignorable shiny function calls such as
shiny::req()andshiny::validate() - class_call_subset
The class capturing a subset (
$) or a double-bracket subset ([[) call
Reactive Variables Definition Check
Description
A helper function to check whether or not the reactive variables to be added
to the Repro object already exists. Used to avoid duplicate definitions being
added to a script.
Usage
is_new_reactive(new, exisitng)
Arguments
new, exisitng |
A named list of reactive variable definitions |
Value
A boolean stating whether or not there is at least one reactive definition
in new that doesn't exist in existing
Call Checks
Description
A set of helper functions that determine what type of call is being made within an expression.
is_reactive_call checks whether or not the call is evaluating a
shiny::reactive variable.
is_reactive_val_call checks whether or not the call is evaluating a
shiny::reactiveVal variable.
is_reactive_val_setter_call checks whether or not the call is setting the value
of a shiny::reactiveVal variable.
is_reactive_values_call checks whether or not the call is evaluating an item
within a shiny::reactiveValues variable.
is_any_reactive_call checks whether or not the call points to evaluating a
reactive, reactiveVal or reactiveValues.
is_variable_call checks whether or not the call point to a variable that is defined
within the given module.
is_input_call checks whether or not the call points to evaluate an input value.
is_session_user_data checks whether or not the call points to evaluate an object
within session$userData
Usage
is_reactive_call(x, env = rlang::caller_env())
is_reactive_val_call(x, env = rlang::caller_env())
is_reactive_val_setter_call(x, env = rlang::caller_env())
is_reactive_values_call(x, env = rlang::caller_env())
is_any_reactive_call(x, env = rlang::caller_env())
is_variable_call(x, existing_vars = NULL, env = rlang::caller_env())
is_input_call(x)
is_session_user_data(x)
Arguments
x |
An R call object |
env |
The environment the call is being made, by default it is the environment calling the check, but is likely the environment the call is being made i.e. the reactive expression. |
existing_vars |
A character vector of variable definitions that exist in the |
Value
A boolean value determining whether or not the call check has passed.
Reactive Expression and Environment
Description
Extract the unevaluated body of a reactive, along with the environment it was assigned in.
Environments
The Observable object attached to the given reactive is extracted. Within the
Observable, the .origFunc contains the environment that the reactive expression
was created - the parent environment being the module that the reactive is assigned
in. This allows the variables in the module to be found and set as pre-requisites
for the given reactive.
If bindCache or bindEvent are used, then the environment found is the call within
the relevant function. To get to the module environment, we find that the reactive
is assigned as "wrappedFunc", so that is used to find the module environment.
Usage
reactive_expression(x)
Arguments
x |
A |
Value
A list with two elements: body, the unevaluated body of the reactive, and
env, the module environment the reactive was assigned in.
Register Reactives for Reproduction
Description
Record reactives against the current Shiny session, so that
reprex_packages() and reprex_lockfile() can be called with no arguments
and still cover the whole application.
This avoids having to return reactives out of every module purely so a single top-level call can see them. Each module registers what it owns, and the session holds the collection.
Namespacing
Registrations are namespaced by the calling module, so two modules may register reactives of the same name without collision, and re-registering the same name in the same module replaces the previous entry rather than adding a duplicate.
When to Register
Registering does not evaluate the reactive. Packages are resolved by reading
the expression held in the reactive, so it may be registered while still
gated behind shiny::req() or inputs that have yet to be set. Registering at
module setup is therefore both safe and preferred, as it does not depend on
the user having visited the output first.
Session Scope
The collection lives on the session, so it is discarded when the session ends and is never shared between concurrent users of the same application.
Usage
register_reactives(..., session = shiny::getDefaultReactiveDomain())
Arguments
... |
One or more |
session |
The Shiny session to register against. Defaults to the current reactive domain, which inside a module is that module's session. |
Value
The registered reactives, invisibly, as a named list.
See Also
reprex_packages() and reprex_lockfile(), which read the
registered collection when called with no reactives.
Examples
library(shiny)
summaryServer <- function(id) {
moduleServer(id, function(input, output, session) {
summary_tbl <- reactive(purrr::keep(iris, is.numeric))
register_reactives(summary_tbl)
summary_tbl
})
}
# Elsewhere in the application, with no reactives threaded through:
# reprex_lockfile()
Registered Reactives
Description
The reactives registered against a session by register_reactives().
Usage
registered_reactives(session)
Arguments
session |
A Shiny session object, or |
Value
A named list of reactives, empty if none have been registered.
Create a Lockfile to Reproduce Reactives
Description
Capture the exact package versions, sources and R version needed to reproduce
one or more shiny::reactive objects, as an renv lockfile. Restoring the
lockfile with renv::restore() recreates the environment the reactives were
generated in, including the recursive dependency tree.
A single lockfile covers every reactive passed, so an application can offer one download that reproduces all of its outputs.
Snapshot Scope
The snapshot is taken from the currently loaded library (.libPaths()), so
the versions recorded are exactly those the running Shiny session used to
produce the reactives. renv resolves and records the full recursive
dependency tree of packages, so only the top-level set needs to be supplied.
Isolation
The snapshot runs against a throwaway temporary project, so it never writes
renv infrastructure into, or otherwise modifies, the application's own
directory.
Output Path
A relative lockfile is resolved against the working directory, not against
the temporary project used for the snapshot. The default therefore writes
renv.lock into whichever directory the application is running from. Pass an
absolute path to control where it lands; inside a
shiny::downloadHandler() that is the file argument.
Usage
reprex_lockfile(
...,
packages = NULL,
lockfile = "renv.lock",
exclude = NULL,
session = shiny::getDefaultReactiveDomain()
)
Arguments
... |
One or more |
packages |
Character vector of package names to snapshot. If |
lockfile |
Path to write the lockfile to. Defaults to |
exclude |
Character vector of package names to omit from the snapshot. |
session |
The Shiny session to read registered reactives from. Only used
when |
Value
The absolute path the lockfile was written to, invisibly.
See Also
reprex_packages() to inspect the detected set without writing a
lockfile, and register_reactives() to record reactives from within each module.
Examples
library(shiny)
numeric_iris <- reactive(purrr::keep(iris, is.numeric))
isolate(reprex_lockfile(numeric_iris, lockfile = tempfile(fileext = ".lock")))
Packages Required to Reproduce Reactives
Description
Extract the set of non-base packages needed to reproduce one or more
shiny::reactive objects, with duplicates removed across every reactive
passed.
Use this when building a custom UI around reprex_lockfile(): it gives you
the list of detected packages to present to the user for selection.
Packages are found by reading the expression held in each reactive rather
than by generating its script, so every branch of an if or switch
contributes. The result may therefore be a superset of the library() calls
reprex_reactive() emits for the branch actually taken, on the basis that a
lockfile is safer holding a package that is not needed than missing one.
Usage
reprex_packages(..., session = shiny::getDefaultReactiveDomain())
Arguments
... |
One or more |
session |
The Shiny session to read registered reactives from. Only used
when |
Value
A character vector of unique package names. Base packages are excluded, as they require no installation.
See Also
reprex_lockfile() to turn this set into an renv lockfile, and
register_reactives() to record reactives from within each module.
Examples
library(shiny)
numeric_iris <- reactive(purrr::keep(iris, is.numeric))
styled_code <- reactive(styler::style_text("1 + 1"))
# Outside a running application, isolate supplies the reactive context
isolate(reprex_packages(numeric_iris, styled_code))
Reproduce Code
Description
Construct the code within a given shiny::reactive object
to be able to re-create the output outside of a Shiny session.
Usage
reprex_reactive(x)
Arguments
x |
|
Value
A character string, that when printed (using base::cat),
displays the script that reproduces the contents of x.
Examples
library(shiny)
ui <- fluidPage(
h1("Reproducible Code Example"),
inputPanel(
sliderInput(
"min_width",
"Minimum Petal Width",
min(iris$Petal.Width),
max(iris$Petal.Width),
min(iris$Petal.Width),
step = 0.1
),
selectInput(
"summary_fn",
"Summary Function",
c("Mean" = "mean", "Median" = "median", "SD" = "sd"),
selected = "mean"
)
),
fluidRow(
column(
width = 5,
h2("Table"),
tableOutput("table")
),
column(
width = 7,
h2("Code"),
verbatimTextOutput("code")
)
)
)
server <- function(input, output, session) {
iris_filt <- reactive({
iris[with(iris, Petal.Width > input$min_width), ]
})
summary_tbl <- reactive({
aggregate(
Sepal.Width ~ Species,
data = iris_filt(),
FUN = get(input$summary_fn)
)
})
output$table <- renderTable(summary_tbl())
output$code <- renderText(reprex_reactive(summary_tbl))
}
if (interactive()) {
shinyApp(ui, server)
}
Session Reactive Store
Description
Fetch (creating on first use) the environment holding this session's registered reactives. An environment is used so that registrations from module servers mutate a single shared collection.
Usage
reprex_store(session)
Arguments
session |
A Shiny session object |
Value
An environment with a reactives element, a named list of reactives.
Reproduce Code Chunk
Description
Evaluate a chunk of code to extract Shiny inputs and reactives, replacing the inputs with the values selected by the user, and the reactives with the code bodies used to generate them.
Usage
repro_chunk(x, repro_code = Repro(), env = rlang::caller_env())
Arguments
x |
|
repro_code |
A |
env |
The environment |
Details
Whilst a default is provided to env, it is unlikely that this is the same environment
x is defined in. This allows the top-level reprex_reactive call to pass through
environments found for calls to other reactives in the chunk.
Value
A Repro object containing all the necessary code and packages to recreate
the provided expression when evaluated.
Collect Packages From an Expression
Description
Walk an expression and collect the packages of every call found within it,
without reproducing any of the code. This is a deliberately cheap alternative
to repro_chunk() for the cases that only need the package list, such as
reprex_packages() and reprex_lockfile().
Why Not Reproduce the Code
Reproducing a chunk substitutes reactive and input values into the
expression, which means constructing every module variable as code via
constructive::construct. For a module holding a data frame that dominates
the run time, and all of it is discarded when only the packages are wanted.
Branches Are Not Evaluated
Every branch of an if or switch is walked, rather than evaluating the
condition and following only the branch that would be taken. A lockfile is
safer for containing a package that turns out not to be needed than for
missing one, and not evaluating keeps the walk both faster and independent of
the current input values.
Usage
walk_packages(expr, env, seen = character(), packages = character())
Arguments
expr |
An expression to walk |
env |
The environment the expression belongs to, used to identify calls to other reactives |
seen |
Names of reactives already walked, preventing a reactive that is referenced more than once from being walked repeatedly |
packages |
Packages collected so far |
Value
A character vector of unique package names, excluding base packages.