Embedding R in WordPress using webR

Discover webR: a new advancement that has set the R community abuzz this year! Imagine being able to harness the potential of R without the need for an R server. With webR, the R interpreter runs directly on the user’s machine, empowering you to run R code effortlessly in a web browser. In this article, we delve into the details of webR and will demonstrate how you can integrate R into your own (company’s) website. We’ll be using WordPress as an example, but no matter which type of website you have: the principles stay the same. At the end you will know exactly how embedding R in WordPress using webR works!

What is webR?

webR is a special version of R. It has been modified and transformed so that it can run directly in web browser and in Node.js (a popular sever-side JavaScript runtime). This transformation is made possible by using a technology called WebAssembly. WebAssembly, often abbreviated as wasm, is a binary instruction format designed to enable high-performance execution of code on the web. It is supported by all the major web browsers. The process of compiling R to WebAssembly is done using a tool called Emscripten.

Often, we want to extent the functionality of base R and make use of packages. Luckily, webR supports loading R packages that have been pre-compiled to be used with webR. Sounds complicated? There is a small collection of supported packages readily available.

webR use cases

So what can you do with webR?

Imagine going through an online article that explains something about R. There’s example code. There’s also a button that will instantly run that code, right there and then. No more switching between your web browser and R! It changes the way we learn and interact with R. So embedding code and output in a blog article is one use case.

Another use case would be a JupyterLite kernel that uses webR to execute R. This will allow you to write and execute reproducible Jupiter notebooks for R directly in your web browser.

Finally, you can use webR to run Shiny applications straight in the browser. While this would only be suited for small applications, it still is an interesting idea if you simply want to give users some interactivity on your website. Think about demonstrating part of your services or explore some data.

Running R in your web page: prerequisites

Now that you understand what webR is about it’s time for some action! How cool would it be if you can add R directly to a WordPress site, just like this one? Or any website for that matter?

With webR, you can. But before we can do that, we need to understand some communication and website security fundamentals.

Communication channels

Obviously you don’t want your R interpreter to block your web page while it’s busy. That’s why webR launches R in a separate JavaScript web worker. Whenever R is busy with a long computation, this worker thread won’t block the main thread of your web page. The worker thread and the main thread communicate through messages that are being passed through a communication channel.

There are two possible channels:
  • SharedArrayBuffer: a feature that allows multiple (JavaScript) threads to share a common buffer of memory. It allows different parts of a web page or application to communicate and share data efficiently. This requires cross-origin isolation.
  • ServiceWorker: a JavaScript-based background worker that runs separately from the main web page, acting as a proxy between the web page and the network. This requires a service worker script to be hosted on the same origin as the web page that uses webR.

Don’t worry too much about the differences. The important thing to know is that webR automatically defaults to SharedArrayBuffer, and that there are less limitations using this communication channel.

COOP and COEP

If you want to have an R interpreter or console in a web page using SharedArrayBuffer, you basically want to load a piece of external content into a web page.

When you load a web page in your browser, the browser ensures that the code running on that page can only access data and resources from the same origin (domain, protocol, and port) to prevent security risks. This is known as the same-origin policy. However, when we load external content it comes from a different origin.

To safely allow web pages to load such external resources and run code from different origins, the concept of cross-origin isolation was introduced. Cross-origin isolation provides a way to separate the execution environment of a web page from other web pages, ensuring that its code runs in an isolated and secure environment.

To enable cross-origin isolation for a web page that loads webR or similar resources, certain HTTP headers need to be set when serving the page. These headers are called Cross-Origin-Opener-Policy (COOP) and Cross-Origin-Embedder-Policy (COEP). More specifically, we need:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

Setting HTTP headers in WordPress

In WordPress, you can simply add HTTP headers with the HTTP Headers plugin. Once installed, head over to “Settings” > “HTTP Headers” 

You can set every header that you can think of, but for now the interest is in the security headers:

Simply set COOP and COEP according to the requirements:

