
Recording demos for a package README
recording-for-readme.RmdThe single most common reason to reach for vhsR is to ship an
animated demo at the top of a package’s README. This vignette walks
through the canonical workflow — from a fresh record_demo()
call to a GIF that GitHub renders inline.
The 30-second version
record_demo({
x <- 1:5
mean(x)
head(cars, 3)
}, output = "man/figures/demo.gif")Then in your README.md (or README.Rmd):
That’s it. The recording runs once on your machine, gets committed to
the repository, and renders inline on GitHub, pkgdown, and CRAN’s HTML
package page (where supported). Treat the GIF as you would any other
artefact under man/figures/ — refresh it when the demo’s
content changes, otherwise leave it alone.
Using README.Rmd and the vhsr knitr
engine
If you maintain a README.Rmd, vhsR registers a
vhsr knitr engine on package load that records and
image-includes in one step. Inside README.Rmd:
``` vhsr
x <- 1:5
mean(x)
head(cars, 3)
```
When you run devtools::build_readme() (or knit by hand),
the chunk:
- records a GIF at
output=; - emits a markdown image-include in the rendered
README.md.
Forwarded chunk options are output,
backend, and any pacing or styling argument accepted by
tape_options() (e.g. width,
height, font_size, theme,
typing_speed, line_pause,
start_pause, paragraph_pause). See
?tape_options for the full list. Anything else is
ignored.
Pipe chains type the way you wrote them
Multi-line |> pipelines record verbatim — the
recording shows the pipe form you typed, not the desugared nested-call
form R’s parser produces internally:
record_demo({
library(dplyr)
mtcars |>
filter(cyl == 6) |>
arrange(desc(mpg))
}, output = "man/figures/demo.gif")Same goes for blank lines: an empty line between two statements stays
empty in the recording, which composes nicely with
paragraph_pause when you want a beat between sections.
Where the GIF lives, and what to commit
man/figures/ is the convention. roxygen, pkgdown, and
CRAN’s package page all know to look there. Commit the recorded GIF:
- Recording is slow (5–30 seconds per run) and CI typically doesn’t have vhs installed — there’s no point regenerating on every push.
- A committed GIF is bit-stable across clones, so a reader’s view of your README never depends on a CI artefact.
- When the demo content changes, refresh by re-running
record_demo()(or rebuilding the README) and committing the new GIF.
A common make target keeps the workflow explicit:
demo: man/figures/demo.gif
man/figures/demo.gif: scripts/demo.R
R -e 'vhsR::record_demo_file("scripts/demo.R", output = "$@")'
File-size budget
GitHub renders inline GIFs up to about 10 MB but anything over 1–2 MB starts to feel slow on a slow connection. To stay small:
- Keep the demo short. 4–8 lines of code is plenty.
-
Trim the dimensions.
width = 1000, height = 500is a good balance. -
Speed up typing.
typing_speed = "40ms"(instead of the"60ms"default) shaves a meaningful amount of frames.
If the GIF is still too big, switch to .mp4 or
.webm:
record_demo({...}, output = "man/figures/demo.mp4")GitHub’s README renderer doesn’t autoplay video, but pkgdown and any
HTML render do. Embed it with vhsr_widget():
vhsr_widget("man/figures/demo.mp4")…which produces a
<video controls loop muted playsinline> tag tree
suitable for a chunk with results = "asis".
Common gotchas
-
macOS install.
vhsr_install()auto-downloadsvhson macOS but printsbrew installlines forttydandffmpeg(those have no usable upstream binaries on macOS). The recorder doesn’t care whether the binaries came from brew or the cache. -
CRAN doesn’t ship
man/figures/. Files there are only included in the built tarball if they’re referenced from a help page (\figure{...}in an.Rd). For a README image this almost never matters — the README on GitHub references the same file directly — but be aware if you also reference it from package documentation. -
CI rarely has vhs. Don’t let
devtools::check()re-record the README at check time. Theeval = FALSEchunks in this vignette, combined with committed figures, are the safe pattern.
See also
-
vignette("pacing-and-feel", package = "vhsR")— making a recording feel less robotic. -
vignette("tape-scripts", package = "vhsR")— when you outgrowrecord_demo(). -
?record_demo,?record_demo_file,?vhsr_widget.