JS and R, what a clickbait! Come for JS, stay for our posts about Solaris and WinBuilder. 😉 No matter how strongly you believe in JavaScript being the language of the future (see below), you might still gain from using it in your R practice, be it back-end or front-end.
In this blog post, Garrick Aden-Buie and I share a roundup of resources around JavaScript for R package developers.
JavaScript in your R package
Why and how you include JavaScript in your R package?
Bundling JavaScript code
JavaScript’s being so popular these days, you might want to bundle JavaScript code with your package. Bundling instead of porting (i.e. translating to R) JavaScript code might be a huge time gain and less error-prone (your port would be hard to keep up-to-date with the original JavaScript library).
The easiest way to interface JavaScript code from an R package is using the V8 package. From its docs, “A major advantage over the other foreign language interfaces is that V8 requires no compilers, external executables or other run-time dependencies. The entire engine is contained within a 6MB package (2MB zipped) and works on all major platforms.” V8 documentation includes a vignette on how to use JavaScript libraries with V8. Some examples of use include the js package, “A set of utilities for working with JavaScript syntax in R”; jsonld for working with, well, JSON-LD where LD means Linked Data; slugify (not on CRAN) for creating slugs out of strings.
For another approach, depending on a local NodeJS and Node Package Manager (NPM) installation, see Colin Fay’s blog post “How to Write an R Package Wrapping a NodeJS Module”. An interesting read about NPM and R, even if you end up going the easier V8 route.
JavaScript for your package documentation
Now, maybe you’re not using JavaScript in your R package at all, but you might want to use it to pimp up your documentation! Here are some examples for inspiration. Of course, they all only work for the HTML documentation, in a PDF you can’t be that creative.
Manual
The roxygenlabs package, that is an incubator for experimental roxygen features, includes a way to add JS themes to your documentation. With its default JS script, your examples gain a copy-paste button!
Noam Ross once described a way to include a searchable table in reference pages, with DT.
In writexl docs, the infamous Clippy makes an appearance. It triggers a tweet nearly once a week, which might be a way to check people are reading the docs?
For actual analytics in manual pages, it seems the unknown package found a trick by adding a script from statcounter.
Vignettes
In HTML vignettes, you can also use web dependencies. On a pkgdown website, you might encounter some incompatibilities between your, say, HTML widgets, and Boostrap (that powers pkgdown).
Web dependency management
HTML Dependencies
A third, and most common, way in which you as an R package developer might interact with JavaScript is to repackage web dependencies, such as JavaScript and CSS libraries, that enhance HTML documents and Shiny apps!
For that, you’ll want to learn about the htmltools package, in particular for its htmlDependency()
function.
As Hadley Wickham describes in the Managing JavaScript/CSS dependencies section of Mastering Shiny, an HTML dependency object describes a single JavaScript/CSS library, which often contains one or more JavaScript and/or CSS files and additional assets.
As an R package author providing reusable web components for Shiny or R Markdown, in Hadley’s words, you “absolutely should be using HTML dependency objects rather than calling tags$link()
, tags$script()
, includeCSS()
, or includeScript()
directly.”
htmlDependency()
There are two main advantages to using htmltools::htmlDependency()
.
First, HTML dependencies can be included with HTML generated with htmltools, and htmltools will ensure that the dependencies are loaded only once per page, even if multiple components appear on a page.
Second, if components from different packages depend on the same JavaScript or CSS library, htmltools can detect and resolve conflicts and load only the most recent version of the same dependency.
Here’s an example from the applause package. This package wraps applause-button, a zero-configuration button for adding applause/claps/kudos to web pages and blog posts. It was also created to demonstrate how to package a web component in an R package using htmltools. For a full walk through of the package development process, see the dev log in the package README.
html_dependency_applause <- function() {
htmltools::htmlDependency(
name = "applause-button",
version = "3.3.2",
package = "applause",
src = c(
file = "applause-button",
href = "https://unpkg.com/applause-button@3.3.2/dist"
),
script = "applause-button.js",
stylesheet = "applause-button.css"
)
}
The HTML dependency for applause-button
is provided in the html_dependency_applause()
function.
htmltools tracks all of the web dependencies being loaded into a document, and conflicts are determined by the name
of the dependency where the highest version
of a dependency will be loaded.
For this reason, it’s important for package authors to use the package name as known on npm or GitHub and to ensure that the version
is up to date.
Inside the R package source, the applause button dependencies are stored in inst/applause-button.
applause
└── inst
└── applause-button
├── applause-button.js
└── applause-button.css
The package
, src
, and script
or stylesheet
arguments work together to locate the dependency’s resources: htmlDependency()
finds the package
’s installation directory (i.e. inst/
), then finds the directory specified by src
, where the script
(.js
) and/or stylesheet
(.css
) files are located.
The src
argument can be a named vector or a single character of the directory in your package’s inst
folder.
If src
is named, the file
element indicates the directory in the inst
folder, and the href
element indicates the URL to the containing folder on a remote server, like a CDN.
To ship dependencies in your package, copy the dependencies into a sub-directory of inst
in your package (but not inst/src
or inst/lib
, these are reserved directory names1).
As long as the dependencies are a reasonable size2, it’s best to include the dependencies in your R package so that an internet connection isn’t strictly required
. Users who want to explicitly use the version hosted at a CDN can use shiny::createWebDependency().
Finally, it’s important that the HTML dependency be provided by a function and not stored as a variable in your package namespace.
This allows htmltools to correctly locate the dependency’s files once the package is installed on a user’s computer.
By convention, the function providing the dependency object is typically prefixed with html_dependency_
.
Using an HTML dependency
Functions that provide HTML dependencies like html_dependency_applause()
aren’t typically called by package users.
Instead, package authors provide UI functions that construct the HTML tags required for the component, and the HTML dependency is attached to this, generally by including the UI and the dependency together in an htmltools::tagList()
.
applause_button <- function(...) {
htmltools::tagList(
applause_button_html(...),
html_dependency_applause()
)
}
Note that package authors can and should attach HTML dependencies to any tags produced by package functions that require the web dependencies shipped by the package.
This way, users don’t need to worry about having to manually attach dependencies and htmltools will ensure that the web dependency files are added only once to the output.
This way, for instance, to include a button, using the applause
package an user only needs to type in e.g. their Hugo blog post3 or Shiny app:
applause::button()
Some web dependencies only need to be included in the output document and don’t require any HTML tags.
In these cases, the dependency can appear alone in the htmltools::tagList()
, as in this example from xaringanExtra::use_webcam().
The names of these types of functions commonly include the use_
prefix.
use_webcam <- function(width = 200, height = 200, margin = "1em") {
htmltools::tagList(
html_dependency_webcam(width, height)
)
}
JS and package robustness
How do you test JS code for your package, and how do you test your package that helps managing JS dependencies? We’ll simply offer some food for thought here. If you bundle or help bundling an existing JS library, be careful to choose dependencies as you would with R packages. Check the reputation and health of that library (is it tested?). If you are packaging your own JS code, also make sure you use best practice for JS development. 😉 Lastly, if you want to check how using your package works in a Shiny app, e.g. how does that applause button turn out, you might find interesting ideas in the book “Engineering Production-Grade Shiny Apps” by Colin Fay, Sébastien Rochette, Vincent Guyader and Cervan Girard, in particular the quote “instead of deliberately clicking on the application interface, you let a program do it for you”.
Learning and showing JavaScript from R
Now, what if you want to learn JavaScript? Besides the resources that one would recommend to any JS learner, there are interesting ones just for you as R user!
Learning materials
The resources for learning we found are mostly related to Shiny, but might be relevant anyway.
-
Materials from the RStudio conf 2020 workshop about JS for Shiny lead by Garrick Aden-Buie
-
Really only for Shiny, see the documentation about packaging JavaScript in Shiny apps
Literate JavaScript programming
As an R user, you might really appreciate literate R programming. You’re lucky, you can actually use JavaScript in R Markdown.
At a basic level, knitr
includes a JavaScript chunk engine that writes the code in JavaScript chunks marked with ```{js}
into a <script>
tag in the HTML document.
The JS code is then rendered in the browser when the reader opens the output document!
Now, what about executing JS code at compile time i.e. when knitting? For that the experimental bubble package provides a knitr engines that uses Node to run JavaScript chunks and insert the results in the rendered output.
The js4shiny package blends of the above approaches in html_document_js(), an R Markdown output for literate JavaScript programming. In this case, JavaScript chunks are run in the reader’s browser and console outputs and results are written into output chunks in the page, mimicking R Markdown’s R chunks.
Different problem, using JS libraries in Rmd documents
More as a side-note let us mention the htmlwidgets package for adding elements such as leaflet maps to your HTML documents and Shiny apps.
Playground
When learning a new language, using a playground is great. Did you know that the js4shiny package provides a JS playground you can use from RStudio? Less new things at once if you already use RStudio, so more confidence for learning!
And if you’d rather stick to the command line, bubble can launch a Node terminal where you can interactively run JavaScript, just like the R console.
R from JavaScript?
Before we jump to the conclusion, let us mention a few ways to go the other way round, calling R from JavaScript…
Shiny, “an R package that makes it easy to build interactive web apps straight from R.”, and the golemverse, a set of packages for developing Shiny apps as packages
OpenCPU is “An API for Embedded Scientific Computing” that can allow you to use JS and R together.
If you use the plumber R package to make a web API out of R code, you can then interact with that API from e.g. Node.
Colin Fay wrote an experimental Node package for calling R.
Conclusion
In this post we went over some resources useful to R package developers looking to use JavaScript code in the backend or docs of their packages, or to help others use JavaScript dependencies. Do not hesitate to share more links or experience in the comments below!
-
Refer to the R packages book by Hadley Wickham and Jenny Bryan for a full list of reserved directory names. ↩︎
-
For instance, remember that for CRAN, “neither data nor documentation should exceed 5MB”. ↩︎
-
And some setup work to use HTML widgets, see
hugodown
docs. ↩︎