When that is done, you should see this in the overview:

Simple as that!

Alternatively, you can edit the .htaccess file on your website. This server configuration file is used by the most commonly used Apache webserver software.

Note: I’m not responsible for any website that becomes irresponsive after making changes, so please make a backup 😉.

Adding R code to your web page

Let’s take a look at two examples: embedding an interactive R code editor into your web page and embedding an R console.

Interactive R code editor

Having an interactive R code editor on your web page is great if you are trying to explain something or quickly want to show some analysis.You just need to insert some HTML in your web page. To adjust the code that is being displayed, you need to change the value

The HTML looks like this:

				
					<button class="btn btn-success btn-sm" disabled type="button" id="runButton">Loading webR...</button>
<div id="editor"></div>
<pre><code id="out"></code></pre>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/codemirror.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/codemirror.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/mode/r/r.js"></script>
<script type="module">
  const editor = CodeMirror((elt) => {
    elt.style.border = '1px solid #eee';
    elt.style.height = 'auto';
    document.getElementById('editor').append(elt);
  },{
    value: 'set.seed(123)\n\nrandom_numbers <- rnorm(100, mean = 10, sd = 2)\n\nmean_value <- mean(random_numbers)\nsd_value <- sd(random_numbers)\n\nprint(mean_value)\nprint(sd_value)',
    lineNumbers: true,
    mode: 'r',
    theme: 'light default',
    viewportMargin: Infinity,
  });
  import { WebR } from 'https://webr.r-wasm.org/v0.1.1/webr.mjs';
  const webR = new WebR();
  await webR.init();
  const shelter = await new webR.Shelter();

  async function runR() {
    let code = editor.getValue();
    const result = await shelter.captureR(code, {
      withAutoprint: true,
      captureStreams: true,
      captureConditions: false
    });
    try {
      const out = result.output.filter(
        evt => evt.type == 'stdout' || evt.type == 'stderr'
      ).map((evt) => evt.data);
      document.getElementById('out').innerText = out.join('\n');
    } finally {
      shelter.purge();
    }
  }
  document.getElementById('runButton').onclick = runR;
  document.getElementById('runButton').innerText = 'Run code';
  document.getElementById('runButton').disabled = false;

</script>

				
			

Which results in:

Note: this will only work when you’ve followed the steps mentioned earlier, if you did not add the correct security policies, you will get JavaScript errors because it will default to another communication channel.

Other note: if you’re using Elementor, webR won’t load while editing. If you want to see it in action, you need to use the preview.

Behind the code

Awesome! But what happens in the code above?

Let’s break down the code step by step in the following sections.

HTML Markup

				
					<button id="runButton" class="btn btn-success btn-sm" disabled="disabled"type="button">Loading webR...</button>
<div id="editor"></div>
<pre><code id="out"></code></pre>
				
			

This part defines the structure of the element. It includes a button which is initially disabled and displays the text “Loading webR…”. There is a <div> element where the R code editor will be placed. The <pre><code> element will be used to display the output of the executed R code.

CodeMirror

				
					<scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/codemirror.min.js"></script>
<scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/mode/r/r.js"></script>
				
			

These script tags import the CodeMirror library and the R language mode for CodeMirror. CodeMirror is a versatile text editor implemented in JavaScript, and in this code, it is used to create the R code editor.

Editor Initialization

				
					  const editor = CodeMirror((elt) => {
    elt.style.border = '1px solid #eee';
    elt.style.height = 'auto';
    document.getElementById('editor').append(elt);
  },{
    value: 'set.seed(123)\n\nrandom_numbers <- rnorm(100, mean = 10, sd = 2)\n\nmean_value <- mean(random_numbers)\nsd_value <- sd(random_numbers)\n\nprint(mean_value)\nprint(sd_value)',
    lineNumbers: true,
    mode: 'r',
    theme: 'light default',
    viewportMargin: Infinity,
  });

				
			

