webROjs = {
  const { WebR, ChannelType } = window._exercise_ojs_runtime.WebR;
  const {
    WebREvaluator,
    WebREnvironmentManager,
    setupR,
    b64Decode,
    collapsePath
  } = window._exercise_ojs_runtime;

  const statusContainer = document.getElementById("exercise-loading-status");
  const indicatorContainer = document.getElementById("exercise-loading-indicator");
  indicatorContainer.classList.remove("d-none");

  let statusText = document.createElement("div")
  statusText.classList = "exercise-loading-details";
  statusText = statusContainer.appendChild(statusText);
  statusText.textContent = `Initialise`;

  // Hoist indicator out from final slide when running under reveal
  const revealStatus = document.querySelector(".reveal .exercise-loading-indicator");
  if (revealStatus) {
    revealStatus.remove();
    document.querySelector(".reveal > .slides").appendChild(revealStatus);
  }

  // Make any reveal slides with live cells scrollable
  document.querySelectorAll(".reveal .exercise-cell").forEach((el) => {
    el.closest('section.slide').classList.add("scrollable");
  })

  // webR supplemental data and options
  const dataContent = document.querySelector(`script[type=\"webr-data\"]`).textContent;
  const data = JSON.parse(b64Decode(dataContent));

  // Grab list of resources to be downloaded
  const filesContent = document.querySelector(`script[type=\"vfs-file\"]`).textContent;
  const files = JSON.parse(b64Decode(filesContent));

  // Initialise webR and setup for R code evaluation
  let webRPromise = (async (webR) => {
    statusText.textContent = `Downloading webR`;
    await webR.init();

    // Install provided list of packages
    // Ensure webR default repo is included
    data.packages.repos.push("https://repo.r-wasm.org")
    await data.packages.pkgs.map((pkg) => () => {
      statusText.textContent = `Downloading package: ${pkg}`;
      return webR.evalRVoid(`
        webr::install(pkg, repos = repos)
        library(pkg, character.only = TRUE)
      `, { env: {
        pkg: pkg,
        repos: data.packages.repos,
      }});
    }).reduce((cur, next) => cur.then(next), Promise.resolve());

    // Download and install resources
    await files.map((file) => async () => {
      const name = file.substring(file.lastIndexOf('/') + 1);
      statusText.textContent = `Downloading resource: ${name}`;
      const response = await fetch(file);
      if (!response.ok) {
        throw new Error(`Can't download \`${file}\`. Error ${response.status}: "${response.statusText}".`);
      }
      const data = await response.arrayBuffer();

      // Store URLs in the cwd without any subdirectory structure
      if (file.includes("://")) {
        file = name;
      }

      // Collapse higher directory structure
      file = collapsePath(file);

      // Create directory tree, ignoring "directory exists" VFS errors
      const parts = file.split('/').slice(0, -1);
      let path = '';
      while (parts.length > 0) {
        path += parts.shift() + '/';
        const analysis = await webR.FS.analyzePath(path);
        if (!analysis.exists) {
          try {
            await webR.FS.mkdir(path);
          } catch (e) {
            throw new Error(`Filesystem Error: "${e.message}".`);
          }
        }
      }

      // Write this file to the VFS
      return await webR.FS.writeFile(file, new Uint8Array(data));
    }).reduce((cur, next) => cur.then(next), Promise.resolve());

    statusText.textContent = `Installing webR shims`;
    await webR.evalRVoid(`webr::shim_install()`);

    statusText.textContent = `WebR environment setup`;
    await setupR(webR, data);

    statusText.remove();
    if (statusContainer.children.length == 0) {
      statusContainer.parentNode.remove();
    }
    return webR;
  })(new WebR(data.options));

  // Keep track of initial OJS block render
  const renderedOjs = {};

  const process = async (context, inputs) => {
    const webR = await webRPromise;
    const evaluator = new WebREvaluator(webR, context)
    await evaluator.process(inputs);
    return evaluator.container;
  }

  return {
    process,
    webRPromise,
    renderedOjs,
  };
}
