Skip to contents

The idea

Sharing R code usually means asking someone to install something. livelink asks nothing: it packs your code into a URL, and the recipient clicks it and is looking at a running R session in their browser.

There is no server and no upload. The code is compressed and encoded into the part of the URL after the #, which browsers never send anywhere. The link is the payload.

Your code is serialized to JSON, gzipped, base64-encoded, and placed in the fragment of a webR URL after the #. Opening the link runs it in the browser.

Two destinations are supported:

  • webR: a full R console and editor in the browser.
  • Shinylive: Shiny apps, R or Python, with no server.

Pass some code. Get a link.

In a rendered document like this one the object becomes a clickable link, so you can open it right from the page. At an interactive console it prints its details instead, and as.character() gives you the bare URL string either way.

You can skip the quotes entirely and hand it a braced expression, which is easier to write and lets your editor highlight the code:

webr_repl_link({
  data(mtcars)
  plot(mtcars$mpg, mtcars$wt)
})

Comments and expression input

Expression input reconstructs your code from R’s source references. Those exist in an interactive session, but not when a document is knitted, so comments inside { } are dropped from the link when this vignette is rendered.

If your code has comments worth keeping, pass it as a string or a file path, or write it as a chunk. See Links inside documents.

You can also point at a file:

script <- tempfile(fileext = ".R")
writeLines(c("# fit a model", "lm(mpg ~ wt, data = mtcars)"), script)

webr_repl_link(script)

…or, in an interactive session, copy code to your clipboard and call webr_repl_link() with no arguments at all.

Making it run on arrival

By default the recipient sees your code, ready to run. Set autorun = TRUE and it runs the moment the page opens, which is right for a demo and wrong for an exercise.

webr_repl_link("hist(rnorm(1000))", autorun = TRUE)

Choosing what they see

webR opens with four panels: an editor for your code, a terminal for the R console, a files browser, and a plot pane. panels names the ones you want, and the rest never appear.

webR has four panels. panels names the ones you want, and the rest never appear. Omit it and all four are shown.

webr_repl_link("plot(1:10)", panels = c("editor", "plot"))

The names are joined into the URL’s query string, ?mode='editor-plot', so you can read a link and tell what the recipient will land on. Omit panels and webR shows everything:

Trimming the panels is worth doing when the terminal would only invite someone to wander off. Teaching with livelink leans on it.

A Shiny app instead

shinylive_r_link() does the same for a Shiny application.

app <- "
library(shiny)
ui <- fluidPage(
  sliderInput('bins', 'Bins:', 1, 50, 30),
  plotOutput('hist')
)
server <- function(input, output) {
  output$hist <- renderPlot(hist(faithful$waiting, breaks = input$bins))
}
shinyApp(ui, server)
"

shinylive_r_link(app, mode = "app")

mode = "app" shows the running application. mode = "editor" shows the code beside it, which is what you want when the point is to teach. Python apps work the same way through shinylive_py_link().

Why a string, and not a braced expression?

shinylive_r_link({ ... }) does work. But a Shiny app is source text destined for another runtime, and putting library(shiny) inside braces makes it look like R code your own package runs: R CMD check scans vignettes for library() calls and will report shiny as an undeclared dependency of livelink, which it is not.

Braces are for R you want executed in webR. For a Shiny app, a string or a file path says what you mean.

Nothing here is magic, and the URL is readable if you know where to look.

A webR URL in five parts: the base address, the version, the panels to show, the #code= fragment carrying your files, and the flags saying how the payload was encoded.

The flags at the end say how the payload was encoded: j for JSON, z for gzip, m for msgpack, u for uncompressed, a for autorun. livelink writes jz, and webR’s own share button writes mz. livelink reads both.

Going the other way

Because the files live in the URL, any link can be turned back into files, with no network access and no cooperation from a server.

url <- as.character(webr_repl_link("plot(1:10)"))

preview_webr_link(url)
#> 
#> ── webR Link Preview ──
#> 
#> URL:
#> <https://webr.r-wasm.org/latest/#code=eJyLrlbKS8xNVbJSKk4uyiwo0QtS0lEqSCzJAIroZ%2BTnpuqXpybFlxanFukjKShJrSgBKijIyS%2FRMLQyNNBUqo0FAJecF%2Fo%3D&jz>
#> 
#> Files: 1
#> Total size: 10 bytes
#> Version: "latest"
#> Encoding: "jz"
#> 
#> 'script.R' (10 bytes)
#> 
#> Use print(preview, show_content = TRUE) to see file contents

preview_webr_link() inspects a link without writing anything, which is the sensible way to open a link someone sent you. decode_webr_link() extracts the files once you have decided you trust them.

If you write in Quarto or R Markdown, a chunk can carry its own link. Add livelink: true to an ordinary chunk and it keeps running exactly as before, output and plots and all, with a link added underneath:

```{r}
#| livelink: true
#| autorun: true
data(mtcars)
plot(mtcars$mpg, mtcars$wt)   # a comment that survives
```

Your comments reach the link, which they do not when a { } expression is knitted. Links inside documents covers this properly: the hook, the engine, every chunk option, and how to set them once for a whole document.

Where to next