In this part, the CodeMirror editor is initialized with some configurations. It creates a new CodeMirror instance inside the <div> element we saw earlier. The options passed to the CodeMirror constructor specify the initial value, enable line numbers, set the mode to ‘r’ (R language mode), set the theme to ‘light default’, and expand the viewport margin to infinity.

webR initialization

				
					import { WebR } from 'https://webr.r-wasm.org/v0.1.1/webr.mjs';
  const webR = new WebR();
  await webR.init();

				
			

These lines import the webR library and initialize a new webR instance. The await webR.init() line initializes the webR instance and waits for it to be ready before proceeding.

R code execution

				
					const shelter = await new webR.Shelter();

  async function runR() {
    let code = editor.getValue();
    const result = await shelter.captureR(code, {
      withAutoprint: true,
      captureStreams: true,
      captureConditions: false
    });
    try {
      const out = result.output.filter(
        evt => evt.type == 'stdout' || evt.type == 'stderr'
      ).map((evt) => evt.data);
      document.getElementById('out').innerText = out.join('\n');
    } finally {
      shelter.purge();
    }
  }
  
  document.getElementById('runButton').onclick = runR;
  document.getElementById('runButton').innerText = 'Run code';
  document.getElementById('runButton').disabled = false;

				
			
This part defines an async function called runR() which is triggered when the “runButton” is clicked. It retrieves the R code from the CodeMirror editor using editor.getValue(). The code is then executed using the shelter.captureR() method provided by webR. The captureR() function takes the R code as input and an options object. In this case, withAutoprint is set to true to capture both standard output and standard error streams. The result of the execution is stored in the result variable.

The output is extracted from the result using result.output.filter() to filter out only the standard output and standard error events. The data from these events is then extracted using map(evt => evt.data). Finally, the output is displayed in the <code> element with the id “out” by setting its innerText.

The shelter.purge() function is called in a finally block to clean up the shelter and release any resources used during the R code execution.

The last few lines of the code set up the click event listener for the “runButton” to call the runR() function when clicked. The text of the button is set to “Run code”, and the button is enabled by setting the disabled attribute to false(note that the button is disabled and has another text on initialization).

R console

Following the example from the webR documentation, we can simply insert some HTML into our web page to get an R console up and running as well.

The HTML being:

				
					<html>
  <head>
    <title>WebR Test Console</title>
  </head>
  <body>
    <div>
      <pre><code id="out">Loading webR, please wait...</code></pre>
      <input spellcheck="false" autocomplete="off" id="input" type="text">
      <button onclick="globalThis.sendInput()" id="run">Run</button>
    </div>
    
    <script type="module">
      /* Create a webR console using the Console helper class */
      import { Console } from 'https://webr.r-wasm.org/latest/webr.mjs';
      const webRConsole = new Console({
        stdout: line => document.getElementById('out').append(line + '\n'),
        stderr: line => document.getElementById('out').append(line + '\n'),
        prompt: p => document.getElementById('out').append(p),
      });
      webRConsole.run();
      
      /* Write to the webR console using the ``stdin()`` method */
      let input = document.getElementById('input');
      globalThis.sendInput = () => {
        webRConsole.stdin(input.value);
        document.getElementById('out').append(input.value + '\n');
        input.value = "";
      }
      
      /* Send input on Enter key */
      input.addEventListener(
        "keydown",
        (evt) => {if(evt.keyCode === 13) globalThis.sendInput()}
      );
    </script>
  </body>
</html>
				
			

Note: currently you can only have one worker on a single web page!

Wrap-up

In this article you hopefully learned a bit more about webR, its use cases, and how to use it to embed R code into your (WordPress) web page!

⚠️ webR is new and currently under development, the API is not stable yet and might change- meaning these examples might stop working at some stage. Keep an eye on the documentation page for the latest news!

☕️ Was this useful to you and would you like to support me? You can buy me a coffee!

I provide R and Shiny consultancy. Do you need help with a project? Reach out and let’s have a chat!

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *