Skip to content

Commit

Permalink
Set webR options from within Quarto Document YAML (#13)
Browse files Browse the repository at this point in the history
* Add old code cell type to check against

* Add more documentation to the README file

* Bump embedded workers to the 0.1.1 version

* Re-write the initialization portion of WebR to allow for user-specified variables in the quarto document's meta field

* Update the template file with new YAML options

* Bump the extension

* Fix code cell specification

* Better description

* Remove log diagnostics
  • Loading branch information
coatless authored Apr 5, 2023
1 parent 288faab commit 7c605ee
Show file tree
Hide file tree
Showing 8 changed files with 434 additions and 86 deletions.
68 changes: 56 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ filters:
- webr
```
Then, place the code for `webr` in a code block marked with `{webr}`
Then, place the R code for `webR` in a code block marked with `{webr-r}`

````markdown
---
Expand All @@ -52,33 +52,75 @@ summary(fit)
````


When `quarto render` or `quarto preview` is called, the filter will execute under the `jupyter` compute engine if `engine: knitr` is not specified.
When `quarto render` or `quarto preview` is called, the filter will execute under `engine: knitr`.
During the execution, the filter adds two files to the working directory: `webr-worker.js` and `webr-serviceworker.js`. These files allow for the
webR session to be started and must be present with the rendered output.
`webR` session to be started and must be present with the rendered output.

**Note:** If `engine: knitr` is not specified, then the `jupyter` compute engine will be used by default.

### Packages

By default, the `quarto-webr` extension avoids loading or requesting additional packages. Additional packages can be added by including:
By default, the `quarto-webr` extension avoids loading or requesting additional packages. Additional packages can be added
when the document is first opened or on per-code cell basis. You can view what packages are available by either executing
the following R code (either with WebR or just R):

```r
webr::install("package")
available.packages(repos="https://repo.r-wasm.org/", type="source")
```

For example, to install `ggplot2`, you would need to use:
Or, by navigating to the WebR repository:

```r
webr::install("ggplot2")
<https://github.com/r-wasm/webr-repo/blob/main/repo-packages>


#### Install on document open

Add to the document header YAML the `packages` key under `webr` with each package listed using an array, e.g.

```yaml
---
webr:
packages: ['ggplot2', 'dplyr']
---
```

You can view what packages are available by either executing the following R code (either with WebR or just R):
#### Install on an as needed basis

Packages may also be installed inside of a code cell through the built-in [`webr::install()` function](https://docs.r-wasm.org/webr/latest/packages.html#example-installing-the-matrix-package). For example, to install `ggplot2`, you would need to use:

```r
available.packages(repos="https://repo.r-wasm.org/", type="source")
webr::install("ggplot2")
```

Or, by navigating to the WebR repository:
### Customizing webR from the Quarto Extension

<https://github.com/r-wasm/webr-repo/blob/main/repo-packages>
The `quarto-webr` extension supports specifying the following `WebROptions` options:

- `home-dir`: The WebAssembly user’s home directory and initial working directory ([`Documentation`](https://docs.r-wasm.org/webr/latest/api/js/interfaces/WebR.WebROptions.html#homedir)). Default: `'/home/web_user'`.
- `base-url`: The base URL used for downloading R WebAssembly binaries. ([`Documentation`](https://docs.r-wasm.org/webr/latest/api/js/interfaces/WebR.WebROptions.html#baseurl)). Default: `'https://webr.r-wasm.org/[version]/'`.
- `service-worker-url`: The base URL from where to load JavaScript worker scripts when loading webR with the ServiceWorker communication channel mode ([`Documentation`](https://docs.r-wasm.org/webr/latest/api/js/interfaces/WebR.WebROptions.html#serviceworkerurl)). Default: `''`.

The extension also has native options for:

- `show-startup-message`: Display in the document header the state of WebR initialization. Default: `true`
- `show-header-message`: Display in the document header whether COOP and COEP headers are in use for faster page loads. Default: `false`

For these options to be active, they must be placed underneath the `webr` entry in the documentation header, e.g.

```markdown
---
title: WebR in Quarto HTML Documents
format: html
engine: knitr
webr:
show-startup-message: false
show-header-message: false
home-dir: '/home/r-user/'
packages: ['ggplot2', 'dplyr']
filters:
- webr
---
```

## Known Hiccups

Expand All @@ -96,6 +138,8 @@ If `webr-worker.js` or `webr-serviceworker.js` are not found when the document l
└── webr-worker.js
```

Still having trouble? Try specifying where the worker files are located using the `service-worker-url` option in the document's YAML header.

### Directly accessing rendered HTML

When using `quarto preview` or `quarto render`, the rendered HTML document is being shown by mimicking a server running under `https://localhost/`. Usually, everything works in this context assuming the above directory structure is followed. However, if you **directly** open the rendered HTML document, e.g. `demo-quarto-web.html`, inside of a Web Browser, then the required WebR components cannot be loaded for security reasons. You can read a bit more about the problem in this [StackOverflow answer](https://stackoverflow.com/questions/6811398/html5-web-workers-work-in-firefox-4-but-not-in-chrome-12-0-742-122/6823683#6823683).
Expand Down
2 changes: 1 addition & 1 deletion _extensions/webr/_extension.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: webr
title: Embedded webr code cells
author: James Joseph Balamuta
version: 0.0.4
version: 0.1.0
quarto-required: ">=1.2.198"
contributes:
filters:
Expand Down
13 changes: 10 additions & 3 deletions _extensions/webr/template.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
title: "WebR-enabled code cell"
format: html
engine: knitr
#webr:
# show-startup-message: false # Display status of webR initialization
# show-header-message: false # Check to see if COOP&COEP headers are set for speed.
# packages: ['ggplot2', 'dplyr'] # Pre-install dependencies
# home-dir: "/home/rstudio" # Customize where the working directory is
# base-url: '' # Base URL used for downloading R WebAssembly binaries
# service-worker-url: '' # URL from where to load JavaScript worker scripts when loading webR with the ServiceWorker communication channel.
filters:
- webr
---
Expand All @@ -10,15 +17,15 @@ filters:

This is a webr-enabled code cell in a Quarto HTML document.

```{webr}
```{webr-r}
1 + 1
```

```{webr}
```{webr-r}
fit = lm(mpg ~ am, data = mtcars)
summary(fit)
```

```{webr}
```{webr-r}
plot(pressure)
```
88 changes: 84 additions & 4 deletions _extensions/webr/webr-init.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,97 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/mode/r/r.js"></script>
<script type="module">

// Start a timer
const initializeWebRTimerStart = performance.now();

import { WebR } from "https://webr.r-wasm.org/v0.1.0/webr.mjs";

// Determine if we need to install R packages
var installRPackagesList = [{{INSTALLRPACKAGESLIST}}];
// Check to see if we have an empty array, if we do set to skip the installation.
var setupRPackages = !(installRPackagesList.indexOf("") !== -1);

// Display a startup message?
var showStartupMessage = {{SHOWSTARTUPMESSAGE}};
var showHeaderMessage = {{SHOWHEADERMESSAGE}};
if (showStartupMessage) {

// Create the outermost div element
var quartoTitleMeta = document.createElement("div");
quartoTitleMeta.classList.add("quarto-title-meta");

// Create the first inner div element
var firstInnerDiv = document.createElement("div");

// Create the second inner div element with "WebR Status" heading
// and contents
var secondInnerDiv = document.createElement("div");
secondInnerDiv.classList.add("quarto-title-meta-heading");
secondInnerDiv.innerText = "WebR Status";

// Add another inner div
var secondInnerDivContents = document.createElement("div");
secondInnerDivContents.classList.add("quarto-title-meta-contents");

// Describe the WebR state
var startupMessageWebR = document.createElement("p");
startupMessageWebR.innerText = "🟡 Loading...";
startupMessageWebR.setAttribute("id", "startup");

// Put everything together
secondInnerDivContents.appendChild(startupMessageWebR);

// Add a status indicator for COOP and COEP Headers
if (showHeaderMessage) {
var crossOriginMessage = document.createElement("p");
crossOriginMessage.innerText = `${crossOriginIsolated ? '🟢' : '🟡'} COOP & COEP Headers`;
crossOriginMessage.setAttribute("id", "coop-coep-header");
secondInnerDivContents.appendChild(crossOriginMessage);
}

firstInnerDiv.appendChild(secondInnerDiv);
firstInnerDiv.appendChild(secondInnerDivContents);
quartoTitleMeta.appendChild(firstInnerDiv);

// Add new element as last child in header element
var header = document.getElementsByTagName("header")[0];
header.appendChild(quartoTitleMeta);
}

// Retrieve the webr.mjs
import { WebR } from "https://webr.r-wasm.org/v0.1.1/webr.mjs";

// Maybe return the `/` if needed in SW_URL?
globalThis.webR = new WebR();
// Populate WebR options with defaults or new values based on
// webr meta
globalThis.webR = new WebR({
"baseURL": "{{BASEURL}}",
"serviceWorkerUrl": "{{SERVICEWORKERURL}}",
"homedir": "{{HOMEDIR}}"
});

// Initialization WebR
await globalThis.webR.init();

// Setup a shelter
globalThis.webRCodeShelter = await new globalThis.webR.Shelter();

// Installing Packages
if (showStartupMessage && setupRPackages) {
// If initialized, but we have packages to install switch status
startupMessageWebR.innerText = "🟡 Installing package dependencies..."
// Install packages
await globalThis.webR.installPackages(installRPackagesList)
}

// Switch to allowing code to be executed
document.querySelectorAll(".btn-webr").forEach((btn) => {
btn.innerText = "Run code";
btn.disabled = false;
});

// Stop timer
const initializeWebRTimerEnd = performance.now();

if (showStartupMessage) {
// If initialized, switch to a green light
startupMessageWebR.innerText = "🟢 Ready!"
}
</script>
2 changes: 1 addition & 1 deletion _extensions/webr/webr-serviceworker.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
importScripts('https://webr.r-wasm.org/v0.1.0/webr-serviceworker.js');
importScripts('https://webr.r-wasm.org/v0.1.1/webr-serviceworker.js');
2 changes: 1 addition & 1 deletion _extensions/webr/webr-worker.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
importScripts('https://webr.r-wasm.org/v0.1.0/webr-worker.js');
importScripts('https://webr.r-wasm.org/v0.1.1/webr-worker.js');
Loading

0 comments on commit 7c605ee

Please sign in to comment.