<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.5">Jekyll</generator><link href="https://colinfay.me/feed.xml" rel="self" type="application/atom+xml" /><link href="https://colinfay.me/" rel="alternate" type="text/html" /><updated>2024-07-10T09:29:43+00:00</updated><id>https://colinfay.me/feed.xml</id><title type="html">Colin Fay</title><subtitle>Mostly R, data, and code.</subtitle><author><name>Colin FAY</name><email>contact@colinfay.me</email></author><entry><title type="html">webrcli &amp;amp; spidyr: What’s new</title><link href="https://colinfay.me/webrcli-and-spidyr-whats-new/" rel="alternate" type="text/html" title="webrcli &amp;amp; spidyr: What’s new" /><published>2024-06-14T00:00:00+00:00</published><updated>2024-06-14T00:00:00+00:00</updated><id>https://colinfay.me/webrcli-and-spidyr-what-new</id><content type="html" xml:base="https://colinfay.me/webrcli-and-spidyr-whats-new/"><![CDATA[<p>Find all my posts about webR <a href="https://colinfay.me/categories/#webr">here</a>.</p>

<blockquote>
  <p>Note: the first post of this series explaining roughly what <code class="language-plaintext highlighter-rouge">webR</code> is, I won’t introduce it again here.</p>
</blockquote>

<p>In this blogpost, you’ll find some updates about some recent changes made in <code class="language-plaintext highlighter-rouge">webrcli</code> &amp; <code class="language-plaintext highlighter-rouge">spidyr</code>.</p>

<h2 id="what-webrcli--spidy-wants-to-achieve">What webrcli &amp; spidy wants to achieve</h2>

<p>In a nutshell, the goal of <code class="language-plaintext highlighter-rouge">webrcli</code> &amp; <code class="language-plaintext highlighter-rouge">spidyr</code> is to provide an interface for bringing your R functions inside a NodeJS runtime.</p>

<p>Think of it as something that wants to achieve the following interation:</p>

<ul>
  <li>
    <p>An R dev writes, builds and shares a package called <code class="language-plaintext highlighter-rouge">{rfuns}</code>, which contains a function called <code class="language-plaintext highlighter-rouge">hello_world()</code></p>
  </li>
  <li>
    <p>A NodeJS dev takes this package, can call it with <code class="language-plaintext highlighter-rouge">rfuns.hello_world()</code>, access and manipulate the results with JavaScript.</p>
  </li>
</ul>

<p><img src="/assets/img/js-call-r.png" alt="" /></p>

<h2 id="an-important-note">An important note</h2>

<p>With <code class="language-plaintext highlighter-rouge">webR</code> &amp; the tools described below, there is no R session running while the NodeJS app is running. <code class="language-plaintext highlighter-rouge">spidyr</code> imports an R function <strong>converted to a native JS</strong>.
<code class="language-plaintext highlighter-rouge">rfuns.hello_world</code> does not call an R session running somewhere else.</p>

<p>That also means that if you build a container containing the app, you don’t need to install R in it. It’s a NodeJS app.</p>

<p>Below are some updates recently made to both <code class="language-plaintext highlighter-rouge">webrcli</code> &amp; <code class="language-plaintext highlighter-rouge">spidyr</code>.</p>

<h2 id="making-the-template-code-smaller">Making the template code smaller</h2>

<p>When drafting the first code of <code class="language-plaintext highlighter-rouge">webrcli</code> &amp; <code class="language-plaintext highlighter-rouge">spidyr</code>, I focused on providing a minimal template for an <code class="language-plaintext highlighter-rouge">index.js</code> loading your R functions, contained in a subdirectory inside your node project.</p>

<p>This worked, but might have been a bit complex to grasp. Provided you don’t change the default names of the folders (in that case you’ll need to modify the default parameters), the new default <code class="language-plaintext highlighter-rouge">index.js</code> look like this:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="p">{</span>
  <span class="nx">initSpidyr</span><span class="p">,</span>
  <span class="nx">mountLocalPackage</span>
<span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">spidyr</span><span class="dl">'</span><span class="p">);</span>

<span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>

  <span class="k">await</span> <span class="nx">initSpidyr</span><span class="p">()</span>

  <span class="kd">const</span> <span class="nx">rfuns</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">mountLocalPackage</span><span class="p">(</span><span class="dl">"</span><span class="s2">./rfuns</span><span class="dl">"</span><span class="p">);</span>

  <span class="kd">const</span> <span class="nx">hw</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">rfuns</span><span class="p">.</span><span class="nx">hello_world</span><span class="p">()</span>

  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">hw</span><span class="p">.</span><span class="nx">values</span><span class="p">);</span>

  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">✅ Everything is ready!</span><span class="dl">"</span><span class="p">);</span>
<span class="p">})();</span>
</code></pre></div></div>

<p>This minimal template makes it easier to read and understand what the code does. Note that you’ll get this template if you run <code class="language-plaintext highlighter-rouge">webrcli init myproject</code>.</p>

<p>Let’s decompose what this code does :</p>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">await initSpidyr()</code> will create an object called <code class="language-plaintext highlighter-rouge">spidyr_webR</code> stored as <code class="language-plaintext highlighter-rouge">globalThis.spidyr_webR</code>, <code class="language-plaintext highlighter-rouge">init()</code> it (as in <code class="language-plaintext highlighter-rouge">webr.init()</code>), and load the packages contained in the <code class="language-plaintext highlighter-rouge">./webr_packages</code> folder inside your project dir, the one containing the packages installed with <code class="language-plaintext highlighter-rouge">webrcli install</code>.</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">const rfuns = await mountLocalPackage("./rfuns");</code> will take the R package contained in <code class="language-plaintext highlighter-rouge">./rfuns</code>, load it into the webR instance (<code class="language-plaintext highlighter-rouge">spidyr_webR</code>), recursively get all the exported functions from your package, and store them inside the <code class="language-plaintext highlighter-rouge">rfuns</code> object.</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">const hw = await rfuns.hello_world()</code> will call the <code class="language-plaintext highlighter-rouge">hello_world()</code> function from <code class="language-plaintext highlighter-rouge">{rfuns}</code>. The values are then <code class="language-plaintext highlighter-rouge">console.log</code>ed.</p>
  </li>
</ul>

<h2 id="new-library-is-now-library"><code class="language-plaintext highlighter-rouge">new Library()</code> is now <code class="language-plaintext highlighter-rouge">library()</code></h2>

<p>Calling a package is now <code class="language-plaintext highlighter-rouge">const spongebob = library("spongebob")</code> instead of the previous approach which was <code class="language-plaintext highlighter-rouge">const spongebob = new Library("spongebob");await spongebob.load(globalThis.webR);</code></p>

<h2 id="better-installation-from-packagejson--description">Better installation from package.json &amp; <code class="language-plaintext highlighter-rouge">DESCRIPTION</code></h2>

<p>The previous version of <code class="language-plaintext highlighter-rouge">webrcli</code> did not stored nor re-installed the R package installed in the project, meaning that if you git cloned a project, you had to guess what R packages to re-install.</p>

<p>Now, a project initiated with <code class="language-plaintext highlighter-rouge">webrcli</code> will store all the packages installed with <code class="language-plaintext highlighter-rouge">webrcli install</code> inside the <code class="language-plaintext highlighter-rouge">package.json</code> file.
Then, when running the <code class="language-plaintext highlighter-rouge">npm install</code> command, the R packages will also be redownloaded.</p>

<p>You can also installed a series of packages based on a <code class="language-plaintext highlighter-rouge">DESCRIPTION</code> file with <code class="language-plaintext highlighter-rouge">webrcli installFromDesc</code>.</p>

<h2 id="other-tools">Other tools</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">shareEnv</code> allows to <strong>copy</strong> one, several or all env var from node to the webR instance.</li>
</ul>

<h2 id="workflow-summary">Workflow summary</h2>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">webrcli init myproject</code> will create a project skeleton at <code class="language-plaintext highlighter-rouge">./myproject</code></p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">webrcli install spongebob</code> will download the version of <code class="language-plaintext highlighter-rouge">{spongebob}</code> compiled for <code class="language-plaintext highlighter-rouge">webR</code>, and add it to <code class="language-plaintext highlighter-rouge">./webr_packages</code>.</p>
  </li>
  <li>
    <p>You can then modify you R code with your own functions.</p>
  </li>
  <li>
    <p>Call your R function from <code class="language-plaintext highlighter-rouge">rfuns.xyz()</code> in Node</p>
  </li>
  <li>
    <p>Run with <code class="language-plaintext highlighter-rouge">npm start</code></p>
  </li>
</ul>

<p><img src="/assets/img/webrcli-init.png" alt="" /></p>

<h2 id="please-do-give-it-a-shot">Please do give it a shot</h2>

<p>I think I’ve been the only user for now 😅, so please do try these and let me know what you think.</p>

<h2 id="future-works">Future works</h2>

<p>Here are what I plan on working in the upcoming weeks:</p>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">webrcli init</code> should allow for specifying a <code class="language-plaintext highlighter-rouge">webr</code> version from npm <a href="https://github.com/ColinFay/webrcli/issues/17">issue 17</a></p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">initSpidyr</code> should allow to pass params to <code class="language-plaintext highlighter-rouge">webR.init()</code> <a href="https://github.com/ColinFay/spidyr/issues/12">issue 12</a></p>
  </li>
  <li>
    <p>Allow to install from <code class="language-plaintext highlighter-rouge">r-universe</code>, something like <code class="language-plaintext highlighter-rouge">webrcli installFromRUniverse</code> <a href="https://github.com/ColinFay/webrcli/issues/16">issue 16</a>.
Probably in the future it will be merged to <code class="language-plaintext highlighter-rouge">webrcli install</code> and we’ll have a way to guess what the user wants based on the input.</p>
  </li>
  <li>
    <p>A small converter from R named list to JS objects <a href="https://github.com/ColinFay/spidyr/issues/3">Issue 3</a></p>
  </li>
  <li>
    <p>More documentation, and probably documenting the conversion of an existing <code class="language-plaintext highlighter-rouge">{shiny}</code> app to a <code class="language-plaintext highlighter-rouge">spidyr</code> based one.</p>
  </li>
</ul>]]></content><author><name>Colin FAY</name><email>contact@colinfay.me</email></author><category term="r-blog-en" /><category term="webr" /><summary type="html"><![CDATA[Find all my posts about webR here. Note: the first post of this series explaining roughly what webR is, I won’t introduce it again here. In this blogpost, you’ll find some updates about some recent changes made in webrcli &amp; spidyr. What webrcli &amp; spidy wants to achieve In a nutshell, the goal of webrcli &amp; spidyr is to provide an interface for bringing your R functions inside a NodeJS runtime. Think of it as something that wants to achieve the following interation: An R dev writes, builds and shares a package called {rfuns}, which contains a function called hello_world() A NodeJS dev takes this package, can call it with rfuns.hello_world(), access and manipulate the results with JavaScript. An important note With webR &amp; the tools described below, there is no R session running while the NodeJS app is running. spidyr imports an R function converted to a native JS. rfuns.hello_world does not call an R session running somewhere else. That also means that if you build a container containing the app, you don’t need to install R in it. It’s a NodeJS app. Below are some updates recently made to both webrcli &amp; spidyr. Making the template code smaller When drafting the first code of webrcli &amp; spidyr, I focused on providing a minimal template for an index.js loading your R functions, contained in a subdirectory inside your node project. This worked, but might have been a bit complex to grasp. Provided you don’t change the default names of the folders (in that case you’ll need to modify the default parameters), the new default index.js look like this: const { initSpidyr, mountLocalPackage } = require('spidyr'); (async () =&gt; { await initSpidyr() const rfuns = await mountLocalPackage("./rfuns"); const hw = await rfuns.hello_world() console.log(hw.values); console.log("✅ Everything is ready!"); })(); This minimal template makes it easier to read and understand what the code does. Note that you’ll get this template if you run webrcli init myproject. Let’s decompose what this code does : await initSpidyr() will create an object called spidyr_webR stored as globalThis.spidyr_webR, init() it (as in webr.init()), and load the packages contained in the ./webr_packages folder inside your project dir, the one containing the packages installed with webrcli install. const rfuns = await mountLocalPackage("./rfuns"); will take the R package contained in ./rfuns, load it into the webR instance (spidyr_webR), recursively get all the exported functions from your package, and store them inside the rfuns object. const hw = await rfuns.hello_world() will call the hello_world() function from {rfuns}. The values are then console.loged. new Library() is now library() Calling a package is now const spongebob = library("spongebob") instead of the previous approach which was const spongebob = new Library("spongebob");await spongebob.load(globalThis.webR); Better installation from package.json &amp; DESCRIPTION The previous version of webrcli did not stored nor re-installed the R package installed in the project, meaning that if you git cloned a project, you had to guess what R packages to re-install. Now, a project initiated with webrcli will store all the packages installed with webrcli install inside the package.json file. Then, when running the npm install command, the R packages will also be redownloaded. You can also installed a series of packages based on a DESCRIPTION file with webrcli installFromDesc. Other tools shareEnv allows to copy one, several or all env var from node to the webR instance. Workflow summary webrcli init myproject will create a project skeleton at ./myproject webrcli install spongebob will download the version of {spongebob} compiled for webR, and add it to ./webr_packages. You can then modify you R code with your own functions. Call your R function from rfuns.xyz() in Node Run with npm start Please do give it a shot I think I’ve been the only user for now 😅, so please do try these and let me know what you think. Future works Here are what I plan on working in the upcoming weeks: webrcli init should allow for specifying a webr version from npm issue 17 initSpidyr should allow to pass params to webR.init() issue 12 Allow to install from r-universe, something like webrcli installFromRUniverse issue 16. Probably in the future it will be merged to webrcli install and we’ll have a way to guess what the user wants based on the input. A small converter from R named list to JS objects Issue 3 More documentation, and probably documenting the conversion of an existing {shiny} app to a spidyr based one.]]></summary></entry><entry><title type="html">webrcli &amp;amp; spidyr: A starter pack for building NodeJS projects with webR inside</title><link href="https://colinfay.me/webrcli-and-spidyr/" rel="alternate" type="text/html" title="webrcli &amp;amp; spidyr: A starter pack for building NodeJS projects with webR inside" /><published>2024-03-06T00:00:00+00:00</published><updated>2024-03-06T00:00:00+00:00</updated><id>https://colinfay.me/webrcli-and-spidyr</id><content type="html" xml:base="https://colinfay.me/webrcli-and-spidyr/"><![CDATA[<p>Find all my posts about webR <a href="https://colinfay.me/categories/#webr">here</a>.</p>

<blockquote>
  <p>Note: the first post of this series explaining roughly what <code class="language-plaintext highlighter-rouge">webR</code> is, I won’t introduce it again here.</p>
</blockquote>

<h2 id="again-a-new-project">Again, a new project?</h2>

<p>Most of the previous posts have been about poking around with <code class="language-plaintext highlighter-rouge">webR</code> inside NodeJS, but nothing stable on how to build an app.</p>

<p>That’s something I’m trying to change.
Based on the experience gathered building apps in R (in <code class="language-plaintext highlighter-rouge">{shiny}</code> with <code class="language-plaintext highlighter-rouge">{golem}</code>) and in JavaScript, I’ve tried to come up with a solution for a starter kit for building what I think will be the perfect skeleton for <code class="language-plaintext highlighter-rouge">webR</code>/<code class="language-plaintext highlighter-rouge">NodeJS</code> project.</p>

<p>The idea is to have one project with inside:</p>

<ul>
  <li>One R package</li>
  <li>One JS app</li>
  <li>A tool that brings the R package into the NodeJS app and allow to use the R functions inside JavaScript</li>
</ul>

<p>This skeleton needed a toolkit to perform the following task:</p>

<p>1️⃣ From a cli point of view:</p>

<ul>
  <li>init the project with a specific skeleton</li>
  <li>allow to install packages from the <code class="language-plaintext highlighter-rouge">webr </code>CRAN to a local <code class="language-plaintext highlighter-rouge">webr</code> library, in the spirit of <a href="https://colinfay.me/rethinking-packages-and-functions-preloading-in-webr-0.2.2/">previous post</a>.</li>
  <li>allow to install the deps from a DESCRIPTION file</li>
</ul>

<p>2️⃣ From a NodeJS point of view:</p>

<ul>
  <li>Load the packages downloaded in a local <code class="language-plaintext highlighter-rouge">webr</code> library</li>
  <li>Create an interface to load and manipulate the functions from a package downloaded from CRAN, and use them in JS.</li>
  <li>Same but with a local package folder</li>
</ul>

<p>For the function manipulation mechanism, I wanted something that would allow to think in terms of <code class="language-plaintext highlighter-rouge">pkg::fun()</code> but in JavaScript, which would translate as <code class="language-plaintext highlighter-rouge">pkg.fun()</code> in your Node app.
Something I had implemented in another old NodeJS module called <a href="https://github.com/ColinFay/hordes">hordes</a>, which I can now safely deprecate in favor of the following tools.</p>

<p>The reasoning being: the R dev writes a standalone package that exports an <code class="language-plaintext highlighter-rouge">xyz</code> function, then the web team can load this package, and launch <code class="language-plaintext highlighter-rouge">xyz()</code> without writing any R code.</p>

<h2 id="webrcli--spidyr">webrcli &amp; spidyr</h2>

<p>So, here comes <a href="https://www.npmjs.com/package/webrcli">webrcli</a> and <a href="https://www.npmjs.com/package/spidyr">spidyr</a>.</p>

<p>❗️ Please note that these tools are work in progress and has been used very few times and only for example apps, will need a lot of bug fixes and documentation, so if ever you plan on using it be indulgent, and report any bug or feature request ❗️</p>

<h3 id="project-init">Project init</h3>

<p>These packages are made to be used together, and here is an example of how to use them:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Global installing webrcli</span>
npm <span class="nb">install</span> <span class="nt">-g</span> webrcli
<span class="c"># Init a webrcli project</span>
<span class="nb">cd</span> /tmp
webrcli init webrspongebob
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>👉 Initializing project ----
(This may take some time, please be patient)
👉 Copying template ----
👉 Installing {pkgload} ----
✅ {pkgload} downloaded and extracted ----

✅ {cli} downloaded and extracted ----

✅ {crayon} downloaded and extracted ----

✅ {desc} downloaded and extracted ----

✅ {fs} downloaded and extracted ----

✅ {glue} downloaded and extracted ----

✅ {pkgbuild} downloaded and extracted ----

✅ {rlang} downloaded and extracted ----

✅ {rprojroot} downloaded and extracted ----

✅ {withr} downloaded and extracted ----

✅ {R6} downloaded and extracted ----

✅ {callr} downloaded and extracted ----

✅ {processx} downloaded and extracted ----

✅ {ps} downloaded and extracted ----
</code></pre></div></div>

<p>The project is now created, let’s move into it.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> ./webrspongebob
tree <span class="nt">-L</span> 1
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.
├── index.js
├── node_modules
├── package-lock.json
├── package.json
├── rfuns
└── webr_packages

4 directories, 3 files
</code></pre></div></div>

<p>Here is how it’s organized:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">index.js</code> is the main file for the app, <code class="language-plaintext highlighter-rouge">node_modules</code> the standard folder for the node deps</li>
  <li><code class="language-plaintext highlighter-rouge">package-lock.json</code> / <code class="language-plaintext highlighter-rouge">package.json</code> are standard Node metadata files</li>
  <li><code class="language-plaintext highlighter-rouge">rfuns</code> contains the R package that will be added to the NodeJS app</li>
  <li><code class="language-plaintext highlighter-rouge">webr_packages</code> contains the R dependencies</li>
</ul>

<p>We can launch the app with <code class="language-plaintext highlighter-rouge">node index.js</code> and it will output:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>node index.js
👉 Loading WebR ----
👉 Loading R packages ----
ℹ Loading rfuns
[ 'Hello, world!' ]
✅ Everything is ready
</code></pre></div></div>

<p>Let’s dive a bit inside the <code class="language-plaintext highlighter-rouge">index.js</code>:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">path</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">path</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">WebR</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">webr</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">loadPackages</span><span class="p">,</span> <span class="nx">LibraryFromLocalFolder</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">spidyr</span><span class="dl">'</span><span class="p">);</span>

<span class="kd">const</span> <span class="nx">rfuns</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">LibraryFromLocalFolder</span><span class="p">(</span><span class="dl">"</span><span class="s2">rfuns</span><span class="dl">"</span><span class="p">);</span>


<span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>

  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">👉 Loading WebR ----</span><span class="dl">"</span><span class="p">);</span>
  <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">WebR</span><span class="p">();</span>
  <span class="k">await</span> <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">.</span><span class="nx">init</span><span class="p">();</span>

  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">👉 Loading R packages ----</span><span class="dl">"</span><span class="p">);</span>

  <span class="k">await</span> <span class="nx">loadPackages</span><span class="p">(</span>
    <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">,</span>
    <span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="dl">'</span><span class="s1">webr_packages</span><span class="dl">'</span><span class="p">)</span>
  <span class="p">)</span>

  <span class="k">await</span> <span class="nx">rfuns</span><span class="p">.</span><span class="nx">mountAndLoad</span><span class="p">(</span>
    <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">,</span>
    <span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="dl">'</span><span class="s1">rfuns</span><span class="dl">'</span><span class="p">)</span>
  <span class="p">);</span>

  <span class="kd">const</span> <span class="nx">hw</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">rfuns</span><span class="p">.</span><span class="nx">hello_world</span><span class="p">()</span>

  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">hw</span><span class="p">.</span><span class="nx">values</span><span class="p">);</span>

  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">✅ Everything is ready!</span><span class="dl">"</span><span class="p">);</span>

<span class="p">})();</span>
</code></pre></div></div>

<p>Here are the bits that are specific to a <code class="language-plaintext highlighter-rouge">spidyr</code> project:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">rfuns</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">LibraryFromLocalFolder</span><span class="p">(</span><span class="dl">"</span><span class="s2">rfuns</span><span class="dl">"</span><span class="p">);</span>
</code></pre></div></div>

<p>This function will take a local folder containing an R package, and load the functions from this package into the <code class="language-plaintext highlighter-rouge">rfuns</code> object.
Here, for example, our R package contains one R function, <code class="language-plaintext highlighter-rouge">hello_world()</code>, it will then be available in NodeJS as <code class="language-plaintext highlighter-rouge">rfuns.hello_world()</code> once the <code class="language-plaintext highlighter-rouge">mountAndLoad</code> function is called.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">await</span> <span class="nx">loadPackages</span><span class="p">(</span>
  <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">,</span>
  <span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="dl">'</span><span class="s1">webr_packages</span><span class="dl">'</span><span class="p">)</span>
<span class="p">)</span>

<span class="k">await</span> <span class="nx">rfuns</span><span class="p">.</span><span class="nx">mountAndLoad</span><span class="p">(</span>
  <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">,</span>
  <span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="dl">'</span><span class="s1">rfuns</span><span class="dl">'</span><span class="p">)</span>
<span class="p">);</span>
</code></pre></div></div>

<p>The first bit loads the <code class="language-plaintext highlighter-rouge">webr_packages</code> folder, containing all the R dependencies, then the second bit mount the local folder into the <code class="language-plaintext highlighter-rouge">webR</code> file system and load the functions.</p>

<p>Finally, <code class="language-plaintext highlighter-rouge">const hw = await rfuns.hello_world()</code> calls the function from the R package, and its value is <code class="language-plaintext highlighter-rouge">console.log</code>ed just after that.</p>

<h3 id="with-a-cran-package">With a CRAN package</h3>

<p>And now, how do I load a CRAN package? For example, let’s say I want to use <code class="language-plaintext highlighter-rouge">{spongebob}</code> in my app?</p>

<p>First, let’s install <code class="language-plaintext highlighter-rouge">{spongebob}</code> via <code class="language-plaintext highlighter-rouge">webrcli</code> :</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>webrcli <span class="nb">install </span>spongebob
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>✅ {spongebob} downloaded and extracted ----
</code></pre></div></div>

<p>Then, let’s update our <code class="language-plaintext highlighter-rouge">index.js</code>:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">path</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">path</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">WebR</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">webr</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">loadPackages</span><span class="p">,</span> <span class="nx">LibraryFromLocalFolder</span><span class="p">,</span> <span class="nx">Library</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">spidyr</span><span class="dl">'</span><span class="p">);</span>

<span class="kd">const</span> <span class="nx">rfuns</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">LibraryFromLocalFolder</span><span class="p">(</span><span class="dl">"</span><span class="s2">rfuns</span><span class="dl">"</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">spongebob</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Library</span><span class="p">(</span><span class="dl">"</span><span class="s2">spongebob</span><span class="dl">"</span><span class="p">);</span>

<span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>

  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">👉 Loading WebR ----</span><span class="dl">"</span><span class="p">);</span>
  <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">WebR</span><span class="p">();</span>
  <span class="k">await</span> <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">.</span><span class="nx">init</span><span class="p">();</span>

  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">👉 Loading R packages ----</span><span class="dl">"</span><span class="p">);</span>

  <span class="k">await</span> <span class="nx">loadPackages</span><span class="p">(</span>
    <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">,</span>
    <span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="dl">'</span><span class="s1">webr_packages</span><span class="dl">'</span><span class="p">)</span>
  <span class="p">)</span>

  <span class="k">await</span> <span class="nx">rfuns</span><span class="p">.</span><span class="nx">mountAndLoad</span><span class="p">(</span>
    <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">,</span>
    <span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="dl">'</span><span class="s1">rfuns</span><span class="dl">'</span><span class="p">)</span>
  <span class="p">);</span>

  <span class="k">await</span> <span class="nx">spongebob</span><span class="p">.</span><span class="nx">load</span><span class="p">(</span>
    <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span>
  <span class="p">);</span>

  <span class="kd">const</span> <span class="nx">hw</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">rfuns</span><span class="p">.</span><span class="nx">hello_world</span><span class="p">()</span>

  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">hw</span><span class="p">.</span><span class="nx">values</span><span class="p">);</span>

  <span class="kd">const</span> <span class="nx">said</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">spongebob</span><span class="p">.</span><span class="nx">tospongebob</span><span class="p">(</span><span class="dl">"</span><span class="s2">hello from spongebob</span><span class="dl">"</span><span class="p">)</span>

  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">said</span><span class="p">.</span><span class="nx">values</span><span class="p">)</span>

  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">✅ Everything is ready!</span><span class="dl">"</span><span class="p">);;</span>

<span class="p">})();</span>
</code></pre></div></div>

<p>Here:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">const spongebob = new Library("spongebob");</code> will create a lib, ready to mount a package</li>
  <li><code class="language-plaintext highlighter-rouge">await spongebob.load(globalThis.webR)</code> will load all the functions from the <code class="language-plaintext highlighter-rouge">{spongebob}</code> package</li>
  <li><code class="language-plaintext highlighter-rouge">const said = await spongebob.tospongebob("hello from spongebob")</code> will run the <code class="language-plaintext highlighter-rouge">spongebob::tospongebob()</code> R function</li>
</ul>

<p>Then :</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>node index.js
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>👉 Loading WebR ----
👉 Loading R packages ----
ℹ Loading rfuns
[ 'Hello, world!' ]
[ 'helLo fROm spONgEbOb' ]
✅ Everything is ready!
</code></pre></div></div>

<h2 id="some-random-notes">Some random notes</h2>

<p>The <code class="language-plaintext highlighter-rouge">webrspongebob</code> example is available at <a href="https://github.com/ColinFay/webrspongebob">https://github.com/ColinFay/webrspongebob</a></p>

<h3 id="about-the-current-implementation">About the current implementation</h3>

<ul>
  <li>
    <p>Right now I’m not really sure how this handles the infix function like <code class="language-plaintext highlighter-rouge">%&gt;%</code> for example. That being said, this might not be a function you’d want to use in the current way of building things.</p>
  </li>
  <li>
    <p>The exported functions are read via <code class="language-plaintext highlighter-rouge">getNamespaceExports("pkg")</code> and <code class="language-plaintext highlighter-rouge">getExportedValue("pkg", "fun")</code>, if ever this is not the perfect way to do this in base R but I’ll be happy to find some other ways to do this.</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">webrcli</code> and <code class="language-plaintext highlighter-rouge">spidyr</code> both encapsulate code run in <code class="language-plaintext highlighter-rouge">webR</code>, especially <code class="language-plaintext highlighter-rouge">webrcli</code> which has an <a href="https://github.com/ColinFay/webrcli/blob/main/rpkg/R/get_list_of_tar_gz_dependencies_for_package.R">R script</a> which is sourced and run in a <code class="language-plaintext highlighter-rouge">webR</code> instance.</p>
  </li>
</ul>

<h3 id="future">Future</h3>

<p>Please do try these tools. I would be very happy to have your feedback on the philosophy, and on the general workflow.</p>

<p>If ever you find a bug or have an idea, feel free to open issues at :</p>

<ul>
  <li>
    <p>https://github.com/ColinFay/webrcli</p>
  </li>
  <li>
    <p>https://github.com/ColinFay/spidyr</p>
  </li>
</ul>]]></content><author><name>Colin FAY</name><email>contact@colinfay.me</email></author><category term="r-blog-en" /><category term="webr" /><summary type="html"><![CDATA[Find all my posts about webR here. Note: the first post of this series explaining roughly what webR is, I won’t introduce it again here. Again, a new project? Most of the previous posts have been about poking around with webR inside NodeJS, but nothing stable on how to build an app. That’s something I’m trying to change. Based on the experience gathered building apps in R (in {shiny} with {golem}) and in JavaScript, I’ve tried to come up with a solution for a starter kit for building what I think will be the perfect skeleton for webR/NodeJS project. The idea is to have one project with inside: One R package One JS app A tool that brings the R package into the NodeJS app and allow to use the R functions inside JavaScript This skeleton needed a toolkit to perform the following task: 1️⃣ From a cli point of view: init the project with a specific skeleton allow to install packages from the webr CRAN to a local webr library, in the spirit of previous post. allow to install the deps from a DESCRIPTION file 2️⃣ From a NodeJS point of view: Load the packages downloaded in a local webr library Create an interface to load and manipulate the functions from a package downloaded from CRAN, and use them in JS. Same but with a local package folder For the function manipulation mechanism, I wanted something that would allow to think in terms of pkg::fun() but in JavaScript, which would translate as pkg.fun() in your Node app. Something I had implemented in another old NodeJS module called hordes, which I can now safely deprecate in favor of the following tools. The reasoning being: the R dev writes a standalone package that exports an xyz function, then the web team can load this package, and launch xyz() without writing any R code. webrcli &amp; spidyr So, here comes webrcli and spidyr. ❗️ Please note that these tools are work in progress and has been used very few times and only for example apps, will need a lot of bug fixes and documentation, so if ever you plan on using it be indulgent, and report any bug or feature request ❗️ Project init These packages are made to be used together, and here is an example of how to use them: # Global installing webrcli npm install -g webrcli # Init a webrcli project cd /tmp webrcli init webrspongebob 👉 Initializing project ---- (This may take some time, please be patient) 👉 Copying template ---- 👉 Installing {pkgload} ---- ✅ {pkgload} downloaded and extracted ---- ✅ {cli} downloaded and extracted ---- ✅ {crayon} downloaded and extracted ---- ✅ {desc} downloaded and extracted ---- ✅ {fs} downloaded and extracted ---- ✅ {glue} downloaded and extracted ---- ✅ {pkgbuild} downloaded and extracted ---- ✅ {rlang} downloaded and extracted ---- ✅ {rprojroot} downloaded and extracted ---- ✅ {withr} downloaded and extracted ---- ✅ {R6} downloaded and extracted ---- ✅ {callr} downloaded and extracted ---- ✅ {processx} downloaded and extracted ---- ✅ {ps} downloaded and extracted ---- The project is now created, let’s move into it. cd ./webrspongebob tree -L 1 . ├── index.js ├── node_modules ├── package-lock.json ├── package.json ├── rfuns └── webr_packages 4 directories, 3 files Here is how it’s organized: index.js is the main file for the app, node_modules the standard folder for the node deps package-lock.json / package.json are standard Node metadata files rfuns contains the R package that will be added to the NodeJS app webr_packages contains the R dependencies We can launch the app with node index.js and it will output: node index.js 👉 Loading WebR ---- 👉 Loading R packages ---- ℹ Loading rfuns [ 'Hello, world!' ] ✅ Everything is ready Let’s dive a bit inside the index.js: const path = require('path'); const { WebR } = require('webr'); const { loadPackages, LibraryFromLocalFolder } = require('spidyr'); const rfuns = new LibraryFromLocalFolder("rfuns"); (async () =&gt; { console.log("👉 Loading WebR ----"); globalThis.webR = new WebR(); await globalThis.webR.init(); console.log("👉 Loading R packages ----"); await loadPackages( globalThis.webR, path.join(__dirname, 'webr_packages') ) await rfuns.mountAndLoad( globalThis.webR, path.join(__dirname, 'rfuns') ); const hw = await rfuns.hello_world() console.log(hw.values); console.log("✅ Everything is ready!"); })(); Here are the bits that are specific to a spidyr project: const rfuns = new LibraryFromLocalFolder("rfuns"); This function will take a local folder containing an R package, and load the functions from this package into the rfuns object. Here, for example, our R package contains one R function, hello_world(), it will then be available in NodeJS as rfuns.hello_world() once the mountAndLoad function is called. await loadPackages( globalThis.webR, path.join(__dirname, 'webr_packages') ) await rfuns.mountAndLoad( globalThis.webR, path.join(__dirname, 'rfuns') ); The first bit loads the webr_packages folder, containing all the R dependencies, then the second bit mount the local folder into the webR file system and load the functions. Finally, const hw = await rfuns.hello_world() calls the function from the R package, and its value is console.loged just after that. With a CRAN package And now, how do I load a CRAN package? For example, let’s say I want to use {spongebob} in my app? First, let’s install {spongebob} via webrcli : webrcli install spongebob ✅ {spongebob} downloaded and extracted ---- Then, let’s update our index.js: const path = require('path'); const { WebR } = require('webr'); const { loadPackages, LibraryFromLocalFolder, Library } = require('spidyr'); const rfuns = new LibraryFromLocalFolder("rfuns"); const spongebob = new Library("spongebob"); (async () =&gt; { console.log("👉 Loading WebR ----"); globalThis.webR = new WebR(); await globalThis.webR.init(); console.log("👉 Loading R packages ----"); await loadPackages( globalThis.webR, path.join(__dirname, 'webr_packages') ) await rfuns.mountAndLoad( globalThis.webR, path.join(__dirname, 'rfuns') ); await spongebob.load( globalThis.webR ); const hw = await rfuns.hello_world() console.log(hw.values); const said = await spongebob.tospongebob("hello from spongebob") console.log(said.values) console.log("✅ Everything is ready!");; })(); Here: const spongebob = new Library("spongebob"); will create a lib, ready to mount a package await spongebob.load(globalThis.webR) will load all the functions from the {spongebob} package const said = await spongebob.tospongebob("hello from spongebob") will run the spongebob::tospongebob() R function Then : node index.js 👉 Loading WebR ---- 👉 Loading R packages ---- ℹ Loading rfuns [ 'Hello, world!' ] [ 'helLo fROm spONgEbOb' ] ✅ Everything is ready! Some random notes The webrspongebob example is available at https://github.com/ColinFay/webrspongebob About the current implementation Right now I’m not really sure how this handles the infix function like %&gt;% for example. That being said, this might not be a function you’d want to use in the current way of building things. The exported functions are read via getNamespaceExports("pkg") and getExportedValue("pkg", "fun"), if ever this is not the perfect way to do this in base R but I’ll be happy to find some other ways to do this. webrcli and spidyr both encapsulate code run in webR, especially webrcli which has an R script which is sourced and run in a webR instance. Future Please do try these tools. I would be very happy to have your feedback on the philosophy, and on the general workflow. If ever you find a bug or have an idea, feel free to open issues at : https://github.com/ColinFay/webrcli https://github.com/ColinFay/spidyr]]></summary></entry><entry><title type="html">Rethinking packages &amp;amp; functions preloading in webR 0.2.2</title><link href="https://colinfay.me/rethinking-packages-and-functions-preloading-in-webr-0.2.2/" rel="alternate" type="text/html" title="Rethinking packages &amp;amp; functions preloading in webR 0.2.2" /><published>2023-11-21T00:00:00+00:00</published><updated>2023-11-21T00:00:00+00:00</updated><id>https://colinfay.me/simple-package-and-functions-in-webr</id><content type="html" xml:base="https://colinfay.me/rethinking-packages-and-functions-preloading-in-webr-0.2.2/"><![CDATA[<p>Find all my posts about webR <a href="https://colinfay.me/categories/#webr">here</a>.</p>

<blockquote>
  <p>Note: the first post of this series explaining roughly what <code class="language-plaintext highlighter-rouge">webR</code> is, I won’t introduce it again here.</p>
</blockquote>

<p>When I wrote my blogpost about <a href="/preloading-your-r-packages-in-webr-in-an-express-js-api/">Preloading your R packages in webR in an Express JS API</a>, I mentioned that there was no native way to preload things in the webR filesystem — meaning that you had to reinstall all the R packages whenever the app was launched (and reported it in a <a href="https://github.com/r-wasm/webr/issues/260">Github issue</a>).
This also meant that I couldn’t easily take my own functions and run them in the webR environment, as described in <a href="/using-own-functions-in-webr-node-js">Using my own R functions in webR in an Express JS API, and thoughts on building web apps with Node &amp; webR</a>.
These needs made me write the <a href="https://www.npmjs.com/package/webrtools">webrtools</a> NodeJS package, to do just that:</p>

<ul>
  <li>Download the R packages compiled for webR, to a local folder</li>
  <li>Bundle them in the webR lib (by reading the folder tree and reproducing it in webR filesystem)</li>
  <li>Load your local package to access its functions in webR</li>
</ul>

<p>The last webR version now exposes Emscripten’s <code class="language-plaintext highlighter-rouge">FS.mount</code>, so this makes the process easier, as there is now no need to read the file tree and recreate it: the folder from the server can be mounted into webR filesystem.</p>

<p>That implied some rework (now integrated to <code class="language-plaintext highlighter-rouge">webrtools</code>) :</p>

<ul>
  <li>Use <code class="language-plaintext highlighter-rouge">webR.FS.mkdir</code> to create a local lib: the module can’t mount the package library into the libPath as is, because it would overwrite the already bundled library (in other words, if you mount into a folder that is not empty, the content from the server overwrites the content from webR).</li>
  <li>Use <code class="language-plaintext highlighter-rouge">webR.FS.mount</code> to mount the local directory into the newly created library</li>
  <li>Ensure that this new library is in the libPath()</li>
</ul>

<p>Here is the new code for <code class="language-plaintext highlighter-rouge">loadPackages</code>, bundled into <code class="language-plaintext highlighter-rouge">webrtools</code>:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="kd">function</span> <span class="nx">loadPackages</span><span class="p">(</span><span class="nx">webR</span><span class="p">,</span> <span class="nx">dirPath</span><span class="p">,</span> <span class="nx">libName</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">webr_packages</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// Create a custom lib so that we don't have to worry about</span>
  <span class="c1">// overwriting any packages that are already installed.</span>
  <span class="k">await</span> <span class="nx">webR</span><span class="p">.</span><span class="nx">FS</span><span class="p">.</span><span class="nx">mkdir</span><span class="p">(</span><span class="s2">`/usr/lib/R/</span><span class="p">${</span><span class="nx">libName</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>
  <span class="c1">// Mount the custom lib</span>
  <span class="k">await</span> <span class="nx">webR</span><span class="p">.</span><span class="nx">FS</span><span class="p">.</span><span class="nx">mount</span><span class="p">(</span><span class="dl">"</span><span class="s2">NODEFS</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> <span class="na">root</span><span class="p">:</span> <span class="nx">dirPath</span> <span class="p">},</span> <span class="s2">`/usr/lib/R/</span><span class="p">${</span><span class="nx">libName</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
  <span class="c1">// Add the custom lib to the R search path</span>
  <span class="k">await</span> <span class="nx">webR</span><span class="p">.</span><span class="nx">evalR</span><span class="p">(</span><span class="s2">`.libPaths(c('/usr/lib/R/</span><span class="p">${</span><span class="nx">libName</span><span class="p">}</span><span class="s2">', .libPaths()))`</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>I’ve also decided to deprecate the <code class="language-plaintext highlighter-rouge">loadFolder</code> function, as it is now native with <code class="language-plaintext highlighter-rouge">webR.FS.mkdir</code> + <code class="language-plaintext highlighter-rouge">webR.FS.mount</code>.</p>

<p>So here is a rewrite of the app from <a href="/using-own-functions-in-webr-node-js/">Using my own R functions in webR in an Express JS API, and thoughts on building web apps with Node &amp; webR</a>.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">express</span><span class="dl">'</span><span class="p">)()</span>
<span class="kd">const</span> <span class="nx">path</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">path</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">loadPackages</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">webrtools</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">WebR</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">webr</span><span class="dl">'</span><span class="p">);</span>

<span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">WebR</span><span class="p">();</span>
  <span class="k">await</span> <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">.</span><span class="nx">init</span><span class="p">();</span>

  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">🚀 webR is ready 🚀</span><span class="dl">"</span><span class="p">);</span>

  <span class="k">await</span> <span class="nx">loadPackages</span><span class="p">(</span>
    <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">,</span>
    <span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="dl">'</span><span class="s1">webr_packages</span><span class="dl">'</span><span class="p">)</span>
  <span class="p">)</span>

  <span class="k">await</span> <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">.</span><span class="nx">FS</span><span class="p">.</span><span class="nx">mkdir</span><span class="p">(</span><span class="dl">"</span><span class="s2">/home/rfuns</span><span class="dl">"</span><span class="p">)</span>

  <span class="k">await</span> <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">.</span><span class="nx">FS</span><span class="p">.</span><span class="nx">mount</span><span class="p">(</span>
    <span class="dl">"</span><span class="s2">NODEFS</span><span class="dl">"</span><span class="p">,</span>
    <span class="p">{</span>
      <span class="na">root</span><span class="p">:</span> <span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="dl">'</span><span class="s1">rfuns</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">},</span>
    <span class="dl">"</span><span class="s2">/home/rfuns</span><span class="dl">"</span>
  <span class="p">)</span>

  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">📦 Packages written to webR 📦</span><span class="dl">"</span><span class="p">);</span>

  <span class="c1">// see https://github.com/r-wasm/webr/issues/292</span>
  <span class="k">await</span> <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">.</span><span class="nx">evalR</span><span class="p">(</span><span class="dl">"</span><span class="s2">options(expressions=1000)</span><span class="dl">"</span><span class="p">)</span>
  <span class="k">await</span> <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">.</span><span class="nx">evalR</span><span class="p">(</span><span class="dl">"</span><span class="s2">pkgload::load_all('/home/rfuns')</span><span class="dl">"</span><span class="p">);</span>

  <span class="nx">app</span><span class="p">.</span><span class="nx">listen</span><span class="p">(</span><span class="mi">3000</span><span class="p">,</span> <span class="dl">'</span><span class="s1">0.0.0.0</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">http://localhost:3000</span><span class="dl">'</span><span class="p">)</span>
  <span class="p">})</span>

<span class="p">})();</span>

<span class="nx">app</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">let</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">.</span><span class="nx">evalR</span><span class="p">(</span>
    <span class="dl">'</span><span class="s1">unique_species()</span><span class="dl">'</span>
  <span class="p">);</span>
  <span class="k">try</span> <span class="p">{</span>
    <span class="kd">let</span> <span class="nx">js_res</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">result</span><span class="p">.</span><span class="nx">toJs</span><span class="p">()</span>
    <span class="nx">res</span><span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="nx">js_res</span><span class="p">.</span><span class="nx">values</span><span class="p">)</span>
  <span class="p">}</span> <span class="k">finally</span> <span class="p">{</span>
    <span class="nx">webR</span><span class="p">.</span><span class="nx">destroy</span><span class="p">(</span><span class="nx">result</span><span class="p">);</span>
  <span class="p">}</span>

<span class="p">})</span>

<span class="nx">app</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/:n</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">let</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">.</span><span class="nx">evalR</span><span class="p">(</span>
    <span class="dl">'</span><span class="s1">star_wars_by_species(n)</span><span class="dl">'</span><span class="p">,</span>
    <span class="p">{</span> <span class="na">env</span><span class="p">:</span> <span class="p">{</span> <span class="na">n</span><span class="p">:</span> <span class="nx">req</span><span class="p">.</span><span class="nx">params</span><span class="p">.</span><span class="nx">n</span> <span class="p">}</span> <span class="p">}</span>
  <span class="p">);</span>
  <span class="k">try</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">result_js</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">result</span><span class="p">.</span><span class="nx">toJs</span><span class="p">();</span>
    <span class="nx">res</span><span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="nx">result_js</span><span class="p">)</span>
  <span class="p">}</span> <span class="k">finally</span> <span class="p">{</span>
    <span class="nx">webR</span><span class="p">.</span><span class="nx">destroy</span><span class="p">(</span><span class="nx">result</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>

<p>Full code is at <a href="https://github.com/ColinFay/webr-examples/tree/main/webr-preload-funs">ColinFay/webr-examples/</a>.</p>

<p>Further exploration to be done: webR now bundles a way to package a file system in a file, which can then be downloaded and mounted into the runtime, as described <a href="https://docs.r-wasm.org/webr/latest/mounting.html#emscripten-filesystem-images">here</a>.
This might come handy for our current structure, but I’ll have to explore it a bit more.</p>]]></content><author><name>Colin FAY</name><email>contact@colinfay.me</email></author><category term="r-blog-en" /><category term="webr" /><summary type="html"><![CDATA[Find all my posts about webR here. Note: the first post of this series explaining roughly what webR is, I won’t introduce it again here. When I wrote my blogpost about Preloading your R packages in webR in an Express JS API, I mentioned that there was no native way to preload things in the webR filesystem — meaning that you had to reinstall all the R packages whenever the app was launched (and reported it in a Github issue). This also meant that I couldn’t easily take my own functions and run them in the webR environment, as described in Using my own R functions in webR in an Express JS API, and thoughts on building web apps with Node &amp; webR. These needs made me write the webrtools NodeJS package, to do just that: Download the R packages compiled for webR, to a local folder Bundle them in the webR lib (by reading the folder tree and reproducing it in webR filesystem) Load your local package to access its functions in webR The last webR version now exposes Emscripten’s FS.mount, so this makes the process easier, as there is now no need to read the file tree and recreate it: the folder from the server can be mounted into webR filesystem. That implied some rework (now integrated to webrtools) : Use webR.FS.mkdir to create a local lib: the module can’t mount the package library into the libPath as is, because it would overwrite the already bundled library (in other words, if you mount into a folder that is not empty, the content from the server overwrites the content from webR). Use webR.FS.mount to mount the local directory into the newly created library Ensure that this new library is in the libPath() Here is the new code for loadPackages, bundled into webrtools: async function loadPackages(webR, dirPath, libName = "webr_packages") { // Create a custom lib so that we don't have to worry about // overwriting any packages that are already installed. await webR.FS.mkdir(`/usr/lib/R/${libName}`) // Mount the custom lib await webR.FS.mount("NODEFS", { root: dirPath }, `/usr/lib/R/${libName}`); // Add the custom lib to the R search path await webR.evalR(`.libPaths(c('/usr/lib/R/${libName}', .libPaths()))`); } I’ve also decided to deprecate the loadFolder function, as it is now native with webR.FS.mkdir + webR.FS.mount. So here is a rewrite of the app from Using my own R functions in webR in an Express JS API, and thoughts on building web apps with Node &amp; webR. const app = require('express')() const path = require('path'); const { loadPackages } = require('webrtools'); const { WebR } = require('webr'); (async () =&gt; { globalThis.webR = new WebR(); await globalThis.webR.init(); console.log("🚀 webR is ready 🚀"); await loadPackages( globalThis.webR, path.join(__dirname, 'webr_packages') ) await globalThis.webR.FS.mkdir("/home/rfuns") await globalThis.webR.FS.mount( "NODEFS", { root: path.join(__dirname, 'rfuns') }, "/home/rfuns" ) console.log("📦 Packages written to webR 📦"); // see https://github.com/r-wasm/webr/issues/292 await globalThis.webR.evalR("options(expressions=1000)") await globalThis.webR.evalR("pkgload::load_all('/home/rfuns')"); app.listen(3000, '0.0.0.0', () =&gt; { console.log('http://localhost:3000') }) })(); app.get('/', async (req, res) =&gt; { let result = await globalThis.webR.evalR( 'unique_species()' ); try { let js_res = await result.toJs() res.send(js_res.values) } finally { webR.destroy(result); } }) app.get('/:n', async (req, res) =&gt; { let result = await globalThis.webR.evalR( 'star_wars_by_species(n)', { env: { n: req.params.n } } ); try { const result_js = await result.toJs(); res.send(result_js) } finally { webR.destroy(result); } }); Full code is at ColinFay/webr-examples/. Further exploration to be done: webR now bundles a way to package a file system in a file, which can then be downloaded and mounted into the runtime, as described here. This might come handy for our current structure, but I’ll have to explore it a bit more.]]></summary></entry><entry><title type="html">Using my own R functions in webR in an Express JS API, and thoughts on building web apps with Node &amp;amp; webR</title><link href="https://colinfay.me/using-own-functions-in-webr-node-js/" rel="alternate" type="text/html" title="Using my own R functions in webR in an Express JS API, and thoughts on building web apps with Node &amp;amp; webR" /><published>2023-10-17T00:00:00+00:00</published><updated>2023-10-17T00:00:00+00:00</updated><id>https://colinfay.me/adding-my-r-function-to-webr</id><content type="html" xml:base="https://colinfay.me/using-own-functions-in-webr-node-js/"><![CDATA[<p>Find all my posts about webR <a href="https://colinfay.me/categories/#webr">here</a>.</p>

<blockquote>
  <p>Note: the first post of this series explaining roughly what <code class="language-plaintext highlighter-rouge">webR</code> is, I won’t introduce it again here.</p>
</blockquote>

<blockquote>
  <p>Note 2: webR and the tools I’ve been writing are moving fast and if you’re reading this from the future, some of the things here might be obsolete.</p>
</blockquote>

<h2 id="the-problem">The problem</h2>

<p>Ok, so now that we have our webR / NodeJS machinery up and running, let’s try something more interesting: use our own R functions inside <code class="language-plaintext highlighter-rouge">webR</code>.</p>

<p>How can I do that?</p>

<p>There are at least three ways I could think of:</p>

<p>1️⃣ Writing a function inside the JS code to define a function, something like :</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">await</span> <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">.</span><span class="nx">evalR</span><span class="p">(</span><span class="dl">"</span><span class="s2">my_fun &lt;- function(x){...}</span><span class="dl">"</span><span class="p">);</span>
</code></pre></div></div>

<p>But that doesn’t check what I would expect from something I’ll use in prod and I’m pretty sure you don’t need me to detail why 😅</p>

<ul>
  <li>❌ Well organized</li>
  <li>❌ Documented</li>
  <li>❌ Tested</li>
  <li>❌ Safely installable</li>
</ul>

<p>2️⃣ Simply create an R script and source it. Something like:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">fs</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">fs</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">path</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">path</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">script</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="dl">'</span><span class="s1">script.R</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">readFileSync</span><span class="p">(</span><span class="nx">script</span><span class="p">);</span>
<span class="k">await</span> <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">.</span><span class="nx">FS</span><span class="p">.</span><span class="nx">writeFile</span><span class="p">(</span>
  <span class="dl">"</span><span class="s2">/home/web_user/script.R</span><span class="dl">"</span><span class="p">,</span>
  <span class="nx">data</span>
<span class="p">);</span>
<span class="k">await</span> <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">.</span><span class="nx">evalR</span><span class="p">(</span><span class="dl">"</span><span class="s2">source('/home/web_user/script.R')</span><span class="dl">"</span><span class="p">);</span>
</code></pre></div></div>

<p>That’s a bit better, we can at least organize our code in a script and it will be:</p>

<ul>
  <li>✅ Well organized (more or less)</li>
  <li>✅ Documented (more or less)</li>
  <li>❌ Tested</li>
  <li>❌ Safely installable</li>
</ul>

<p>3️⃣ I bet you saw me coming, the best way let’s put stuff into an R package, so that we can check all the boxes.</p>

<ul>
  <li>✅ Well organized</li>
  <li>✅ Documented</li>
  <li>✅ Tested</li>
  <li>✅ Safely installable</li>
</ul>

<p>Jeroen has written a <a href="https://github.com/r-universe-org/build-wasm">Docker image</a> to compile an R package to WASM, but I was looking for something that wouldn’t involve compiling via a docker container every time I make a change on my R package (even if that does sound appealing, I’m pretty sure this wouldn’t make for a seamless workflow).</p>

<p>So here is what I’m thinking should be a well structured NodeJS / WebR app:</p>

<ul>
  <li>Putting all the web stuff inside the NodeJS app, because, well, NodeJS is really good at doing that.</li>
  <li>Putting all the “business logic”, data-crunching, modeling stuff (and everything R is really good at) into an R package.</li>
  <li>load webR, write my R package to the webR file system, and <code class="language-plaintext highlighter-rouge">pkgload::load_all()</code> it into webR.</li>
</ul>

<p>That way, I can enjoy the best of both worlds:</p>

<ul>
  <li>NodeJS is really good at doing web related things, and there are plenty of ways to test and deploy the code.</li>
  <li>And same goes for the R package: if you’re reading my blog I’m pretty sure I don’t need to convince you of why packages are the perfect tool for sharing production code.</li>
</ul>

<h2 id="the-how">The how</h2>

<p>Let’s start by creating our project:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir </span>webr-preload-funs
<span class="nb">cd </span>webr-preload-funs
npm init <span class="nt">-y</span>
<span class="nb">touch </span>index.js
npm <span class="nb">install </span>express webr
R <span class="nt">-e</span> <span class="s2">"usethis::create_package('rfuns', rstudio = FALSE)"</span>
</code></pre></div></div>

<p>Let’s now create a simple function :</p>

<div class="language-r highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span><span class="w"> </span><span class="n">usethis</span><span class="o">::</span><span class="n">use_r</span><span class="p">(</span><span class="s2">"sw"</span><span class="p">)</span><span class="w">
</span></code></pre></div></div>

<div class="language-r highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cd">#' @title Star Wars by Species</span><span class="w">
</span><span class="cd">#' @description Return a tibble of Star Wars characters by species</span><span class="w">
</span><span class="cd">#' @import dplyr</span><span class="w">
</span><span class="cd">#' @export</span><span class="w">
</span><span class="cd">#' @param species character</span><span class="w">
</span><span class="cd">#' @return tibble</span><span class="w">
</span><span class="cd">#' @examples</span><span class="w">
</span><span class="cd">#' star_wars_by_species("Human")</span><span class="w">
</span><span class="cd">#' star_wars_by_species("Droid")</span><span class="w">
</span><span class="cd">#' star_wars_by_species("Wookiee")</span><span class="w">
</span><span class="cd">#' star_wars_by_species("Rodian")</span><span class="w">
</span><span class="n">star_wars_by_species</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="k">function</span><span class="p">(</span><span class="n">species</span><span class="p">){</span><span class="w">
  </span><span class="n">dplyr</span><span class="o">::</span><span class="n">starwars</span><span class="w"> </span><span class="o">|&gt;</span><span class="w">
    </span><span class="n">filter</span><span class="p">(</span><span class="n">species</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>We can now add <code class="language-plaintext highlighter-rouge">{dplyr}</code> and <code class="language-plaintext highlighter-rouge">{pkgload}</code> to our <code class="language-plaintext highlighter-rouge">DESCRIPTION</code> (we’ll need <code class="language-plaintext highlighter-rouge">{pkgload}</code> to <code class="language-plaintext highlighter-rouge">load_all()</code> the package).</p>

<div class="language-r highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">usethis</span><span class="o">::</span><span class="n">use_package</span><span class="p">(</span><span class="s2">"dplyr"</span><span class="p">)</span><span class="w">
</span><span class="n">usethis</span><span class="o">::</span><span class="n">use_package</span><span class="p">(</span><span class="s2">"pkgload"</span><span class="p">)</span><span class="w">
</span><span class="n">devtools</span><span class="o">::</span><span class="n">document</span><span class="p">()</span><span class="w">
</span></code></pre></div></div>

<p>Now that we have a package skeleton, we’ll have to upload it to webR.</p>

<p>As described in the previous post, I’ve started a <code class="language-plaintext highlighter-rouge">webrtools</code> NodeJS module, which will contains function to play with webR.
Before this post, it had one function, <code class="language-plaintext highlighter-rouge">loadPackages</code>, that was used to build a webR dependency library (see <a href="https://colinfay.me/preloading-your-r-packages-in-webr-in-an-express-js-api/">Preloading your R packages in webR in an Express JS API</a> for more info).</p>

<p>We’ll need to add two features :</p>

<ul>
  <li>Install deps from DESCRIPTION (not just a package name), so a wrapper around the <code class="language-plaintext highlighter-rouge">Rscript ./node_modules/webrtools/r/install.R dplyr</code> from before</li>
  <li>Copy the package folder in NodeJS, so a more generic version of <code class="language-plaintext highlighter-rouge">loadPackages</code>, that can load any folder to the webR filesystem.</li>
</ul>

<p>First, in R, we’ll need to read the <code class="language-plaintext highlighter-rouge">DESCRIPTION</code> and build the lib:</p>

<div class="language-r highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">download_packs_and_deps_from_desc</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="k">function</span><span class="w"> </span><span class="p">(</span><span class="w">
  </span><span class="n">description</span><span class="p">,</span><span class="w">
  </span><span class="n">path_to_installation</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"./webr_packages"</span><span class="w">
</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="n">file.exists</span><span class="p">(</span><span class="n">description</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="n">stop</span><span class="p">(</span><span class="s2">"DESCRIPTION file not found"</span><span class="p">)</span><span class="w">
    </span><span class="p">}</span><span class="w">
    </span><span class="n">deps</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="n">desc</span><span class="o">::</span><span class="n">desc_get_deps</span><span class="p">(</span><span class="n">description</span><span class="p">)</span><span class="w">
    </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="n">pak</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">deps</span><span class="o">$</span><span class="n">package</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="n">webrtools</span><span class="o">::</span><span class="n">download_packs_and_deps</span><span class="p">(</span><span class="n">pak</span><span class="p">,</span><span class="w"> </span><span class="n">path_to_installation</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">path_to_installation</span><span class="p">)</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<blockquote>
  <p>Note: the code of <code class="language-plaintext highlighter-rouge">webrtools::download_packs_and_deps()</code> is a wrapper around the R code described in <a href="/preloading-your-r-packages-in-webr-in-an-express-js-api/">Preloading your R packages in webR in an Express JS API</a></p>
</blockquote>

<p>And in Node, we’ll rework our <code class="language-plaintext highlighter-rouge">loadPackages</code> and split it into two functions — one to load into any folder, and one to load into the package library.</p>

<p><strong>Note that this function is bundled in the <a href="https://www.npmjs.com/package/webrtools">webrtools</a> npm module</strong>.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="kd">function</span> <span class="nx">loadFolder</span><span class="p">(</span><span class="nx">webR</span><span class="p">,</span> <span class="nx">dirPath</span><span class="p">,</span> <span class="nx">outputdir</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">/usr/lib/R/library</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// function from webrtools</span>
  <span class="kd">const</span> <span class="nx">files</span> <span class="o">=</span> <span class="nx">getDirectoryTree</span><span class="p">(</span>
    <span class="nx">dirPath</span>
  <span class="p">)</span>
  <span class="k">for</span> <span class="k">await</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">file</span> <span class="k">of</span> <span class="nx">files</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">file</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">directory</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">await</span> <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">.</span><span class="nx">FS</span><span class="p">.</span><span class="nx">mkdir</span><span class="p">(</span>
        <span class="s2">`</span><span class="p">${</span><span class="nx">outputdir</span><span class="p">}</span><span class="s2">/</span><span class="p">${</span><span class="nx">file</span><span class="p">.</span><span class="nx">path</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span>
      <span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
      <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">readFileSync</span><span class="p">(</span><span class="s2">`</span><span class="p">${</span><span class="nx">dirPath</span><span class="p">}</span><span class="s2">/</span><span class="p">${</span><span class="nx">file</span><span class="p">.</span><span class="nx">path</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
      <span class="k">await</span> <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">.</span><span class="nx">FS</span><span class="p">.</span><span class="nx">writeFile</span><span class="p">(</span>
        <span class="s2">`</span><span class="p">${</span><span class="nx">outputdir</span><span class="p">}</span><span class="s2">/</span><span class="p">${</span><span class="nx">file</span><span class="p">.</span><span class="nx">path</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span>
        <span class="nx">data</span>
      <span class="p">);</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="k">async</span> <span class="kd">function</span> <span class="nx">loadPackages</span><span class="p">(</span><span class="nx">webR</span><span class="p">,</span> <span class="nx">dirPath</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">await</span> <span class="nx">loadFolder</span><span class="p">(</span><span class="nx">webR</span><span class="p">,</span> <span class="nx">dirPath</span><span class="p">,</span> <span class="nx">outputdir</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">/usr/lib/R/library</span><span class="dl">"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="the-end-app">The end app</h2>

<p>We now have everything we need!</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm i webrtools@0.0.2
Rscript ./node_modules/webrtools/r/install_from_desc.R $(pwd)/rfuns/DESCRIPTION
</code></pre></div></div>

<p>And now, to our index.js</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">express</span><span class="dl">'</span><span class="p">)()</span>
<span class="kd">const</span> <span class="nx">path</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">path</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">loadPackages</span><span class="p">,</span> <span class="nx">loadFolder</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">webrtools</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">WebR</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">webr</span><span class="dl">'</span><span class="p">);</span>

<span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">WebR</span><span class="p">();</span>
  <span class="k">await</span> <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">.</span><span class="nx">init</span><span class="p">();</span>

  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">🚀 webR is ready 🚀</span><span class="dl">"</span><span class="p">);</span>

  <span class="k">await</span> <span class="nx">loadPackages</span><span class="p">(</span>
    <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">,</span>
    <span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="dl">'</span><span class="s1">webr_packages</span><span class="dl">'</span><span class="p">)</span>
  <span class="p">)</span>

  <span class="k">await</span> <span class="nx">loadFolder</span><span class="p">(</span>
    <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">,</span>
    <span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="dl">'</span><span class="s1">rfuns</span><span class="dl">'</span><span class="p">),</span>
    <span class="dl">"</span><span class="s2">/home/web_user</span><span class="dl">"</span>
  <span class="p">)</span>

  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">📦 Packages written to webR 📦</span><span class="dl">"</span><span class="p">);</span>

  <span class="c1">// see https://github.com/r-wasm/webr/issues/292</span>
  <span class="k">await</span> <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">.</span><span class="nx">evalR</span><span class="p">(</span><span class="dl">"</span><span class="s2">options(expressions=1000)</span><span class="dl">"</span><span class="p">)</span>
  <span class="k">await</span> <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">.</span><span class="nx">evalR</span><span class="p">(</span><span class="dl">"</span><span class="s2">pkgload::load_all('/home/web_user')</span><span class="dl">"</span><span class="p">);</span>

  <span class="nx">app</span><span class="p">.</span><span class="nx">listen</span><span class="p">(</span><span class="mi">3000</span><span class="p">,</span> <span class="dl">'</span><span class="s1">0.0.0.0</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">http://localhost:3000</span><span class="dl">'</span><span class="p">)</span>
  <span class="p">})</span>

<span class="p">})();</span>

<span class="nx">app</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">let</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">.</span><span class="nx">evalR</span><span class="p">(</span>
    <span class="dl">'</span><span class="s1">unique(dplyr::starwars$species)</span><span class="dl">'</span>
  <span class="p">);</span>
  <span class="kd">let</span> <span class="nx">js_res</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">result</span><span class="p">.</span><span class="nx">toJs</span><span class="p">()</span>
  <span class="nx">res</span><span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="nx">js_res</span><span class="p">.</span><span class="nx">values</span><span class="p">)</span>
<span class="p">})</span>


<span class="nx">app</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/:n</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">let</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">.</span><span class="nx">evalR</span><span class="p">(</span>
    <span class="dl">'</span><span class="s1">star_wars_by_species(n)</span><span class="dl">'</span><span class="p">,</span>
    <span class="p">{</span> <span class="na">env</span><span class="p">:</span> <span class="p">{</span> <span class="na">n</span><span class="p">:</span> <span class="nx">req</span><span class="p">.</span><span class="nx">params</span><span class="p">.</span><span class="nx">n</span> <span class="p">}</span> <span class="p">}</span>
    <span class="p">);</span>
  <span class="k">try</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">result_js</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">result</span><span class="p">.</span><span class="nx">toJs</span><span class="p">();</span>
    <span class="nx">res</span><span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="nx">result_js</span><span class="p">)</span>
  <span class="p">}</span> <span class="k">finally</span> <span class="p">{</span>
    <span class="nx">webR</span><span class="p">.</span><span class="nx">destroy</span><span class="p">(</span><span class="nx">result</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>

<p>Let’s now try from another terminal:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl http://localhost:3000
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>["Human","Droid","Wookiee","Rodian","Hutt","Yoda's species","Trandoshan","Mon Calamari","Ewok","Sullustan","Neimodian","Gungan",null,"Toydarian","Dug","Zabrak","Twi'lek","Vulptereen","Xexto","Toong","Cerean","Nautolan","Tholothian","Iktotchi","Quermian","Kel Dor","Chagrian","Geonosian","Mirialan","Clawdite","Besalisk","Kaminoan","Aleena","Skakoan","Muun","Togruta","Kaleesh","Pau'an"]
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl http://localhost:3000/Rodian
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{"type":"list","names":["name","height","mass","hair_color","skin_color","eye_color","birth_year","sex","gender","homeworld","species","films","vehicles","starships"],"values":[{"type":"character","names":null,"values":["Greedo"]},{"type":"integer","names":null,"values":[173]},{"type":"double","names":null,"values":[74]},{"type":"character","names":null,"values":[null]},{"type":"character","names":null,"values":["green"]},{"type":"character","names":null,"values":["black"]},{"type":"double","names":null,"values":[44]},{"type":"character","names":null,"values":["male"]},{"type":"character","names":null,"values":["masculine"]},{"type":"character","names":null,"values":["Rodia"]},{"type":"character","names":null,"values":["Rodian"]},{"type":"list","names":null,"values":[{"type":"character","names":null,"values":["A New Hope"]}]},{"type":"list","names":null,"values":[{"type":"character","names":null,"values":[]}]},{"type":"list","names":null,"values":[{"type":"character","names":null,"values":[]}]}]}
</code></pre></div></div>

<p>Yeay 🎉 .</p>

<p>You can find the code <a href="https://github.com/ColinFay/webr-examples/tree/main/webr-preload-funs">here</a>, and see it live at <a href="https://srv.colinfay.me/webr-preload-funs/Rodian">srv.colinfay.me/webr-preload-funs/</a>.</p>

<p>You can also try it with</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run <span class="nt">-it</span> <span class="nt">-p</span> 3000:3000 colinfay/webr-preload-funs
</code></pre></div></div>]]></content><author><name>Colin FAY</name><email>contact@colinfay.me</email></author><category term="r-blog-en" /><category term="webr" /><summary type="html"><![CDATA[Find all my posts about webR here. Note: the first post of this series explaining roughly what webR is, I won’t introduce it again here. Note 2: webR and the tools I’ve been writing are moving fast and if you’re reading this from the future, some of the things here might be obsolete. The problem Ok, so now that we have our webR / NodeJS machinery up and running, let’s try something more interesting: use our own R functions inside webR. How can I do that? There are at least three ways I could think of: 1️⃣ Writing a function inside the JS code to define a function, something like : await globalThis.webR.evalR("my_fun &lt;- function(x){...}"); But that doesn’t check what I would expect from something I’ll use in prod and I’m pretty sure you don’t need me to detail why 😅 ❌ Well organized ❌ Documented ❌ Tested ❌ Safely installable 2️⃣ Simply create an R script and source it. Something like: const fs = require('fs'); const path = require('path'); const script = path.join(__dirname, 'script.R') const data = fs.readFileSync(script); await globalThis.webR.FS.writeFile( "/home/web_user/script.R", data ); await globalThis.webR.evalR("source('/home/web_user/script.R')"); That’s a bit better, we can at least organize our code in a script and it will be: ✅ Well organized (more or less) ✅ Documented (more or less) ❌ Tested ❌ Safely installable 3️⃣ I bet you saw me coming, the best way let’s put stuff into an R package, so that we can check all the boxes. ✅ Well organized ✅ Documented ✅ Tested ✅ Safely installable Jeroen has written a Docker image to compile an R package to WASM, but I was looking for something that wouldn’t involve compiling via a docker container every time I make a change on my R package (even if that does sound appealing, I’m pretty sure this wouldn’t make for a seamless workflow). So here is what I’m thinking should be a well structured NodeJS / WebR app: Putting all the web stuff inside the NodeJS app, because, well, NodeJS is really good at doing that. Putting all the “business logic”, data-crunching, modeling stuff (and everything R is really good at) into an R package. load webR, write my R package to the webR file system, and pkgload::load_all() it into webR. That way, I can enjoy the best of both worlds: NodeJS is really good at doing web related things, and there are plenty of ways to test and deploy the code. And same goes for the R package: if you’re reading my blog I’m pretty sure I don’t need to convince you of why packages are the perfect tool for sharing production code. The how Let’s start by creating our project: mkdir webr-preload-funs cd webr-preload-funs npm init -y touch index.js npm install express webr R -e "usethis::create_package('rfuns', rstudio = FALSE)" Let’s now create a simple function : &gt; usethis::use_r("sw") #' @title Star Wars by Species #' @description Return a tibble of Star Wars characters by species #' @import dplyr #' @export #' @param species character #' @return tibble #' @examples #' star_wars_by_species("Human") #' star_wars_by_species("Droid") #' star_wars_by_species("Wookiee") #' star_wars_by_species("Rodian") star_wars_by_species &lt;- function(species){ dplyr::starwars |&gt; filter(species == ) } We can now add {dplyr} and {pkgload} to our DESCRIPTION (we’ll need {pkgload} to load_all() the package). usethis::use_package("dplyr") usethis::use_package("pkgload") devtools::document() Now that we have a package skeleton, we’ll have to upload it to webR. As described in the previous post, I’ve started a webrtools NodeJS module, which will contains function to play with webR. Before this post, it had one function, loadPackages, that was used to build a webR dependency library (see Preloading your R packages in webR in an Express JS API for more info). We’ll need to add two features : Install deps from DESCRIPTION (not just a package name), so a wrapper around the Rscript ./node_modules/webrtools/r/install.R dplyr from before Copy the package folder in NodeJS, so a more generic version of loadPackages, that can load any folder to the webR filesystem. First, in R, we’ll need to read the DESCRIPTION and build the lib: download_packs_and_deps_from_desc &lt;- function ( description, path_to_installation = "./webr_packages" ) { if (!file.exists(description)) { stop("DESCRIPTION file not found") } deps &lt;- desc::desc_get_deps(description) for (pak in deps$package) { webrtools::download_packs_and_deps(pak, path_to_installation = path_to_installation) } } Note: the code of webrtools::download_packs_and_deps() is a wrapper around the R code described in Preloading your R packages in webR in an Express JS API And in Node, we’ll rework our loadPackages and split it into two functions — one to load into any folder, and one to load into the package library. Note that this function is bundled in the webrtools npm module. async function loadFolder(webR, dirPath, outputdir = "/usr/lib/R/library") { // function from webrtools const files = getDirectoryTree( dirPath ) for await (const file of files) { if (file.type === 'directory') { await globalThis.webR.FS.mkdir( `${outputdir}/${file.path}`, ); } else { const data = fs.readFileSync(`${dirPath}/${file.path}`); await globalThis.webR.FS.writeFile( `${outputdir}/${file.path}`, data ); } } } async function loadPackages(webR, dirPath) { await loadFolder(webR, dirPath, outputdir = "/usr/lib/R/library"); } The end app We now have everything we need! npm i webrtools@0.0.2 Rscript ./node_modules/webrtools/r/install_from_desc.R $(pwd)/rfuns/DESCRIPTION And now, to our index.js const app = require('express')() const path = require('path'); const { loadPackages, loadFolder } = require('webrtools'); const { WebR } = require('webr'); (async () =&gt; { globalThis.webR = new WebR(); await globalThis.webR.init(); console.log("🚀 webR is ready 🚀"); await loadPackages( globalThis.webR, path.join(__dirname, 'webr_packages') ) await loadFolder( globalThis.webR, path.join(__dirname, 'rfuns'), "/home/web_user" ) console.log("📦 Packages written to webR 📦"); // see https://github.com/r-wasm/webr/issues/292 await globalThis.webR.evalR("options(expressions=1000)") await globalThis.webR.evalR("pkgload::load_all('/home/web_user')"); app.listen(3000, '0.0.0.0', () =&gt; { console.log('http://localhost:3000') }) })(); app.get('/', async (req, res) =&gt; { let result = await globalThis.webR.evalR( 'unique(dplyr::starwars$species)' ); let js_res = await result.toJs() res.send(js_res.values) }) app.get('/:n', async (req, res) =&gt; { let result = await globalThis.webR.evalR( 'star_wars_by_species(n)', { env: { n: req.params.n } } ); try { const result_js = await result.toJs(); res.send(result_js) } finally { webR.destroy(result); } }); Let’s now try from another terminal: curl http://localhost:3000 ["Human","Droid","Wookiee","Rodian","Hutt","Yoda's species","Trandoshan","Mon Calamari","Ewok","Sullustan","Neimodian","Gungan",null,"Toydarian","Dug","Zabrak","Twi'lek","Vulptereen","Xexto","Toong","Cerean","Nautolan","Tholothian","Iktotchi","Quermian","Kel Dor","Chagrian","Geonosian","Mirialan","Clawdite","Besalisk","Kaminoan","Aleena","Skakoan","Muun","Togruta","Kaleesh","Pau'an"] curl http://localhost:3000/Rodian {"type":"list","names":["name","height","mass","hair_color","skin_color","eye_color","birth_year","sex","gender","homeworld","species","films","vehicles","starships"],"values":[{"type":"character","names":null,"values":["Greedo"]},{"type":"integer","names":null,"values":[173]},{"type":"double","names":null,"values":[74]},{"type":"character","names":null,"values":[null]},{"type":"character","names":null,"values":["green"]},{"type":"character","names":null,"values":["black"]},{"type":"double","names":null,"values":[44]},{"type":"character","names":null,"values":["male"]},{"type":"character","names":null,"values":["masculine"]},{"type":"character","names":null,"values":["Rodia"]},{"type":"character","names":null,"values":["Rodian"]},{"type":"list","names":null,"values":[{"type":"character","names":null,"values":["A New Hope"]}]},{"type":"list","names":null,"values":[{"type":"character","names":null,"values":[]}]},{"type":"list","names":null,"values":[{"type":"character","names":null,"values":[]}]}]} Yeay 🎉 . You can find the code here, and see it live at srv.colinfay.me/webr-preload-funs/. You can also try it with docker run -it -p 3000:3000 colinfay/webr-preload-funs]]></summary></entry><entry><title type="html">Preloading your R packages in webR in an Express JS API</title><link href="https://colinfay.me/preloading-your-r-packages-in-webr-in-an-express-js-api/" rel="alternate" type="text/html" title="Preloading your R packages in webR in an Express JS API" /><published>2023-09-04T00:00:00+00:00</published><updated>2023-09-04T00:00:00+00:00</updated><id>https://colinfay.me/preloading-your-r-packages-in-webr-in-an-express-js-api</id><content type="html" xml:base="https://colinfay.me/preloading-your-r-packages-in-webr-in-an-express-js-api/"><![CDATA[<p>Find all my posts about webR <a href="https://colinfay.me/categories/#webr">here</a>.</p>

<blockquote>
  <p>Note: the first post of this series explaining roughly what <code class="language-plaintext highlighter-rouge">webR</code> is, I won’t introduce it again here.</p>
</blockquote>

<blockquote>
  <p>Note 2 (or Disclaimer): this is kind of a long post that get into the weeds of package installation and library structure</p>
</blockquote>

<blockquote>
  <p>Note 3: webR and the tools I’ve been writing are moving fast and if you’re reading this from the future, some of the things here might be obsolete.</p>
</blockquote>

<h2 id="the-problem">The problem</h2>

<p>In the two last posts, I’ve been deploying two REST APIs that use functions from the base distribution of R:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">tools::toTitleCase()</code> in <a href="https://srv.colinfay.me/express-webr-hello-world" target="_blank">express-webr-hello-world</a></li>
  <li><code class="language-plaintext highlighter-rouge">cut()</code> &amp; <code class="language-plaintext highlighter-rouge">faithful</code> for <a href="https://srv.colinfay.me/express-webr-old-faithful/" target="_blank">express-webr-old-faithful</a></li>
</ul>

<p>One of the strengths of the R ecosystem is its extensive list of contributed packages: a wide collection of code written by R nerds from all over the world.
It would be unfortunate to use <code class="language-plaintext highlighter-rouge">webR</code> to create a web applications or API without taking advantage of this extensive library of packages: R wouldn’t be R without its packages.</p>

<p>Yet, bringing packages to <code class="language-plaintext highlighter-rouge">webR</code> is not that simple: it’s a distribution of R compiled for WASM, and you can’t just install a package from any CRAN repo.</p>

<p>Thankfully we don’t need to scratch our head too much anymore: the <code class="language-plaintext highlighter-rouge">webR</code> team has precompiled more than 18000 (at the time of writing this post) packages that we can install by simply calling <code class="language-plaintext highlighter-rouge">webr::install()</code> inside the <code class="language-plaintext highlighter-rouge">webR</code> instance.</p>

<p>That’s awesome, but there’s an issue with that: you can only install it from the <code class="language-plaintext highlighter-rouge">webR</code> JavaScript object
As far as I know, there is no way to download them beforehand and upload them to the <code class="language-plaintext highlighter-rouge">webR</code> filesystem, as I reported in <a href="https://github.com/r-wasm/webr/issues/260" target="_blank">this issue</a>.
(Note: at the time of writing these lines, there has been some discussion on the GH issue, but there is at the moment no native, efficient way to do what I want to do 😅 — there might be one though if you’re reading this post in the future).</p>

<p>That means that every time the API is launched, <code class="language-plaintext highlighter-rouge">webR</code> has to download and reinstall the packages, as shown by this simple example:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">express</span><span class="dl">'</span><span class="p">)()</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">WebR</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">webr</span><span class="dl">'</span><span class="p">);</span>

<span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">WebR</span><span class="p">();</span>
  <span class="k">await</span> <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">.</span><span class="nx">init</span><span class="p">();</span>
  <span class="k">await</span> <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">.</span><span class="nx">installPackages</span><span class="p">([</span><span class="dl">'</span><span class="s1">attempt</span><span class="dl">'</span><span class="p">])</span>
  <span class="nx">app</span><span class="p">.</span><span class="nx">listen</span><span class="p">(</span><span class="mi">3000</span><span class="p">,</span> <span class="dl">'</span><span class="s1">0.0.0.0</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">http://localhost:3000</span><span class="dl">'</span><span class="p">)</span>
  <span class="p">})</span>
<span class="p">})();</span>


<span class="nx">app</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">res</span><span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="dl">"</span><span class="s2">hello</span><span class="dl">"</span><span class="p">)</span>
<span class="p">});</span>
</code></pre></div></div>

<p>If I launch the app twice, the packages are downloaded twice 👇</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>node index.js
webR is ready
Downloading webR package: rlang

Downloading webR package: attempt

http://localhost:3000

<span class="nv">$ </span>node index.js
webR is ready
Downloading webR package: rlang

Downloading webR package: attempt

http://localhost:3000
</code></pre></div></div>

<p>This approach is definitely suboptimal: imagine having to reinstall packages every time you <code class="language-plaintext highlighter-rouge">source()</code> an R script, instead of benefiting from pre-installed packages in your lib.
That’s kind of what is happening right now in this <code class="language-plaintext highlighter-rouge">webR</code> example.</p>

<p>Sure, it works, but it means that:</p>

<ul>
  <li>I’m downloading the (same) packages every time the app is launched, which feels weird</li>
  <li>If I have a lot of packages, it would take a lot of time every time</li>
  <li>I can’t deploy this on a service that has no access to internet</li>
  <li>I can’t build my app just as any other language does: by relying on pre-downloaded /pre-installed modules</li>
  <li>I can’t benefit from caching the package installation in a Docker layer</li>
</ul>

<p>So, faced with something I needed but didn’t find, I behave like any other software engineer: I built my own solution to pre-load R packages in <code class="language-plaintext highlighter-rouge">webR</code>, without having to re-download them every time.
The rest of this blogpost explains this process.</p>

<h2 id="preloading-r-packages-in-my-expressjswebr-api">Preloading R packages in my ExpressJS/webR API</h2>

<h3 id="building-the-r-lib">Building the R lib</h3>

<p>If you look at the internal of the folder on your computer that contains your packages (which is at <code class="language-plaintext highlighter-rouge">.libPaths()</code>), you’ll see that it’s a series of folders with a bunch of files.
Whenever you load R and call <code class="language-plaintext highlighter-rouge">library()</code>, it looks there to load the code.</p>

<p>Inside these folders (you can have several lib), you’ll find unzipped archives, downloaded from a repository (or installed from git/developed locally).
These packages, at installation time, possibly performed some build and compilation, depending on the package and the OS you’re on.
I won’t go into the details of this here, as the packages for webR have been pre-compiled, meaning that you simply have to download the tar file, and unarchive it at the correct place.
In other words, the content of this archive is exactly what you’ll find in the library folder, and there is nothing to compile/install again.</p>

<p>Keeping this in mind, the first thing to do then is to create a folder that will contain all the folders and files necessary to load a package.
In other words, if I want to use <code class="language-plaintext highlighter-rouge">{dplyr}</code>, I need to find a way to download the pre-compiled version of <code class="language-plaintext highlighter-rouge">{dplyr}</code> and all its dependencies from <code class="language-plaintext highlighter-rouge">https://repo.r-wasm.org/</code>, and untar them into the folder I wanna use inside my project.</p>

<p>R has a weird way of managing packages, and if ever you want to hear me monologue, catch me at a conference and ask me to talk about this for 30 minutes (just kidding, it will probably last one hour).
I won’t go into details about how it works and why it’s weird, but here are some issues I’ve been facing for my current task:</p>

<ul>
  <li>
    <p>By default R will not reinstall the dependencies if they are already installed on the machine. Which is great, but not in my case (I want the destination folder to contain the entire dependency tree). In other words, if I do <code class="language-plaintext highlighter-rouge">install.packages("dplyr", lib="webr_packages")</code>, R will only download the packages that are not already on my computer.</p>
  </li>
  <li>
    <p>There is no (sane) way to avoid the default library. So basically unless you’re ready to write hacky code that will override the library environments and stuff, R will always look into the default lib. There is an old issue on the <a href="https://github.com/r-lib/withr/issues/122" target="_blank"><code class="language-plaintext highlighter-rouge">{withr}</code> repo </a> if you want to look into this, but basically the idea is that avoiding default lib is quite a challenge.</p>
  </li>
  <li>
    <p>That means that if I try to do something as simple as <code class="language-plaintext highlighter-rouge">install.packages("dplyr", lib="webr_packages", repos = "https://repo.r-wasm.org/")</code>, it will only install <code class="language-plaintext highlighter-rouge">{dplyr}</code>, given that all the other dependencies already exist on my machine :</p>
  </li>
</ul>

<div class="language-r highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span><span class="w"> </span><span class="n">setwd</span><span class="p">(</span><span class="n">tempdir</span><span class="p">())</span><span class="w">
</span><span class="o">&gt;</span><span class="w"> </span><span class="n">dir.create</span><span class="p">(</span><span class="s2">"webr_packages"</span><span class="p">)</span><span class="w">
</span><span class="o">&gt;</span><span class="w"> </span><span class="n">install.packages</span><span class="p">(</span><span class="s2">"dplyr"</span><span class="p">,</span><span class="w"> </span><span class="n">lib</span><span class="o">=</span><span class="s2">"webr_packages"</span><span class="p">,</span><span class="w"> </span><span class="n">repos</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"https://repo.r-wasm.org/"</span><span class="p">)</span><span class="w">
</span><span class="c1"># [...TRUNCATED]</span><span class="w">
</span><span class="o">*</span><span class="w"> </span><span class="n">DONE</span><span class="w"> </span><span class="p">(</span><span class="n">dplyr</span><span class="p">)</span><span class="w">
</span><span class="o">&gt;</span><span class="w"> </span><span class="n">list.files</span><span class="p">(</span><span class="s2">"webr_packages/"</span><span class="p">)</span><span class="w">
</span><span class="p">[</span><span class="m">1</span><span class="p">]</span><span class="w"> </span><span class="s2">"dplyr"</span><span class="w">
</span></code></pre></div></div>

<p>I’ve tried the following:</p>

<ul>
  <li>
    <p>Hacking <code class="language-plaintext highlighter-rouge">.lib.loc</code> as suggest by Andrie <a href="https://stackoverflow.com/a/36873741">here</a>, but it comes with a series of challenges like not having access to other packages and having to reinstall them and load them to the webR FileSystem which is not what I wanted.</p>
  </li>
  <li>
    <p>Using <code class="language-plaintext highlighter-rouge">{renv}</code> but it feels a bit overkill for what I’m trying to do: download a bunch of zip files from the internet and unzip them in the correct folder. Also, <code class="language-plaintext highlighter-rouge">{renv}</code> failed to parse the dep tree in a NodeJS project for some reasons.</p>
  </li>
</ul>

<p>So I’ve ended up with my own R script, inspired by the internal of <code class="language-plaintext highlighter-rouge">install.packages</code> and <code class="language-plaintext highlighter-rouge">webr::install</code> (<a href="https://blog.codinghorror.com/learn-to-read-the-source-luke/">Read the source, Luke</a>).
I’ve first explored a version with <code class="language-plaintext highlighter-rouge">{pak}</code> and <code class="language-plaintext highlighter-rouge">{pkgdepends}</code>, but ended up using a base solution so that I don’t have to install anything in the Docker container.</p>

<div class="language-r highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#!/usr/bin/env Rscript</span><span class="w">

</span><span class="c1"># This script only works if you pass it at least one arg</span><span class="w">
</span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nf">is.na</span><span class="p">(</span><span class="n">commandArgs</span><span class="p">(</span><span class="n">trailingOnly</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">TRUE</span><span class="p">)[</span><span class="m">1</span><span class="p">]))</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="n">stop</span><span class="p">(</span><span class="s2">"This script requires at least one argument"</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="n">pk_to_install</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="n">commandArgs</span><span class="p">(</span><span class="n">trailingOnly</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">TRUE</span><span class="p">)[</span><span class="m">1</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c1"># If no second value, defaulting to webr_packages</span><span class="w">
</span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nf">is.na</span><span class="p">(</span><span class="n">commandArgs</span><span class="p">(</span><span class="n">trailingOnly</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">TRUE</span><span class="p">)[</span><span class="m">2</span><span class="p">]))</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="n">path_to_installation</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="s2">"./webr_packages"</span><span class="w">
</span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="n">path_to_installation</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="n">commandArgs</span><span class="p">(</span><span class="n">trailingOnly</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">TRUE</span><span class="p">)[</span><span class="m">2</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c1"># Build the repos url</span><span class="w">
</span><span class="n">repos</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="n">sprintf</span><span class="p">(</span><span class="w">
  </span><span class="s2">"https://repo.r-wasm.org/bin/emscripten/contrib/%s.%s"</span><span class="p">,</span><span class="w">
  </span><span class="c1"># Get major R version</span><span class="w">
  </span><span class="n">R.version</span><span class="o">$</span><span class="n">major</span><span class="p">,</span><span class="w">
  </span><span class="n">substr</span><span class="p">(</span><span class="n">R.version</span><span class="o">$</span><span class="n">minor</span><span class="p">,</span><span class="w"> </span><span class="m">1</span><span class="p">,</span><span class="w"> </span><span class="m">1</span><span class="p">)</span><span class="w">
</span><span class="p">)</span><span class="w">

</span><span class="n">deps</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="n">unique</span><span class="p">(</span><span class="w">
  </span><span class="n">unlist</span><span class="p">(</span><span class="w">
    </span><span class="n">use.names</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">FALSE</span><span class="p">,</span><span class="w">
    </span><span class="n">tools</span><span class="o">::</span><span class="n">package_dependencies</span><span class="p">(</span><span class="w">
      </span><span class="n">recursive</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">TRUE</span><span class="p">,</span><span class="w">
      </span><span class="n">pk_to_install</span><span class="w">
    </span><span class="p">)</span><span class="w">
  </span><span class="p">)</span><span class="w">
</span><span class="p">)</span><span class="w">

</span><span class="c1"># Now the package list</span><span class="w">
</span><span class="n">pkg_deps</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="nf">c</span><span class="p">(</span><span class="w">
  </span><span class="n">pk_to_install</span><span class="p">,</span><span class="w">
  </span><span class="n">deps</span><span class="w">
</span><span class="p">)</span><span class="w">

</span><span class="c1"># I don't want to re-download things so I'm listing all the already</span><span class="w">
</span><span class="c1"># downloaded folders</span><span class="w">
</span><span class="n">already_installed</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="n">list.files</span><span class="p">(</span><span class="w">
  </span><span class="n">path_to_installation</span><span class="w">
</span><span class="p">)</span><span class="w">

</span><span class="c1"># Getting the list of available package</span><span class="w">
</span><span class="n">info</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="n">utils</span><span class="o">::</span><span class="n">available.packages</span><span class="p">(</span><span class="n">contriburl</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">repos</span><span class="p">)</span><span class="w">

</span><span class="c1"># Now we can install</span><span class="w">
</span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="n">pkg</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">pkg_deps</span><span class="p">){</span><span class="w">
  </span><span class="c1"># Don't redownload things</span><span class="w">
  </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">pkg</span><span class="w"> </span><span class="o">%in%</span><span class="w"> </span><span class="n">already_installed</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">message</span><span class="p">(</span><span class="s2">"Package "</span><span class="p">,</span><span class="w"> </span><span class="n">pkg</span><span class="p">,</span><span class="w"> </span><span class="s2">" already installed"</span><span class="p">)</span><span class="w">
    </span><span class="k">next</span><span class="w">
  </span><span class="p">}</span><span class="w">
  </span><span class="c1"># Not available: either it's not on the repo or it's included</span><span class="w">
  </span><span class="c1"># in the base distribution of R</span><span class="w">
  </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">pkg</span><span class="w"> </span><span class="o">%in%</span><span class="w"> </span><span class="n">info</span><span class="p">[,</span><span class="w"> </span><span class="s2">"Package"</span><span class="p">]))</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">message</span><span class="p">(</span><span class="s2">"Package {"</span><span class="p">,</span><span class="w"> </span><span class="n">pkg</span><span class="p">,</span><span class="w"> </span><span class="s2">"} not found in repo (unavailable or is base package)"</span><span class="p">)</span><span class="w">
    </span><span class="k">next</span><span class="w">
  </span><span class="p">}</span><span class="w">
  </span><span class="n">message</span><span class="p">(</span><span class="s2">"Installing "</span><span class="p">,</span><span class="w"> </span><span class="n">pkg</span><span class="p">)</span><span class="w">
  </span><span class="c1"># Name of the targz, should be on the form of pkg_version.this.that.tgz</span><span class="w">
  </span><span class="n">targz_file</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="n">paste0</span><span class="p">(</span><span class="n">pkg</span><span class="p">,</span><span class="w"> </span><span class="s2">"_"</span><span class="p">,</span><span class="w"> </span><span class="n">info</span><span class="p">[</span><span class="n">info</span><span class="p">[,</span><span class="w"> </span><span class="s2">"Package"</span><span class="p">]</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">pkg</span><span class="p">,</span><span class="w"> </span><span class="s2">"Version"</span><span class="p">],</span><span class="w"> </span><span class="s2">".tgz"</span><span class="p">)</span><span class="w">
  </span><span class="c1"># Location of file on the repo</span><span class="w">
  </span><span class="n">url</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="n">paste0</span><span class="p">(</span><span class="n">repos</span><span class="p">,</span><span class="w"> </span><span class="s2">"/"</span><span class="p">,</span><span class="w"> </span><span class="n">targz_file</span><span class="p">)</span><span class="w">
  </span><span class="c1"># Download the file and untar the archive on the local lib</span><span class="w">
  </span><span class="n">tmp</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="n">tempfile</span><span class="p">(</span><span class="n">fileext</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">".tgz"</span><span class="p">)</span><span class="w">
  </span><span class="n">download.file</span><span class="p">(</span><span class="n">url</span><span class="p">,</span><span class="w"> </span><span class="n">destfile</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">tmp</span><span class="p">)</span><span class="w">
  </span><span class="n">untar</span><span class="p">(</span><span class="n">tmp</span><span class="p">,</span><span class="w"> </span><span class="n">exdir</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">path_to_installation</span><span class="p">)</span><span class="w">
  </span><span class="n">unlink</span><span class="p">(</span><span class="n">tmp</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="n">message</span><span class="p">(</span><span class="s2">"Done"</span><span class="p">)</span><span class="w">
</span></code></pre></div></div>

<p>And now, let’s try this:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir </span>webr_packages
script tools/installR.r attempt
✔ Loading metadata database ... <span class="k">done
</span>Installing attempt
trying URL <span class="s1">'https://repo.r-wasm.org/bin/emscripten/contrib/4.3/attempt_0.3.1.tgz'</span>
Content <span class="nb">type</span> <span class="s1">'application/x-tar'</span> length 93561 bytes <span class="o">(</span>91 KB<span class="o">)</span>
<span class="o">==================================================</span>
downloaded 91 KB

Installing rlang
trying URL <span class="s1">'https://repo.r-wasm.org/bin/emscripten/contrib/4.3/rlang_1.1.1.tgz'</span>
Content <span class="nb">type</span> <span class="s1">'application/x-tar'</span> length 1062482 bytes <span class="o">(</span>1.0 MB<span class="o">)</span>
<span class="o">==================================================</span>
downloaded 1.0 MB

Done

Rscript <span class="nt">-e</span> <span class="s2">"fs::dir_tree('webr_packages')"</span>
webr_packages
├── attempt
│   ├── DESCRIPTION
│   ├── INDEX
│   ├── LICENSE
│   ├── Meta
│   │   ├── Rd.rds
│   │   ├── features.rds
│   │   ├── hsearch.rds
│   │   ├── links.rds
│   │   ├── nsInfo.rds
│   │   ├── package.rds
│   │   └── vignette.rds
│   ├── NAMESPACE
│   ├── NEWS.md
│   ├── R
│   │   ├── attempt
│   │   ├── attempt.rdb
│   │   └── attempt.rdx
│   ├── doc
│   │   ├── a_intro_attempt.R
│   │   ├── a_intro_attempt.Rmd
│   │   ├── a_intro_attempt.html
│   │   ├── b_try_catch.R
│   │   ├── b_try_catch.Rmd
│   │   ├── b_try_catch.html
│   │   ├── c_adverbs.R
│   │   ├── c_adverbs.Rmd
│   │   ├── c_adverbs.html
│   │   ├── d_if.R
│   │   ├── d_if.Rmd
│   │   ├── d_if.html
│   │   ├── e_conditions.R
│   │   ├── e_conditions.Rmd
│   │   ├── e_conditions.html
│   │   └── index.html
│   ├── <span class="nb">help</span>
│   │   ├── AnIndex
│   │   ├── aliases.rds
│   │   ├── attempt.rdb
│   │   ├── attempt.rdx
│   │   └── paths.rds
│   └── html
│       ├── 00Index.html
│       └── R.css
└── rlang
    ├── DESCRIPTION
    ├── INDEX
    ├── LICENSE
    ├── Meta
    │   ├── Rd.rds
    │   ├── features.rds
    │   ├── hsearch.rds
    │   ├── links.rds
    │   ├── nsInfo.rds
    │   └── package.rds
    ├── NAMESPACE
    ├── NEWS.md
    ├── R
    │   ├── rlang
    │   ├── rlang.rdb
    │   └── rlang.rdx
    ├── backtrace-ver
    ├── <span class="nb">help</span>
    │   ├── AnIndex
    │   ├── aliases.rds
    │   ├── figures
    │   │   ├── lifecycle-archived.svg
    │   │   ├── lifecycle-defunct.svg
    │   │   ├── lifecycle-deprecated.svg
    │   │   ├── lifecycle-experimental.svg
    │   │   ├── lifecycle-maturing.svg
    │   │   ├── lifecycle-questioning.svg
    │   │   ├── lifecycle-retired.svg
    │   │   ├── lifecycle-soft-deprecated.svg
    │   │   ├── lifecycle-stable.svg
    │   │   ├── lifecycle-superseded.svg
    │   │   └── logo.png
    │   ├── paths.rds
    │   ├── rlang.rdb
    │   └── rlang.rdx
    ├── html
    │   ├── 00Index.html
    │   └── R.css
    └── libs
        └── rlang.so
</code></pre></div></div>

<p>Woot woot, seems to work 🎉.</p>

<h3 id="moving-everything-to-the-webr-lib-when-the-api-is-launched">Moving everything to the webR lib when the API is launched</h3>

<p>And now, challenge number two: rebuilding this library in the <code class="language-plaintext highlighter-rouge">webR</code> filesystem.</p>

<p><code class="language-plaintext highlighter-rouge">webR</code> has an interface to WASM filesystem (aka Emscripten Virtual File System), exposed via <code class="language-plaintext highlighter-rouge">FS</code> (<a href="https://docs.r-wasm.org/webr/latest/api/js/interfaces/WebR.WebRFS.html">doc is here</a>).
This interface allows to create a directory and write a file, which are the two things we want to do here:</p>

<ul>
  <li>Create the package folders/subfolders</li>
  <li>Copy the files from the local machine (contained in a <code class="language-plaintext highlighter-rouge">./webr_packages</code> folder) into the <code class="language-plaintext highlighter-rouge">webR</code> filesystem when launching the Node API.</li>
</ul>

<h4 id="creating-the-directory-tree">Creating the directory tree</h4>

<p>Creating a directory tree is not an easy task — it implies recursive function, keeping track of parents, and other weird things.
R has <code class="language-plaintext highlighter-rouge">list.files(recursive = TRUE)</code> and we should be VERY happy about it: I looked on the internet and apparently there is no native way to do that in Node, you have to do it by hand.</p>

<p>As any sane modern JavaScript developer would do, I asked chatGPT to do it for me.
It took me several iterations before getting the exact answer I was looking for but here it is.</p>

<p><strong>Note that the JS functions below are bundled in the <a href="https://www.npmjs.com/package/webrtools">webrtools</a> npm module</strong>.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">fs</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">fs</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">path</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">path</span><span class="dl">'</span><span class="p">);</span>

<span class="c1">// comments are mine</span>

<span class="c1">// Recursive function to get files, thank you chatGPT</span>
<span class="kd">function</span> <span class="nx">getDirectoryTree</span><span class="p">(</span><span class="nx">dirPath</span><span class="p">,</span> <span class="nx">currentPath</span> <span class="o">=</span> <span class="dl">''</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// Get a list of all the files/folders in the currently inspected folder</span>
  <span class="kd">const</span> <span class="nx">items</span> <span class="o">=</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">readdirSync</span><span class="p">(</span><span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">dirPath</span><span class="p">,</span> <span class="nx">currentPath</span><span class="p">));</span>
  <span class="c1">// output</span>
  <span class="kd">const</span> <span class="nx">tree</span> <span class="o">=</span> <span class="p">[];</span>

  <span class="c1">// Looping on all the files/folders</span>
  <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">item</span> <span class="k">of</span> <span class="nx">items</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// Inspecting the item</span>
    <span class="kd">const</span> <span class="nx">itemPath</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">dirPath</span><span class="p">,</span> <span class="nx">currentPath</span><span class="p">,</span> <span class="nx">item</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">isDirectory</span> <span class="o">=</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">statSync</span><span class="p">(</span><span class="nx">itemPath</span><span class="p">).</span><span class="nx">isDirectory</span><span class="p">();</span>
    <span class="kd">const</span> <span class="nx">relativePath</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">currentPath</span><span class="p">,</span> <span class="nx">item</span><span class="p">);</span>

    <span class="k">if</span> <span class="p">(</span><span class="nx">isDirectory</span><span class="p">)</span> <span class="p">{</span>
      <span class="c1">// If it's a directory, we'll go inside and repeat the</span>
      <span class="c1">// current mechanism</span>
      <span class="kd">const</span> <span class="nx">subtree</span> <span class="o">=</span> <span class="nx">getDirectoryTree</span><span class="p">(</span><span class="nx">dirPath</span><span class="p">,</span> <span class="nx">relativePath</span><span class="p">);</span>
      <span class="c1">// pushing the current item, and its subtree</span>
      <span class="nx">tree</span><span class="p">.</span><span class="nx">push</span><span class="p">({</span> <span class="na">path</span><span class="p">:</span> <span class="nx">relativePath</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">directory</span><span class="dl">'</span> <span class="p">});</span>
      <span class="nx">tree</span><span class="p">.</span><span class="nx">push</span><span class="p">(...</span><span class="nx">subtree</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
      <span class="nx">tree</span><span class="p">.</span><span class="nx">push</span><span class="p">({</span> <span class="na">path</span><span class="p">:</span> <span class="nx">relativePath</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">});</span>
    <span class="p">}</span>
  <span class="p">}</span>
  <span class="c1">// Getting the tree</span>
  <span class="k">return</span> <span class="nx">tree</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Here is what it looks like on our <code class="language-plaintext highlighter-rouge">./webr_packages</code> folder from before:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> <span class="nx">getDirectoryTree</span><span class="p">(</span><span class="dl">"</span><span class="s2">webr_packages</span><span class="dl">"</span><span class="p">)</span>
<span class="p">[</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">directory</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/DESCRIPTION</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/INDEX</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/LICENSE</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/Meta</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">directory</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/Meta/Rd.rds</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/Meta/features.rds</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/Meta/hsearch.rds</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/Meta/links.rds</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/Meta/nsInfo.rds</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/Meta/package.rds</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/Meta/vignette.rds</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/NAMESPACE</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/NEWS.md</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/R</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">directory</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/R/attempt</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/R/attempt.rdb</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/R/attempt.rdx</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/doc</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">directory</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/doc/a_intro_attempt.R</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/doc/a_intro_attempt.Rmd</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/doc/a_intro_attempt.html</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/doc/b_try_catch.R</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/doc/b_try_catch.Rmd</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/doc/b_try_catch.html</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/doc/c_adverbs.R</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/doc/c_adverbs.Rmd</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/doc/c_adverbs.html</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/doc/d_if.R</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/doc/d_if.Rmd</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/doc/d_if.html</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/doc/e_conditions.R</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/doc/e_conditions.Rmd</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/doc/e_conditions.html</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/doc/index.html</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/help</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">directory</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/help/AnIndex</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/help/aliases.rds</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/help/attempt.rdb</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/help/attempt.rdx</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/help/paths.rds</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/html</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">directory</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/html/00Index.html</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">attempt/html/R.css</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">directory</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/DESCRIPTION</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/INDEX</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/LICENSE</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/Meta</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">directory</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/Meta/Rd.rds</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/Meta/features.rds</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/Meta/hsearch.rds</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/Meta/links.rds</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/Meta/nsInfo.rds</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/Meta/package.rds</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/NAMESPACE</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/NEWS.md</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/R</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">directory</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/R/rlang</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/R/rlang.rdb</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/R/rlang.rdx</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/backtrace-ver</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/help</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">directory</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/help/AnIndex</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/help/aliases.rds</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/help/figures</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">directory</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/help/figures/lifecycle-archived.svg</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/help/figures/lifecycle-defunct.svg</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/help/figures/lifecycle-deprecated.svg</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/help/figures/lifecycle-experimental.svg</span><span class="dl">'</span><span class="p">,</span><span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span><span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/help/figures/lifecycle-maturing.svg</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/help/figures/lifecycle-questioning.svg</span><span class="dl">'</span><span class="p">,</span><span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span><span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/help/figures/lifecycle-retired.svg</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/help/figures/lifecycle-soft-deprecated.svg</span><span class="dl">'</span><span class="p">,</span><span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span><span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/help/figures/lifecycle-stable.svg</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/help/figures/lifecycle-superseded.svg</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/help/figures/logo.png</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/help/paths.rds</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/help/rlang.rdb</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/help/rlang.rdx</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/html</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">directory</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/html/00Index.html</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/html/R.css</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/libs</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">directory</span><span class="dl">'</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rlang/libs/rlang.so</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span> <span class="p">}</span>
<span class="p">]</span>
</code></pre></div></div>

<blockquote>
  <p>Note: I need all the folders and subfolders, for example rlang/libs and rlang/libs/rlang.so, because I haven’t found a way to do a recursive mkdir in webR, and had to create all children level by level.</p>
</blockquote>

<p>🚀 Yeay 🚀</p>

<p>Now I have a JavaScript object I can use to move my <code class="language-plaintext highlighter-rouge">./webr_packages</code> library to a <code class="language-plaintext highlighter-rouge">webR</code> instance library.</p>

<p>Let’s write a function for that (no need for chatGPT here 😅):</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="kd">function</span> <span class="nx">loadPackages</span><span class="p">(</span><span class="nx">webR</span><span class="p">,</span> <span class="nx">dirPath</span><span class="p">){</span>
  <span class="c1">// Our function from before</span>
  <span class="kd">const</span> <span class="nx">files</span> <span class="o">=</span> <span class="nx">getDirectoryTree</span><span class="p">(</span>
    <span class="nx">dirPath</span>
  <span class="p">)</span>
  <span class="k">for</span> <span class="k">await</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">file</span> <span class="k">of</span> <span class="nx">files</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// If the entry is a directory, we'll</span>
    <span class="c1">// call mkdir</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">file</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">directory</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">await</span> <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">.</span><span class="nx">FS</span><span class="p">.</span><span class="nx">mkdir</span><span class="p">(</span>
        <span class="s2">`/usr/lib/R/library/</span><span class="p">${</span><span class="nx">file</span><span class="p">.</span><span class="nx">path</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span>
      <span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="c1">// case 2, the entry is a file, then we can use fs.readFileSync</span>
    <span class="c1">// That will return a ArrayBuffer that can be directly passed</span>
    <span class="c1">// to webR.FS.writeFile</span>
      <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">readFileSync</span><span class="p">(</span><span class="s2">`webr_packages/</span><span class="p">${</span><span class="nx">file</span><span class="p">.</span><span class="nx">path</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
      <span class="k">await</span> <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">.</span><span class="nx">FS</span><span class="p">.</span><span class="nx">writeFile</span><span class="p">(</span>
        <span class="s2">`/usr/lib/R/library/</span><span class="p">${</span><span class="nx">file</span><span class="p">.</span><span class="nx">path</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span>
        <span class="nx">data</span>
      <span class="p">);</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Now we have our whole process:</p>

<ul>
  <li>downloading the precompiled packages</li>
  <li>listing the dir tree in Node</li>
  <li>bringing everything into the <code class="language-plaintext highlighter-rouge">webR</code> filesystem</li>
</ul>

<p>🚀 Yeay (bis) 🚀</p>

<p>To make things simple, I have put these into a node package, at <a href="https://www.npmjs.com/package/webrtools" target="_blank">npmjs.com/package/webrtools</a>.</p>

<h3 id="bringing-everything-together">Bringing everything together</h3>

<p>Workflow for building the app:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd </span>webr-example
<span class="nb">mkdir </span>webr-preloadr
<span class="nb">cd </span>webr-preloadr
npm init <span class="nt">-y</span>
npm <span class="nb">install </span>express webr webrtools
Rscript ./node_modules/webrtools/r/install.R dplyr
<span class="nb">touch </span>index.js
</code></pre></div></div>

<p>Then :</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">express</span><span class="dl">'</span><span class="p">)()</span>
<span class="kd">const</span> <span class="nx">path</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">path</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">loadPackages</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">webrtools</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">WebR</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">webr</span><span class="dl">'</span><span class="p">);</span>

<span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">WebR</span><span class="p">();</span>
  <span class="k">await</span> <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">.</span><span class="nx">init</span><span class="p">();</span>

  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">🚀 webR is ready 🚀</span><span class="dl">"</span><span class="p">);</span>

  <span class="k">await</span> <span class="nx">loadPackages</span><span class="p">(</span>
    <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">,</span>
    <span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="dl">'</span><span class="s1">webr_packages</span><span class="dl">'</span><span class="p">)</span>
  <span class="p">)</span>

  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">📦 Packages written to webR 📦</span><span class="dl">"</span><span class="p">);</span>

  <span class="kd">const</span> <span class="nx">res</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">.</span><span class="nx">FS</span><span class="p">.</span><span class="nx">lookupPath</span><span class="p">(</span><span class="dl">"</span><span class="s2">/usr/lib/R/library</span><span class="dl">"</span><span class="p">);</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">res</span><span class="p">)</span>

  <span class="k">await</span> <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">.</span><span class="nx">evalR</span><span class="p">(</span><span class="dl">"</span><span class="s2">library(dplyr)</span><span class="dl">"</span><span class="p">);</span>
  <span class="nx">app</span><span class="p">.</span><span class="nx">listen</span><span class="p">(</span><span class="mi">3000</span><span class="p">,</span> <span class="dl">'</span><span class="s1">0.0.0.0</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">http://localhost:3000</span><span class="dl">'</span><span class="p">)</span>
  <span class="p">})</span>
<span class="p">})();</span>


<span class="nx">app</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">let</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">.</span><span class="nx">evalR</span><span class="p">(</span><span class="dl">'</span><span class="s1">sample_n(mtcars, 10)</span><span class="dl">'</span><span class="p">);</span>
  <span class="kd">let</span> <span class="nx">output</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">result</span><span class="p">.</span><span class="nx">toJs</span><span class="p">();</span>
  <span class="nx">res</span><span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="nx">output</span><span class="p">.</span><span class="nx">values</span><span class="p">)</span>
<span class="p">});</span>
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>node index.js
</code></pre></div></div>

<p>And in another console:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>curl http://localhost:3000
<span class="o">[{</span><span class="s2">"type"</span>:<span class="s2">"double"</span>,<span class="s2">"names"</span>:null,<span class="s2">"values"</span>:[21,24.4,14.3,32.4,18.7,17.8,22.8,10.4,30.4,21.4]<span class="o">}</span>,<span class="o">{</span><span class="s2">"type"</span>:<span class="s2">"double"</span>,<span class="s2">"names"</span>:null,<span class="s2">"values"</span>:[6,4,8,4,8,6,4,8,4,4]<span class="o">}</span>,<span class="o">{</span><span class="s2">"type"</span>:<span class="s2">"double"</span>,<span class="s2">"names"</span>:null,<span class="s2">"values"</span>:[160,146.7,360,78.7,360,167.6,108,460,75.7,121]<span class="o">}</span>,<span class="o">{</span><span class="s2">"type"</span>:<span class="s2">"double"</span>,<span class="s2">"names"</span>:null,<span class="s2">"values"</span>:[110,62,245,66,175,123,93,215,52,109]<span class="o">}</span>,<span class="o">{</span><span class="s2">"type"</span>:<span class="s2">"double"</span>,<span class="s2">"names"</span>:null,<span class="s2">"values"</span>:[3.9,3.69,3.21,4.08,3.15,3.92,3.85,3,4.93,4.11]<span class="o">}</span>,<span class="o">{</span><span class="s2">"type"</span>:<span class="s2">"double"</span>,<span class="s2">"names"</span>:null,<span class="s2">"values"</span>:[2.62,3.19,3.57,2.2,3.44,3.44,2.32,5.424,1.615,2.78]<span class="o">}</span>,<span class="o">{</span><span class="s2">"type"</span>:<span class="s2">"double"</span>,<span class="s2">"names"</span>:null,<span class="s2">"values"</span>:[16.46,20,15.84,19.47,17.02,18.9,18.61,17.82,18.52,18.6]<span class="o">}</span>,<span class="o">{</span><span class="s2">"type"</span>:<span class="s2">"double"</span>,<span class="s2">"names"</span>:null,<span class="s2">"values"</span>:[0,1,0,1,0,1,1,0,1,1]<span class="o">}</span>,<span class="o">{</span><span class="s2">"type"</span>:<span class="s2">"double"</span>,<span class="s2">"names"</span>:null,<span class="s2">"values"</span>:[1,0,0,1,0,0,1,0,1,1]<span class="o">}</span>,<span class="o">{</span><span class="s2">"type"</span>:<span class="s2">"double"</span>,<span class="s2">"names"</span>:null,<span class="s2">"values"</span>:[4,4,3,4,3,4,4,3,4,4]<span class="o">}</span>,<span class="o">{</span><span class="s2">"type"</span>:<span class="s2">"double"</span>,<span class="s2">"names"</span>:null,<span class="s2">"values"</span>:[4,2,4,1,2,4,1,4,2,2]<span class="o">}]</span>
</code></pre></div></div>

<p>🚀 Yeay (ter) 🚀</p>

<h3 id="and-now-for-the-dockerfile">And now for the Dockerfile</h3>

<div class="language-Dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="s"> r-base:4.3.1</span>

<span class="c"># Create app directory</span>
<span class="k">WORKDIR</span><span class="s"> /usr/src/app</span>

<span class="k">RUN </span>apt update <span class="o">&amp;&amp;</span> <span class="se">\
</span>  apt <span class="nb">install </span>nodejs npm <span class="nt">-y</span>

<span class="k">COPY</span><span class="s"> package*.json ./</span>

<span class="k">RUN </span>npm <span class="nb">install</span>

<span class="k">RUN </span>Rscript ./node_modules/webrtools/r/install.R dplyr

<span class="k">COPY</span><span class="s"> . .</span>

<span class="k">EXPOSE</span><span class="s"> 3000</span>

<span class="k">CMD</span><span class="s"> [ "node", "index.js" ]</span>
</code></pre></div></div>

<p>You can find the code <a href="https://github.com/ColinFay/webr-examples/tree/main/webr-preloadr">here</a>, and see it live at <a href="https://srv.colinfay.me/webr-preloadr/">srv.colinfay.me/webr-preloadr</a>.</p>

<p>You can also try it with</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run <span class="nt">-it</span> <span class="nt">-p</span> 3000:3000 colinfay/webr-preloadr
</code></pre></div></div>

<h2 id="final-notes">Final notes</h2>

<h3 id="should-i-use-it-in-prod">Should I use it in prod?</h3>

<p>Probably not — for now it’s more of an experiment than a real bullet-proof approach.
But if you’re a reader from the future, many things may have happen, and maybe the <code class="language-plaintext highlighter-rouge">webrtools</code> node module is now prod ready.
Who knows!</p>

<h3 id="further-work">Further work</h3>

<p>After some discussion on the issue on the <code class="language-plaintext highlighter-rouge">webR</code> repo, it seems that a promising path to do what I’d like to do is to use the <a href="https://emscripten.org/docs/api_reference/Filesystem-API.html#FS.mount" target="_blank"><code class="language-plaintext highlighter-rouge">FS.mount</code> interface from WASM</a>, but it needs to be expose by <code class="language-plaintext highlighter-rouge">webR</code>.
Keep an eye on the repo!</p>

<p>Finally, I’m a bit displeased with writing the installR fun in R and having to install R in the <code class="language-plaintext highlighter-rouge">Dockerfile</code> to do it, so I should probably re-write it in full NodeJS.
We could read the package tree in R with <code class="language-plaintext highlighter-rouge">webR</code> and then download/untar with Node.</p>

<p>Food for thoughts.</p>]]></content><author><name>Colin FAY</name><email>contact@colinfay.me</email></author><category term="r-blog-en" /><category term="webr" /><summary type="html"><![CDATA[Find all my posts about webR here. Note: the first post of this series explaining roughly what webR is, I won’t introduce it again here. Note 2 (or Disclaimer): this is kind of a long post that get into the weeds of package installation and library structure Note 3: webR and the tools I’ve been writing are moving fast and if you’re reading this from the future, some of the things here might be obsolete. The problem In the two last posts, I’ve been deploying two REST APIs that use functions from the base distribution of R: tools::toTitleCase() in express-webr-hello-world cut() &amp; faithful for express-webr-old-faithful One of the strengths of the R ecosystem is its extensive list of contributed packages: a wide collection of code written by R nerds from all over the world. It would be unfortunate to use webR to create a web applications or API without taking advantage of this extensive library of packages: R wouldn’t be R without its packages. Yet, bringing packages to webR is not that simple: it’s a distribution of R compiled for WASM, and you can’t just install a package from any CRAN repo. Thankfully we don’t need to scratch our head too much anymore: the webR team has precompiled more than 18000 (at the time of writing this post) packages that we can install by simply calling webr::install() inside the webR instance. That’s awesome, but there’s an issue with that: you can only install it from the webR JavaScript object As far as I know, there is no way to download them beforehand and upload them to the webR filesystem, as I reported in this issue. (Note: at the time of writing these lines, there has been some discussion on the GH issue, but there is at the moment no native, efficient way to do what I want to do 😅 — there might be one though if you’re reading this post in the future). That means that every time the API is launched, webR has to download and reinstall the packages, as shown by this simple example: const app = require('express')() const { WebR } = require('webr'); (async () =&gt; { globalThis.webR = new WebR(); await globalThis.webR.init(); await globalThis.webR.installPackages(['attempt']) app.listen(3000, '0.0.0.0', () =&gt; { console.log('http://localhost:3000') }) })(); app.get('/', async (req, res) =&gt; { res.send("hello") }); If I launch the app twice, the packages are downloaded twice 👇 $ node index.js webR is ready Downloading webR package: rlang Downloading webR package: attempt http://localhost:3000 $ node index.js webR is ready Downloading webR package: rlang Downloading webR package: attempt http://localhost:3000 This approach is definitely suboptimal: imagine having to reinstall packages every time you source() an R script, instead of benefiting from pre-installed packages in your lib. That’s kind of what is happening right now in this webR example. Sure, it works, but it means that: I’m downloading the (same) packages every time the app is launched, which feels weird If I have a lot of packages, it would take a lot of time every time I can’t deploy this on a service that has no access to internet I can’t build my app just as any other language does: by relying on pre-downloaded /pre-installed modules I can’t benefit from caching the package installation in a Docker layer So, faced with something I needed but didn’t find, I behave like any other software engineer: I built my own solution to pre-load R packages in webR, without having to re-download them every time. The rest of this blogpost explains this process. Preloading R packages in my ExpressJS/webR API Building the R lib If you look at the internal of the folder on your computer that contains your packages (which is at .libPaths()), you’ll see that it’s a series of folders with a bunch of files. Whenever you load R and call library(), it looks there to load the code. Inside these folders (you can have several lib), you’ll find unzipped archives, downloaded from a repository (or installed from git/developed locally). These packages, at installation time, possibly performed some build and compilation, depending on the package and the OS you’re on. I won’t go into the details of this here, as the packages for webR have been pre-compiled, meaning that you simply have to download the tar file, and unarchive it at the correct place. In other words, the content of this archive is exactly what you’ll find in the library folder, and there is nothing to compile/install again. Keeping this in mind, the first thing to do then is to create a folder that will contain all the folders and files necessary to load a package. In other words, if I want to use {dplyr}, I need to find a way to download the pre-compiled version of {dplyr} and all its dependencies from https://repo.r-wasm.org/, and untar them into the folder I wanna use inside my project. R has a weird way of managing packages, and if ever you want to hear me monologue, catch me at a conference and ask me to talk about this for 30 minutes (just kidding, it will probably last one hour). I won’t go into details about how it works and why it’s weird, but here are some issues I’ve been facing for my current task: By default R will not reinstall the dependencies if they are already installed on the machine. Which is great, but not in my case (I want the destination folder to contain the entire dependency tree). In other words, if I do install.packages("dplyr", lib="webr_packages"), R will only download the packages that are not already on my computer. There is no (sane) way to avoid the default library. So basically unless you’re ready to write hacky code that will override the library environments and stuff, R will always look into the default lib. There is an old issue on the {withr} repo if you want to look into this, but basically the idea is that avoiding default lib is quite a challenge. That means that if I try to do something as simple as install.packages("dplyr", lib="webr_packages", repos = "https://repo.r-wasm.org/"), it will only install {dplyr}, given that all the other dependencies already exist on my machine : &gt; setwd(tempdir()) &gt; dir.create("webr_packages") &gt; install.packages("dplyr", lib="webr_packages", repos = "https://repo.r-wasm.org/") # [...TRUNCATED] * DONE (dplyr) &gt; list.files("webr_packages/") [1] "dplyr" I’ve tried the following: Hacking .lib.loc as suggest by Andrie here, but it comes with a series of challenges like not having access to other packages and having to reinstall them and load them to the webR FileSystem which is not what I wanted. Using {renv} but it feels a bit overkill for what I’m trying to do: download a bunch of zip files from the internet and unzip them in the correct folder. Also, {renv} failed to parse the dep tree in a NodeJS project for some reasons. So I’ve ended up with my own R script, inspired by the internal of install.packages and webr::install (Read the source, Luke). I’ve first explored a version with {pak} and {pkgdepends}, but ended up using a base solution so that I don’t have to install anything in the Docker container. #!/usr/bin/env Rscript # This script only works if you pass it at least one arg if (is.na(commandArgs(trailingOnly = TRUE)[1])) { stop("This script requires at least one argument") } else { pk_to_install &lt;- commandArgs(trailingOnly = TRUE)[1] } # If no second value, defaulting to webr_packages if (is.na(commandArgs(trailingOnly = TRUE)[2])) { path_to_installation &lt;- "./webr_packages" } else { path_to_installation &lt;- commandArgs(trailingOnly = TRUE)[2] } # Build the repos url repos &lt;- sprintf( "https://repo.r-wasm.org/bin/emscripten/contrib/%s.%s", # Get major R version R.version$major, substr(R.version$minor, 1, 1) ) deps &lt;- unique( unlist( use.names = FALSE, tools::package_dependencies( recursive = TRUE, pk_to_install ) ) ) # Now the package list pkg_deps &lt;- c( pk_to_install, deps ) # I don't want to re-download things so I'm listing all the already # downloaded folders already_installed &lt;- list.files( path_to_installation ) # Getting the list of available package info &lt;- utils::available.packages(contriburl = repos) # Now we can install for (pkg in pkg_deps){ # Don't redownload things if (pkg %in% already_installed) { message("Package ", pkg, " already installed") next } # Not available: either it's not on the repo or it's included # in the base distribution of R if (!(pkg %in% info[, "Package"])) { message("Package {", pkg, "} not found in repo (unavailable or is base package)") next } message("Installing ", pkg) # Name of the targz, should be on the form of pkg_version.this.that.tgz targz_file &lt;- paste0(pkg, "_", info[info[, "Package"] == pkg, "Version"], ".tgz") # Location of file on the repo url &lt;- paste0(repos, "/", targz_file) # Download the file and untar the archive on the local lib tmp &lt;- tempfile(fileext = ".tgz") download.file(url, destfile = tmp) untar(tmp, exdir = path_to_installation) unlink(tmp) } message("Done") And now, let’s try this: mkdir webr_packages script tools/installR.r attempt ✔ Loading metadata database ... done Installing attempt trying URL 'https://repo.r-wasm.org/bin/emscripten/contrib/4.3/attempt_0.3.1.tgz' Content type 'application/x-tar' length 93561 bytes (91 KB) ================================================== downloaded 91 KB Installing rlang trying URL 'https://repo.r-wasm.org/bin/emscripten/contrib/4.3/rlang_1.1.1.tgz' Content type 'application/x-tar' length 1062482 bytes (1.0 MB) ================================================== downloaded 1.0 MB Done Rscript -e "fs::dir_tree('webr_packages')" webr_packages ├── attempt │ ├── DESCRIPTION │ ├── INDEX │ ├── LICENSE │ ├── Meta │ │ ├── Rd.rds │ │ ├── features.rds │ │ ├── hsearch.rds │ │ ├── links.rds │ │ ├── nsInfo.rds │ │ ├── package.rds │ │ └── vignette.rds │ ├── NAMESPACE │ ├── NEWS.md │ ├── R │ │ ├── attempt │ │ ├── attempt.rdb │ │ └── attempt.rdx │ ├── doc │ │ ├── a_intro_attempt.R │ │ ├── a_intro_attempt.Rmd │ │ ├── a_intro_attempt.html │ │ ├── b_try_catch.R │ │ ├── b_try_catch.Rmd │ │ ├── b_try_catch.html │ │ ├── c_adverbs.R │ │ ├── c_adverbs.Rmd │ │ ├── c_adverbs.html │ │ ├── d_if.R │ │ ├── d_if.Rmd │ │ ├── d_if.html │ │ ├── e_conditions.R │ │ ├── e_conditions.Rmd │ │ ├── e_conditions.html │ │ └── index.html │ ├── help │ │ ├── AnIndex │ │ ├── aliases.rds │ │ ├── attempt.rdb │ │ ├── attempt.rdx │ │ └── paths.rds │ └── html │ ├── 00Index.html │ └── R.css └── rlang ├── DESCRIPTION ├── INDEX ├── LICENSE ├── Meta │ ├── Rd.rds │ ├── features.rds │ ├── hsearch.rds │ ├── links.rds │ ├── nsInfo.rds │ └── package.rds ├── NAMESPACE ├── NEWS.md ├── R │ ├── rlang │ ├── rlang.rdb │ └── rlang.rdx ├── backtrace-ver ├── help │ ├── AnIndex │ ├── aliases.rds │ ├── figures │ │ ├── lifecycle-archived.svg │ │ ├── lifecycle-defunct.svg │ │ ├── lifecycle-deprecated.svg │ │ ├── lifecycle-experimental.svg │ │ ├── lifecycle-maturing.svg │ │ ├── lifecycle-questioning.svg │ │ ├── lifecycle-retired.svg │ │ ├── lifecycle-soft-deprecated.svg │ │ ├── lifecycle-stable.svg │ │ ├── lifecycle-superseded.svg │ │ └── logo.png │ ├── paths.rds │ ├── rlang.rdb │ └── rlang.rdx ├── html │ ├── 00Index.html │ └── R.css └── libs └── rlang.so Woot woot, seems to work 🎉. Moving everything to the webR lib when the API is launched And now, challenge number two: rebuilding this library in the webR filesystem. webR has an interface to WASM filesystem (aka Emscripten Virtual File System), exposed via FS (doc is here). This interface allows to create a directory and write a file, which are the two things we want to do here: Create the package folders/subfolders Copy the files from the local machine (contained in a ./webr_packages folder) into the webR filesystem when launching the Node API. Creating the directory tree Creating a directory tree is not an easy task — it implies recursive function, keeping track of parents, and other weird things. R has list.files(recursive = TRUE) and we should be VERY happy about it: I looked on the internet and apparently there is no native way to do that in Node, you have to do it by hand. As any sane modern JavaScript developer would do, I asked chatGPT to do it for me. It took me several iterations before getting the exact answer I was looking for but here it is. Note that the JS functions below are bundled in the webrtools npm module. const fs = require('fs'); const path = require('path'); // comments are mine // Recursive function to get files, thank you chatGPT function getDirectoryTree(dirPath, currentPath = '') { // Get a list of all the files/folders in the currently inspected folder const items = fs.readdirSync(path.join(dirPath, currentPath)); // output const tree = []; // Looping on all the files/folders for (const item of items) { // Inspecting the item const itemPath = path.join(dirPath, currentPath, item); const isDirectory = fs.statSync(itemPath).isDirectory(); const relativePath = path.join(currentPath, item); if (isDirectory) { // If it's a directory, we'll go inside and repeat the // current mechanism const subtree = getDirectoryTree(dirPath, relativePath); // pushing the current item, and its subtree tree.push({ path: relativePath, type: 'directory' }); tree.push(...subtree); } else { tree.push({ path: relativePath, type: 'file' }); } } // Getting the tree return tree; } Here is what it looks like on our ./webr_packages folder from before: &gt; getDirectoryTree("webr_packages") [ { path: 'attempt', type: 'directory' }, { path: 'attempt/DESCRIPTION', type: 'file' }, { path: 'attempt/INDEX', type: 'file' }, { path: 'attempt/LICENSE', type: 'file' }, { path: 'attempt/Meta', type: 'directory' }, { path: 'attempt/Meta/Rd.rds', type: 'file' }, { path: 'attempt/Meta/features.rds', type: 'file' }, { path: 'attempt/Meta/hsearch.rds', type: 'file' }, { path: 'attempt/Meta/links.rds', type: 'file' }, { path: 'attempt/Meta/nsInfo.rds', type: 'file' }, { path: 'attempt/Meta/package.rds', type: 'file' }, { path: 'attempt/Meta/vignette.rds', type: 'file' }, { path: 'attempt/NAMESPACE', type: 'file' }, { path: 'attempt/NEWS.md', type: 'file' }, { path: 'attempt/R', type: 'directory' }, { path: 'attempt/R/attempt', type: 'file' }, { path: 'attempt/R/attempt.rdb', type: 'file' }, { path: 'attempt/R/attempt.rdx', type: 'file' }, { path: 'attempt/doc', type: 'directory' }, { path: 'attempt/doc/a_intro_attempt.R', type: 'file' }, { path: 'attempt/doc/a_intro_attempt.Rmd', type: 'file' }, { path: 'attempt/doc/a_intro_attempt.html', type: 'file' }, { path: 'attempt/doc/b_try_catch.R', type: 'file' }, { path: 'attempt/doc/b_try_catch.Rmd', type: 'file' }, { path: 'attempt/doc/b_try_catch.html', type: 'file' }, { path: 'attempt/doc/c_adverbs.R', type: 'file' }, { path: 'attempt/doc/c_adverbs.Rmd', type: 'file' }, { path: 'attempt/doc/c_adverbs.html', type: 'file' }, { path: 'attempt/doc/d_if.R', type: 'file' }, { path: 'attempt/doc/d_if.Rmd', type: 'file' }, { path: 'attempt/doc/d_if.html', type: 'file' }, { path: 'attempt/doc/e_conditions.R', type: 'file' }, { path: 'attempt/doc/e_conditions.Rmd', type: 'file' }, { path: 'attempt/doc/e_conditions.html', type: 'file' }, { path: 'attempt/doc/index.html', type: 'file' }, { path: 'attempt/help', type: 'directory' }, { path: 'attempt/help/AnIndex', type: 'file' }, { path: 'attempt/help/aliases.rds', type: 'file' }, { path: 'attempt/help/attempt.rdb', type: 'file' }, { path: 'attempt/help/attempt.rdx', type: 'file' }, { path: 'attempt/help/paths.rds', type: 'file' }, { path: 'attempt/html', type: 'directory' }, { path: 'attempt/html/00Index.html', type: 'file' }, { path: 'attempt/html/R.css', type: 'file' }, { path: 'rlang', type: 'directory' }, { path: 'rlang/DESCRIPTION', type: 'file' }, { path: 'rlang/INDEX', type: 'file' }, { path: 'rlang/LICENSE', type: 'file' }, { path: 'rlang/Meta', type: 'directory' }, { path: 'rlang/Meta/Rd.rds', type: 'file' }, { path: 'rlang/Meta/features.rds', type: 'file' }, { path: 'rlang/Meta/hsearch.rds', type: 'file' }, { path: 'rlang/Meta/links.rds', type: 'file' }, { path: 'rlang/Meta/nsInfo.rds', type: 'file' }, { path: 'rlang/Meta/package.rds', type: 'file' }, { path: 'rlang/NAMESPACE', type: 'file' }, { path: 'rlang/NEWS.md', type: 'file' }, { path: 'rlang/R', type: 'directory' }, { path: 'rlang/R/rlang', type: 'file' }, { path: 'rlang/R/rlang.rdb', type: 'file' }, { path: 'rlang/R/rlang.rdx', type: 'file' }, { path: 'rlang/backtrace-ver', type: 'file' }, { path: 'rlang/help', type: 'directory' }, { path: 'rlang/help/AnIndex', type: 'file' }, { path: 'rlang/help/aliases.rds', type: 'file' }, { path: 'rlang/help/figures', type: 'directory' }, { path: 'rlang/help/figures/lifecycle-archived.svg', type: 'file' }, { path: 'rlang/help/figures/lifecycle-defunct.svg', type: 'file' }, { path: 'rlang/help/figures/lifecycle-deprecated.svg', type: 'file' }, { path: 'rlang/help/figures/lifecycle-experimental.svg',type: 'file'}, { path: 'rlang/help/figures/lifecycle-maturing.svg', type: 'file' }, { path: 'rlang/help/figures/lifecycle-questioning.svg',type: 'file'}, { path: 'rlang/help/figures/lifecycle-retired.svg', type: 'file' }, { path: 'rlang/help/figures/lifecycle-soft-deprecated.svg',type: 'file'}, { path: 'rlang/help/figures/lifecycle-stable.svg', type: 'file' }, { path: 'rlang/help/figures/lifecycle-superseded.svg', type: 'file' }, { path: 'rlang/help/figures/logo.png', type: 'file' }, { path: 'rlang/help/paths.rds', type: 'file' }, { path: 'rlang/help/rlang.rdb', type: 'file' }, { path: 'rlang/help/rlang.rdx', type: 'file' }, { path: 'rlang/html', type: 'directory' }, { path: 'rlang/html/00Index.html', type: 'file' }, { path: 'rlang/html/R.css', type: 'file' }, { path: 'rlang/libs', type: 'directory' }, { path: 'rlang/libs/rlang.so', type: 'file' } ] Note: I need all the folders and subfolders, for example rlang/libs and rlang/libs/rlang.so, because I haven’t found a way to do a recursive mkdir in webR, and had to create all children level by level. 🚀 Yeay 🚀 Now I have a JavaScript object I can use to move my ./webr_packages library to a webR instance library. Let’s write a function for that (no need for chatGPT here 😅): async function loadPackages(webR, dirPath){ // Our function from before const files = getDirectoryTree( dirPath ) for await (const file of files) { // If the entry is a directory, we'll // call mkdir if (file.type === 'directory') { await globalThis.webR.FS.mkdir( `/usr/lib/R/library/${file.path}`, ); } else { // case 2, the entry is a file, then we can use fs.readFileSync // That will return a ArrayBuffer that can be directly passed // to webR.FS.writeFile const data = fs.readFileSync(`webr_packages/${file.path}`); await globalThis.webR.FS.writeFile( `/usr/lib/R/library/${file.path}`, data ); } } } Now we have our whole process: downloading the precompiled packages listing the dir tree in Node bringing everything into the webR filesystem 🚀 Yeay (bis) 🚀 To make things simple, I have put these into a node package, at npmjs.com/package/webrtools. Bringing everything together Workflow for building the app: cd webr-example mkdir webr-preloadr cd webr-preloadr npm init -y npm install express webr webrtools Rscript ./node_modules/webrtools/r/install.R dplyr touch index.js Then : const app = require('express')() const path = require('path'); const { loadPackages } = require('webrtools'); const { WebR } = require('webr'); (async () =&gt; { globalThis.webR = new WebR(); await globalThis.webR.init(); console.log("🚀 webR is ready 🚀"); await loadPackages( globalThis.webR, path.join(__dirname, 'webr_packages') ) console.log("📦 Packages written to webR 📦"); const res = await globalThis.webR.FS.lookupPath("/usr/lib/R/library"); console.log(res) await globalThis.webR.evalR("library(dplyr)"); app.listen(3000, '0.0.0.0', () =&gt; { console.log('http://localhost:3000') }) })(); app.get('/', async (req, res) =&gt; { let result = await globalThis.webR.evalR('sample_n(mtcars, 10)'); let output = await result.toJs(); res.send(output.values) }); node index.js And in another console: $ curl http://localhost:3000 [{"type":"double","names":null,"values":[21,24.4,14.3,32.4,18.7,17.8,22.8,10.4,30.4,21.4]},{"type":"double","names":null,"values":[6,4,8,4,8,6,4,8,4,4]},{"type":"double","names":null,"values":[160,146.7,360,78.7,360,167.6,108,460,75.7,121]},{"type":"double","names":null,"values":[110,62,245,66,175,123,93,215,52,109]},{"type":"double","names":null,"values":[3.9,3.69,3.21,4.08,3.15,3.92,3.85,3,4.93,4.11]},{"type":"double","names":null,"values":[2.62,3.19,3.57,2.2,3.44,3.44,2.32,5.424,1.615,2.78]},{"type":"double","names":null,"values":[16.46,20,15.84,19.47,17.02,18.9,18.61,17.82,18.52,18.6]},{"type":"double","names":null,"values":[0,1,0,1,0,1,1,0,1,1]},{"type":"double","names":null,"values":[1,0,0,1,0,0,1,0,1,1]},{"type":"double","names":null,"values":[4,4,3,4,3,4,4,3,4,4]},{"type":"double","names":null,"values":[4,2,4,1,2,4,1,4,2,2]}] 🚀 Yeay (ter) 🚀 And now for the Dockerfile FROM r-base:4.3.1 # Create app directory WORKDIR /usr/src/app RUN apt update &amp;&amp; \ apt install nodejs npm -y COPY package*.json ./ RUN npm install RUN Rscript ./node_modules/webrtools/r/install.R dplyr COPY . . EXPOSE 3000 CMD [ "node", "index.js" ] You can find the code here, and see it live at srv.colinfay.me/webr-preloadr. You can also try it with docker run -it -p 3000:3000 colinfay/webr-preloadr Final notes Should I use it in prod? Probably not — for now it’s more of an experiment than a real bullet-proof approach. But if you’re a reader from the future, many things may have happen, and maybe the webrtools node module is now prod ready. Who knows! Further work After some discussion on the issue on the webR repo, it seems that a promising path to do what I’d like to do is to use the FS.mount interface from WASM, but it needs to be expose by webR. Keep an eye on the repo! Finally, I’m a bit displeased with writing the installR fun in R and having to install R in the Dockerfile to do it, so I should probably re-write it in full NodeJS. We could read the package tree in R with webR and then download/untar with Node. Food for thoughts.]]></summary></entry><entry><title type="html">The Old Faithful Geyser Data shiny app with webR, Bootstrap &amp;amp; ExpressJS</title><link href="https://colinfay.me/old-faithful-express-bootstrap-webr/" rel="alternate" type="text/html" title="The Old Faithful Geyser Data shiny app with webR, Bootstrap &amp;amp; ExpressJS" /><published>2023-07-25T00:00:00+00:00</published><updated>2023-07-25T00:00:00+00:00</updated><id>https://colinfay.me/old-faithful-express-bootstrap-webr</id><content type="html" xml:base="https://colinfay.me/old-faithful-express-bootstrap-webr/"><![CDATA[<p>Find all my posts about webR <a href="https://colinfay.me/categories/#webr">here</a>.</p>

<blockquote>
  <p>Note: the first post of this series explaining roughly what <code class="language-plaintext highlighter-rouge">webR</code> is, I won’t introduce it again here.</p>
</blockquote>

<blockquote>
  <p>Note 2: webR and the tools I’ve been writing are moving fast and if you’re reading this from the future, some of the things here might be obsolete.</p>
</blockquote>

<p>In this post, I’ll attempt to recreate a version of the famous <em>Old Faithful Geyser Data</em> <code class="language-plaintext highlighter-rouge">{shiny}</code> app using <code class="language-plaintext highlighter-rouge">webR</code>, <code class="language-plaintext highlighter-rouge">Bootstrap</code> &amp; <code class="language-plaintext highlighter-rouge">ExpressJS</code>.
(If you really don’t know which app I’m talking about, it’s <a href="https://gallery.shinyapps.io/001-hello/">this one</a>.)</p>

<h2 id="introductory-notes">Introductory notes</h2>

<p>Before starting, here are two notes regarding the app built in this post:</p>

<ul>
  <li>
    <p>this app could have been build in full “web mode” (no server required, just an HTML page), but this is not the approach I’m currently experimenting with.
Going full browser-based doesn’t work for all cases, and most of the time with production apps you’ll need some part of your code to be computed by the server (because of resources, because you’ll connect to API with token, because you need access to DB with passwords, because you don’t want the full data to be available in the brower, or many other good reason…).</p>
  </li>
  <li>
    <p>In order to recreate the app, my first approach was to try to draw the histogram in base R and send it back to the browser.
<a href="https://rud.is/b/2023/03/18/the-road-to-ggplot2-in-webr-part-1-the-road-is-paved-with-good-base-r-plots/">boB has a great blogpost</a> about how to do exactly that, but after a lot of bad code and good 4 letter words, I realized there was no sane reason for me to display a base R plot instead of a JavaScript based one.
That’s why I chose to return the data for the barplot (using the <code class="language-plaintext highlighter-rouge">cut()</code> function from R) and draw with <a href="https://www.chartjs.org/">chart.js</a>, instead of trying to display the “not so user friendly” base plot.</p>
  </li>
</ul>

<h2 id="project-init">Project init</h2>

<p>Let’s start by creating a new Express app:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir </span>express-webr-old-faithful
<span class="nb">cd </span>express-webr-old-faithful
npm init <span class="nt">-y</span>
<span class="c"># Installing the deps we'll need</span>
npm i @r-wasm/webr express
<span class="c"># Creating a server, and the front page</span>
<span class="nb">touch </span>index.js
<span class="nb">touch </span>index.html
</code></pre></div></div>

<h2 id="server">Server</h2>

<p>Let’s move to the server side first (<code class="language-plaintext highlighter-rouge">index.js</code> ).
We’ll start by taking the file from the previous blog post, and modify it to:</p>

<ul>
  <li>serve index.html on <code class="language-plaintext highlighter-rouge">/</code></li>
  <li>create a route that returns the data of the bins for our histogram</li>
</ul>

<p>Let’s start with our code to init <code class="language-plaintext highlighter-rouge">webR</code>.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="dl">'</span><span class="s1">use strict</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">express</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">express</span><span class="dl">"</span><span class="p">)</span>
<span class="c1">// For serving the html</span>
<span class="kd">const</span> <span class="nx">path</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">path</span><span class="dl">"</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">express</span><span class="p">()</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">WebR</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">@r-wasm/webr</span><span class="dl">'</span><span class="p">);</span>

<span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">WebR</span><span class="p">();</span>
  <span class="k">await</span> <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">.</span><span class="nx">init</span><span class="p">();</span>
  <span class="c1">// Given that we will reuse this value,</span>
  <span class="c1">// we assign it at launch</span>
  <span class="k">await</span> <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">.</span><span class="nx">evalR</span><span class="p">(</span><span class="dl">'</span><span class="s1">x &lt;- faithful[, 2]</span><span class="dl">'</span><span class="p">)</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">webR is ready</span><span class="dl">"</span><span class="p">);</span>
  <span class="nx">app</span><span class="p">.</span><span class="nx">listen</span><span class="p">(</span><span class="mi">3000</span><span class="p">,</span> <span class="dl">'</span><span class="s1">0.0.0.0</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">http://localhost:3000</span><span class="dl">'</span><span class="p">)</span>
  <span class="p">})</span>
<span class="p">})();</span>
</code></pre></div></div>

<p>Then, an endpoint that serves <code class="language-plaintext highlighter-rouge">index.html</code>:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">app</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">/</span><span class="dl">"</span><span class="p">,</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">res</span><span class="p">.</span><span class="nx">sendFile</span><span class="p">(</span><span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="dl">"</span><span class="s2">index.html</span><span class="dl">"</span><span class="p">))</span>
<span class="p">})</span>
</code></pre></div></div>

<p>And an endpoint that sends the bins for our plot, using ExpressJS parameter notation (<code class="language-plaintext highlighter-rouge">path/:n</code>).
The value is sent to webR via the <code class="language-plaintext highlighter-rouge">env</code> option of <code class="language-plaintext highlighter-rouge">evalR</code></p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">app</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">/hist-data/:n</span><span class="dl">"</span><span class="p">,</span> <span class="k">async</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">let</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">.</span><span class="nx">evalR</span><span class="p">(</span>
    <span class="dl">'</span><span class="s1">table(cut(x, seq(min(x), max(x), length.out = n + 1)))</span><span class="dl">'</span><span class="p">,</span>
    <span class="p">{</span> <span class="na">env</span><span class="p">:</span> <span class="p">{</span> <span class="na">n</span><span class="p">:</span> <span class="nb">parseInt</span><span class="p">(</span><span class="nx">req</span><span class="p">.</span><span class="nx">params</span><span class="p">.</span><span class="nx">n</span><span class="p">)</span> <span class="p">}</span> <span class="p">}</span>
  <span class="p">);</span>
  <span class="kd">let</span> <span class="nx">output</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">result</span><span class="p">.</span><span class="nx">toJs</span><span class="p">();</span>
  <span class="nx">res</span><span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="nx">output</span><span class="p">)</span>
<span class="p">})</span>
</code></pre></div></div>

<p>Now that our backend is ready, let’s check that we can now call it from the command line:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl http://localhost:3000/hist-data/10
<span class="o">{</span><span class="s2">"type"</span>:<span class="s2">"integer"</span>,<span class="s2">"names"</span>:[<span class="s2">"(43,48.3]"</span>,<span class="s2">"(48.3,53.6]"</span>,<span class="s2">"(53.6,58.9]"</span>,<span class="s2">"(58.9,64.2]"</span>,<span class="s2">"(64.2,69.5]"</span>,<span class="s2">"(69.5,74.8]"</span>,<span class="s2">"(74.8,80.1]"</span>,<span class="s2">"(80.1,85.4]"</span>,<span class="s2">"(85.4,90.7]"</span>,<span class="s2">"(90.7,96]"</span><span class="o">]</span>,<span class="s2">"values"</span>:[15,28,26,24,9,23,62,55,23,6]<span class="o">}</span>
</code></pre></div></div>

<h2 id="front">Front</h2>

<p>Now, time to build the front.</p>

<p>We’ll start with the Boostrap boilerplate from <a href="https://getbootstrap.com/docs/5.3/getting-started/introduction/#quick-start">https://getbootstrap.com/docs/5.3/getting-started/introduction/#quick-start</a>.</p>

<blockquote>
  <p>Note also that I’ve chosen (for simplicity’s sake) to use the CDN version of the external deps, instead of installing them in my Node project, which would be what I would do in a normal context.</p>
</blockquote>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;!doctype html&gt;</span>
<span class="nt">&lt;html</span> <span class="na">lang=</span><span class="s">"en"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;head&gt;</span>
    <span class="nt">&lt;meta</span> <span class="na">charset=</span><span class="s">"utf-8"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;meta</span> <span class="na">name=</span><span class="s">"viewport"</span> <span class="na">content=</span><span class="s">"width=device-width, initial-scale=1"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;title&gt;</span>Bootstrap demo<span class="nt">&lt;/title&gt;</span>
    <span class="nt">&lt;link</span> <span class="na">href=</span><span class="s">"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"</span> <span class="na">rel=</span><span class="s">"stylesheet"</span> <span class="na">integrity=</span><span class="s">"sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM"</span> <span class="na">crossorigin=</span><span class="s">"anonymous"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;/head&gt;</span>
  <span class="nt">&lt;body&gt;</span>
    <span class="nt">&lt;h1&gt;</span>Hello, world!<span class="nt">&lt;/h1&gt;</span>
    <span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"</span> <span class="na">integrity=</span><span class="s">"sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz"</span> <span class="na">crossorigin=</span><span class="s">"anonymous"</span><span class="nt">&gt;&lt;/script&gt;</span>
  <span class="nt">&lt;/body&gt;</span>
<span class="nt">&lt;/html&gt;</span>
</code></pre></div></div>

<p>Let’s change the title, and add:</p>

<ul>
  <li>the range input</li>
  <li>a div to receive the chart</li>
</ul>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"container"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;h1&gt;</span>Old Faithful Geyser Data<span class="nt">&lt;/h1&gt;</span>
  <span class="c">&lt;!-- Bootstrap grid system --&gt;</span>
  <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"row align-items-start"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"col-4"</span><span class="nt">&gt;</span>
      <span class="nt">&lt;label</span> <span class="na">for=</span><span class="s">"customRange1"</span> <span class="na">class=</span><span class="s">"form-label"</span><span class="nt">&gt;</span>Number of bins:<span class="nt">&lt;/label&gt;</span>
      <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"range"</span> <span class="na">class=</span><span class="s">"form-range"</span> <span class="na">id=</span><span class="s">"customRange1"</span> <span class="na">min=</span><span class="s">1</span> <span class="na">max=</span><span class="s">30</span> <span class="na">value=</span><span class="s">10</span><span class="nt">&gt;</span>
      <span class="nt">&lt;div</span> <span class="na">id=</span><span class="s">"bins"</span><span class="nt">&gt;</span>Selected: 10<span class="nt">&lt;/div&gt;</span>
    <span class="nt">&lt;/div&gt;</span>
    <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"col-8"</span><span class="nt">&gt;</span>
      <span class="nt">&lt;div&gt;</span>
        <span class="c">&lt;!-- Where we'll get the chart drawn --&gt;</span>
        <span class="nt">&lt;canvas</span> <span class="na">id=</span><span class="s">"myChart"</span><span class="nt">&gt;&lt;/canvas&gt;</span>
      <span class="nt">&lt;/div&gt;</span>
    <span class="nt">&lt;/div&gt;</span>
  <span class="nt">&lt;/div&gt;</span>
<span class="nt">&lt;/div&gt;</span>
</code></pre></div></div>

<p>In order to draw the graph, I’ll rely on a dead simple (yet powerful) JavaScript lib called <a href="https://www.chartjs.org/">Chart.js</a>.
It has a great <a href="https://www.chartjs.org/docs/latest/charts/bar.html">bar chart</a> graph that will work perfectly for our case.</p>

<p>Let’s start by adding the lib with <code class="language-plaintext highlighter-rouge">&lt;script src="https://cdn.jsdelivr.net/npm/chart.js"&gt;&lt;/script&gt;</code>.</p>

<p>We then need some JavaScript to:</p>

<ul>
  <li>Initiate the chart at launch</li>
</ul>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// https://colinfay.me/api-from-client-shiny/</span>
<span class="c1">// Default to 10 bins</span>
<span class="nx">fetch</span><span class="p">(</span><span class="dl">"</span><span class="s2">hist-data/10</span><span class="dl">"</span><span class="p">)</span>
      <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">data</span><span class="p">)</span> <span class="o">=&gt;</span><span class="p">{</span>
        <span class="c1">// Convert the data to json and</span>
        <span class="c1">// create a chart</span>
        <span class="nx">data</span><span class="p">.</span><span class="nx">json</span><span class="p">().</span><span class="nx">then</span><span class="p">((</span><span class="nx">res</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
          <span class="c1">// Keeping a global object with the chart</span>
          <span class="nx">globalThis</span><span class="p">.</span><span class="nx">chart</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Chart</span><span class="p">(</span>
          <span class="c1">// This is where the chart will go</span>
          <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">myChart</span><span class="dl">'</span><span class="p">),</span> <span class="p">{</span>
          <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">bar</span><span class="dl">'</span><span class="p">,</span>
          <span class="na">data</span><span class="p">:</span> <span class="p">{</span>
            <span class="c1">// webR returns the names</span>
            <span class="na">labels</span><span class="p">:</span> <span class="nx">res</span><span class="p">.</span><span class="nx">names</span><span class="p">,</span>
            <span class="na">datasets</span><span class="p">:</span> <span class="p">[{</span>
              <span class="na">label</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Histogram of waiting times</span><span class="dl">"</span><span class="p">,</span>
              <span class="na">data</span><span class="p">:</span> <span class="nx">res</span><span class="p">.</span><span class="nx">values</span>
            <span class="p">}]</span>
          <span class="p">}</span>
        <span class="p">});</span>
        <span class="p">})</span>
        <span class="p">.</span><span class="k">catch</span><span class="p">((</span><span class="nx">error</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
          <span class="nx">alert</span><span class="p">(</span><span class="dl">"</span><span class="s2">Error catchin result from R</span><span class="dl">"</span><span class="p">)</span>
        <span class="p">})</span>
      <span class="p">})</span>
      <span class="p">.</span><span class="k">catch</span><span class="p">((</span><span class="nx">error</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="nx">alert</span><span class="p">(</span><span class="dl">"</span><span class="s2">Error catchin result from R</span><span class="dl">"</span><span class="p">)</span>
      <span class="p">})</span>
</code></pre></div></div>

<ul>
  <li>Update the chart when the slider is moved</li>
</ul>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="kd">const</span> <span class="nx">update</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">n</span> <span class="o">=</span> <span class="mi">10</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">fetch</span><span class="p">(</span><span class="s2">`hist-data/</span><span class="p">${</span><span class="nx">n</span><span class="p">}</span><span class="s2">`</span><span class="p">).</span><span class="nx">then</span><span class="p">((</span><span class="nx">data</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="nx">data</span><span class="p">.</span><span class="nx">json</span><span class="p">().</span><span class="nx">then</span><span class="p">((</span><span class="nx">res</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
          <span class="nx">globalThis</span><span class="p">.</span><span class="nx">chart</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">labels</span> <span class="o">=</span> <span class="nx">res</span><span class="p">.</span><span class="nx">names</span><span class="p">;</span>
          <span class="nx">globalThis</span><span class="p">.</span><span class="nx">chart</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">datasets</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">dataset</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="nx">dataset</span><span class="p">.</span><span class="nx">data</span> <span class="o">=</span> <span class="nx">res</span><span class="p">.</span><span class="nx">values</span><span class="p">;</span>
          <span class="p">})</span>
          <span class="nx">globalThis</span><span class="p">.</span><span class="nx">chart</span><span class="p">.</span><span class="nx">update</span><span class="p">();</span>
        <span class="p">})</span>
      <span class="p">})</span>
      <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="dl">'</span><span class="s1">#bins</span><span class="dl">'</span><span class="p">).</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="s2">`Selected: </span><span class="p">${</span><span class="nx">n</span><span class="p">}</span><span class="s2">`</span><span class="p">;</span>

    <span class="p">}</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="dl">'</span><span class="s1">#customRange1</span><span class="dl">'</span><span class="p">).</span><span class="nx">addEventListener</span><span class="p">(</span>
  <span class="dl">'</span><span class="s1">change</span><span class="dl">'</span><span class="p">,</span>
  <span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="nx">update</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">value</span><span class="p">);</span> <span class="p">}</span>
 <span class="p">);</span>
</code></pre></div></div>

<p>And here it is!
You can see the app live at <a href="https://srv.colinfay.me/express-webr-old-faithful/">srv.colinfay.me/express-webr-old-faithful</a>.
You can find the code <a href="https://github.com/ColinFay/webr-examples/tree/main/express-webr-old-faithful">here</a>.</p>

<p>You can also try it with:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run <span class="nt">-it</span> <span class="nt">-p</span> 3000:3000 colinfay/express-webr-old-faithful
</code></pre></div></div>]]></content><author><name>Colin FAY</name><email>contact@colinfay.me</email></author><category term="r-blog-en" /><category term="webr" /><summary type="html"><![CDATA[Find all my posts about webR here. Note: the first post of this series explaining roughly what webR is, I won’t introduce it again here. Note 2: webR and the tools I’ve been writing are moving fast and if you’re reading this from the future, some of the things here might be obsolete. In this post, I’ll attempt to recreate a version of the famous Old Faithful Geyser Data {shiny} app using webR, Bootstrap &amp; ExpressJS. (If you really don’t know which app I’m talking about, it’s this one.) Introductory notes Before starting, here are two notes regarding the app built in this post: this app could have been build in full “web mode” (no server required, just an HTML page), but this is not the approach I’m currently experimenting with. Going full browser-based doesn’t work for all cases, and most of the time with production apps you’ll need some part of your code to be computed by the server (because of resources, because you’ll connect to API with token, because you need access to DB with passwords, because you don’t want the full data to be available in the brower, or many other good reason…). In order to recreate the app, my first approach was to try to draw the histogram in base R and send it back to the browser. boB has a great blogpost about how to do exactly that, but after a lot of bad code and good 4 letter words, I realized there was no sane reason for me to display a base R plot instead of a JavaScript based one. That’s why I chose to return the data for the barplot (using the cut() function from R) and draw with chart.js, instead of trying to display the “not so user friendly” base plot. Project init Let’s start by creating a new Express app: mkdir express-webr-old-faithful cd express-webr-old-faithful npm init -y # Installing the deps we'll need npm i @r-wasm/webr express # Creating a server, and the front page touch index.js touch index.html Server Let’s move to the server side first (index.js ). We’ll start by taking the file from the previous blog post, and modify it to: serve index.html on / create a route that returns the data of the bins for our histogram Let’s start with our code to init webR. 'use strict'; const express = require("express") // For serving the html const path = require("path") const app = express() const { WebR } = require('@r-wasm/webr'); (async () =&gt; { globalThis.webR = new WebR(); await globalThis.webR.init(); // Given that we will reuse this value, // we assign it at launch await globalThis.webR.evalR('x &lt;- faithful[, 2]') console.log("webR is ready"); app.listen(3000, '0.0.0.0', () =&gt; { console.log('http://localhost:3000') }) })(); Then, an endpoint that serves index.html: app.get("/", (req, res) =&gt; { res.sendFile(path.join(__dirname, "index.html")) }) And an endpoint that sends the bins for our plot, using ExpressJS parameter notation (path/:n). The value is sent to webR via the env option of evalR app.get("/hist-data/:n", async (req, res) =&gt; { let result = await globalThis.webR.evalR( 'table(cut(x, seq(min(x), max(x), length.out = n + 1)))', { env: { n: parseInt(req.params.n) } } ); let output = await result.toJs(); res.send(output) }) Now that our backend is ready, let’s check that we can now call it from the command line: curl http://localhost:3000/hist-data/10 {"type":"integer","names":["(43,48.3]","(48.3,53.6]","(53.6,58.9]","(58.9,64.2]","(64.2,69.5]","(69.5,74.8]","(74.8,80.1]","(80.1,85.4]","(85.4,90.7]","(90.7,96]"],"values":[15,28,26,24,9,23,62,55,23,6]} Front Now, time to build the front. We’ll start with the Boostrap boilerplate from https://getbootstrap.com/docs/5.3/getting-started/introduction/#quick-start. Note also that I’ve chosen (for simplicity’s sake) to use the CDN version of the external deps, instead of installing them in my Node project, which would be what I would do in a normal context. &lt;!doctype html&gt; &lt;html lang="en"&gt; &lt;head&gt; &lt;meta charset="utf-8"&gt; &lt;meta name="viewport" content="width=device-width, initial-scale=1"&gt; &lt;title&gt;Bootstrap demo&lt;/title&gt; &lt;link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous"&gt; &lt;/head&gt; &lt;body&gt; &lt;h1&gt;Hello, world!&lt;/h1&gt; &lt;script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"&gt;&lt;/script&gt; &lt;/body&gt; &lt;/html&gt; Let’s change the title, and add: the range input a div to receive the chart &lt;div class="container"&gt; &lt;h1&gt;Old Faithful Geyser Data&lt;/h1&gt; &lt;!-- Bootstrap grid system --&gt; &lt;div class="row align-items-start"&gt; &lt;div class="col-4"&gt; &lt;label for="customRange1" class="form-label"&gt;Number of bins:&lt;/label&gt; &lt;input type="range" class="form-range" id="customRange1" min=1 max=30 value=10&gt; &lt;div id="bins"&gt;Selected: 10&lt;/div&gt; &lt;/div&gt; &lt;div class="col-8"&gt; &lt;div&gt; &lt;!-- Where we'll get the chart drawn --&gt; &lt;canvas id="myChart"&gt;&lt;/canvas&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; In order to draw the graph, I’ll rely on a dead simple (yet powerful) JavaScript lib called Chart.js. It has a great bar chart graph that will work perfectly for our case. Let’s start by adding the lib with &lt;script src="https://cdn.jsdelivr.net/npm/chart.js"&gt;&lt;/script&gt;. We then need some JavaScript to: Initiate the chart at launch // https://colinfay.me/api-from-client-shiny/ // Default to 10 bins fetch("hist-data/10") .then((data) =&gt;{ // Convert the data to json and // create a chart data.json().then((res) =&gt; { // Keeping a global object with the chart globalThis.chart = new Chart( // This is where the chart will go document.getElementById('myChart'), { type: 'bar', data: { // webR returns the names labels: res.names, datasets: [{ label: "Histogram of waiting times", data: res.values }] } }); }) .catch((error) =&gt; { alert("Error catchin result from R") }) }) .catch((error) =&gt; { alert("Error catchin result from R") }) Update the chart when the slider is moved const update = function (n = 10) { fetch(`hist-data/${n}`).then((data) =&gt; { data.json().then((res) =&gt; { globalThis.chart.data.labels = res.names; globalThis.chart.data.datasets.forEach(dataset =&gt; { dataset.data = res.values; }) globalThis.chart.update(); }) }) document.querySelector('#bins').innerHTML = `Selected: ${n}`; } document.querySelector('#customRange1').addEventListener( 'change', function() { update(this.value); } ); And here it is! You can see the app live at srv.colinfay.me/express-webr-old-faithful. You can find the code here. You can also try it with: docker run -it -p 3000:3000 colinfay/express-webr-old-faithful]]></summary></entry><entry><title type="html">Using webR in an Express JS REST API</title><link href="https://colinfay.me/calling-webr-from-expressjs/" rel="alternate" type="text/html" title="Using webR in an Express JS REST API" /><published>2023-07-04T00:00:00+00:00</published><updated>2023-07-04T00:00:00+00:00</updated><id>https://colinfay.me/webr-in-expressjs</id><content type="html" xml:base="https://colinfay.me/calling-webr-from-expressjs/"><![CDATA[<p>Find all my posts about webR <a href="https://colinfay.me/categories/#webr">here</a>.</p>

<blockquote>
  <p>Note: webR and the tools I’ve been writing are moving fast and if you’re reading this from the future, some of the things here might be obsolete.</p>
</blockquote>

<h2 id="webr-wat-again">webR? wat again?</h2>

<p>As described in the <a href="https://docs.r-wasm.org/webr/latest/">doc</a>:</p>

<blockquote>
  <p>WebR is a version of the statistical language R compiled for the browser and Node.js using WebAssembly, via Emscripten.</p>
</blockquote>

<p>In this post, I won’t go into details into what <code class="language-plaintext highlighter-rouge">webR</code> is, as if you want to know more, you’ll probably get a better understanding by going through the documentation.</p>

<p>But if like a normal person you don’t read the doc, let’s sum up what <code class="language-plaintext highlighter-rouge">webR</code> is.
Simply put, <code class="language-plaintext highlighter-rouge">WebAssembly</code> (shorten wasm) is a format that can be used to encode a programming language to something that can then be called from a JavaScript runtime (in the browser or in NodeJS).
Schematically, you take one language, you translate it to wasm, and the output can be read and used from JavaScript.</p>

<p>If you still have no idea what wasm is but want to get a better grasp on what it is, I suggest reading <a href="https://www.jesuisundev.com/en/understand-webassembly-in-5-minutes/">this blogpost</a> that really sums it well, especially this quote:</p>

<blockquote>
  <p>WASM is a way to use non-Javascript code and run it in your browser.</p>
</blockquote>

<p>I also suggest reading the blogposts by <a href="https://rud.is/b/">boB</a> on the subject.</p>

<p>The rest of this post assume that you understand what wasm &amp; <code class="language-plaintext highlighter-rouge">webR</code> are.</p>

<h2 id="playing-with-webr">Playing with webR</h2>

<p>I’ve been interested with WebAssembly for a while now, and can remember talking about it late during the night at R conferences around 2018 and 2019… but that’s a story for another time 😅</p>

<p>Today’s story is about exploring how to build tools in JavaScript that will call <code class="language-plaintext highlighter-rouge">webR</code>.</p>

<p>I’ll try to document my experimentations, and this post is the first one of a series that I hope I’ll find time to continue over the summer.</p>

<h2 id="hello-world---creating-an-api-where-we-call-webr">Hello World! - Creating an API where we call webR</h2>

<p>I’ll start with a simple <a href="https://expressjs.com/">Express JS</a> REST API that uses webR to run R code.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Initiating a projet and opening it with VSCode</span>
<span class="nb">mkdir </span>webrexpresshelloworld
code webrexpresshelloworld

<span class="c"># Quick init of a node project</span>
npm init <span class="nt">-y</span>
<span class="nb">touch </span>index.js
npm <span class="nb">install </span>express
</code></pre></div></div>

<p>We’ll now install the webR nodejs package</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm i @r-wasm/webr
</code></pre></div></div>

<p>And now, let’s move to our index.js file.
We’ll start with the basics of Express JS: creating the <code class="language-plaintext highlighter-rouge">app</code> object.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Creating the express app</span>
<span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">express</span><span class="dl">'</span><span class="p">)()</span>
</code></pre></div></div>

<p>Then, we’ll import <code class="language-plaintext highlighter-rouge">WebR</code> from <code class="language-plaintext highlighter-rouge">@r-wasm/webr</code>.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// importing the `WebR` class inside the runtime</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">WebR</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">@r-wasm/webr</span><span class="dl">'</span><span class="p">);</span>
</code></pre></div></div>

<p>We’ll now be initiating the webR session by:</p>
<ul>
  <li>creating an instance of webR</li>
  <li>binding it to <code class="language-plaintext highlighter-rouge">globalThis</code> so that it’s available everywhere</li>
  <li>calling the <code class="language-plaintext highlighter-rouge">init</code> method</li>
  <li>Once <code class="language-plaintext highlighter-rouge">webR</code> is ready, we’ll launch the <code class="language-plaintext highlighter-rouge">app</code></li>
</ul>

<p>As webR communicates via the worker thread asynchronously, we need to use <code class="language-plaintext highlighter-rouge">async</code>/<code class="language-plaintext highlighter-rouge">await</code>.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">WebR</span><span class="p">();</span>
  <span class="k">await</span> <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">.</span><span class="nx">init</span><span class="p">();</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">webR is ready</span><span class="dl">"</span><span class="p">);</span>
  <span class="c1">// Starting the express app</span>
  <span class="c1">// only after webR is ready</span>
  <span class="nx">app</span><span class="p">.</span><span class="nx">listen</span><span class="p">(</span><span class="mi">3000</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">http://localhost:3000</span><span class="dl">'</span><span class="p">)</span>
  <span class="p">})</span>
<span class="p">})();</span>
</code></pre></div></div>

<p>Finally, we add a route that returns an hello world:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Creating a route for the express app</span>
<span class="c1">// that will return a titleCase hello world from r</span>
<span class="nx">app</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="c1">// Evaluating the R code inside the R worker</span>
  <span class="kd">let</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">globalThis</span><span class="p">.</span><span class="nx">webR</span><span class="p">.</span><span class="nx">evalR</span><span class="p">(</span><span class="dl">'</span><span class="s1">tools::toTitleCase("hello from r!")</span><span class="dl">'</span><span class="p">);</span>
  <span class="c1">// Converting the result to a JS object</span>
  <span class="kd">let</span> <span class="nx">output</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">result</span><span class="p">.</span><span class="nx">toJs</span><span class="p">();</span>
  <span class="c1">// Sending the result back to the client,</span>
  <span class="c1">// (webR outputs an object with types/names/values</span>
  <span class="c1">// and here we only want the values)</span>
  <span class="nx">res</span><span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="nx">output</span><span class="p">.</span><span class="nx">values</span><span class="p">)</span>
<span class="p">});</span>
</code></pre></div></div>

<p>And now, let’s run our app!</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>node index.js
</code></pre></div></div>

<p>You can find the code <a href="https://github.com/ColinFay/webr-examples/tree/main/express-webr-hello-world">here</a>, and see it live at <a href="https://srv.colinfay.me/express-webr-hello-world">srv.colinfay.me/express-webr-hello-world</a>.</p>

<p>You can also try it with</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run <span class="nt">-it</span> <span class="nt">-p</span> 3000:3000 colinfay/express-webr-hello-world
</code></pre></div></div>]]></content><author><name>Colin FAY</name><email>contact@colinfay.me</email></author><category term="r-blog-en" /><category term="webr" /><summary type="html"><![CDATA[Find all my posts about webR here. Note: webR and the tools I’ve been writing are moving fast and if you’re reading this from the future, some of the things here might be obsolete. webR? wat again? As described in the doc: WebR is a version of the statistical language R compiled for the browser and Node.js using WebAssembly, via Emscripten. In this post, I won’t go into details into what webR is, as if you want to know more, you’ll probably get a better understanding by going through the documentation. But if like a normal person you don’t read the doc, let’s sum up what webR is. Simply put, WebAssembly (shorten wasm) is a format that can be used to encode a programming language to something that can then be called from a JavaScript runtime (in the browser or in NodeJS). Schematically, you take one language, you translate it to wasm, and the output can be read and used from JavaScript. If you still have no idea what wasm is but want to get a better grasp on what it is, I suggest reading this blogpost that really sums it well, especially this quote: WASM is a way to use non-Javascript code and run it in your browser. I also suggest reading the blogposts by boB on the subject. The rest of this post assume that you understand what wasm &amp; webR are. Playing with webR I’ve been interested with WebAssembly for a while now, and can remember talking about it late during the night at R conferences around 2018 and 2019… but that’s a story for another time 😅 Today’s story is about exploring how to build tools in JavaScript that will call webR. I’ll try to document my experimentations, and this post is the first one of a series that I hope I’ll find time to continue over the summer. Hello World! - Creating an API where we call webR I’ll start with a simple Express JS REST API that uses webR to run R code. # Initiating a projet and opening it with VSCode mkdir webrexpresshelloworld code webrexpresshelloworld # Quick init of a node project npm init -y touch index.js npm install express We’ll now install the webR nodejs package npm i @r-wasm/webr And now, let’s move to our index.js file. We’ll start with the basics of Express JS: creating the app object. // Creating the express app const app = require('express')() Then, we’ll import WebR from @r-wasm/webr. // importing the `WebR` class inside the runtime const { WebR } = require('@r-wasm/webr'); We’ll now be initiating the webR session by: creating an instance of webR binding it to globalThis so that it’s available everywhere calling the init method Once webR is ready, we’ll launch the app As webR communicates via the worker thread asynchronously, we need to use async/await. (async () =&gt; { globalThis.webR = new WebR(); await globalThis.webR.init(); console.log("webR is ready"); // Starting the express app // only after webR is ready app.listen(3000, () =&gt; { console.log('http://localhost:3000') }) })(); Finally, we add a route that returns an hello world: // Creating a route for the express app // that will return a titleCase hello world from r app.get('/', async (req, res) =&gt; { // Evaluating the R code inside the R worker let result = await globalThis.webR.evalR('tools::toTitleCase("hello from r!")'); // Converting the result to a JS object let output = await result.toJs(); // Sending the result back to the client, // (webR outputs an object with types/names/values // and here we only want the values) res.send(output.values) }); And now, let’s run our app! node index.js You can find the code here, and see it live at srv.colinfay.me/express-webr-hello-world. You can also try it with docker run -it -p 3000:3000 colinfay/express-webr-hello-world]]></summary></entry><entry><title type="html">[Keynote] Production is like ultra running: brutal, ungrateful, but worth every step</title><link href="https://colinfay.me/keynote-shiny-conf-2023/" rel="alternate" type="text/html" title="[Keynote] Production is like ultra running: brutal, ungrateful, but worth every step" /><published>2023-05-05T00:00:00+00:00</published><updated>2023-05-05T00:00:00+00:00</updated><id>https://colinfay.me/keynote-shiny-conf</id><content type="html" xml:base="https://colinfay.me/keynote-shiny-conf-2023/"><![CDATA[<p>Hey folks, my keynote at the <a href="https://shinyconf.appsilon.com/">shinyconf 2023</a> called “Production is like ultra running: brutal, ungrateful, but worth every step” is now online!</p>

<p>In this talk, I reflect on how ultra running has helped me become a better software engineer, and how building software has helped me learning how to run long distances.</p>

<p>Giving this talk has been something quite hard for me (probably harder than running 50 miles 😅) — I usually give very technical talks, but for one time decided I should try telling a bit more about me and my story, about why I love running long distances and building software that are used in production.
I hope watching this one will inspire some of you, and feel free to share your thoughts about it with me!</p>

<iframe width="560" height="315" src="https://www.youtube.com/embed/wMbhxTJNrmw" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe>

<p>Thanks again to the Apsillon team for inviting me to talk, to Vincent and Diane from ThinkR for their support, and to <a href="https://www.maximetarcher.com/">Maxime</a> for coaching me and pushing me toward being more open about myself.</p>]]></content><author><name>Colin FAY</name><email>contact@colinfay.me</email></author><category term="r-blog-en" /><summary type="html"><![CDATA[Hey folks, my keynote at the shinyconf 2023 called “Production is like ultra running: brutal, ungrateful, but worth every step” is now online! In this talk, I reflect on how ultra running has helped me become a better software engineer, and how building software has helped me learning how to run long distances. Giving this talk has been something quite hard for me (probably harder than running 50 miles 😅) — I usually give very technical talks, but for one time decided I should try telling a bit more about me and my story, about why I love running long distances and building software that are used in production. I hope watching this one will inspire some of you, and feel free to share your thoughts about it with me! Thanks again to the Apsillon team for inviting me to talk, to Vincent and Diane from ThinkR for their support, and to Maxime for coaching me and pushing me toward being more open about myself.]]></summary></entry><entry><title type="html">Advent of Code 2021-02 with R &amp;amp; JavaScript</title><link href="https://colinfay.me/aoc-2021-02/" rel="alternate" type="text/html" title="Advent of Code 2021-02 with R &amp;amp; JavaScript" /><published>2021-12-02T00:00:00+00:00</published><updated>2021-12-02T00:00:00+00:00</updated><id>https://colinfay.me/aoc-02</id><content type="html" xml:base="https://colinfay.me/aoc-2021-02/"><![CDATA[<p>Solving Advent of Code 2021-02 with R and JavaScript.</p>

<p>Like yesterday, trying to write JS code that looks like R code.</p>

<h2 id="disclaimer">Disclaimer</h2>

<p><a href="#disclaimer">Disclaimer</a> Obviously, this post contains a big spoiler
about Advent of Code, as it gives solutions for solving 2021-02.</p>

<p>[Disclaimer bis] I’m no JavaScript expert so this might not be the
perfect solution. TBH, that’s also the case for the R solution.</p>

<h2 id="about-the-javascript-code">About the JavaScript code</h2>

<p>The JavaScript code has been written in the same RMarkdown as the R
code. It runs thanks to the <code class="language-plaintext highlighter-rouge">{bubble}</code> package:
<a href="https://github.com/ColinFay/bubble">https://github.com/ColinFay/bubble</a>.</p>

<div class="language-r highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">bubble</span><span class="o">::</span><span class="n">set_node_engine</span><span class="p">()</span><span class="w">
</span></code></pre></div></div>

<h2 id="instructions">Instructions</h2>

<h3 id="one">One</h3>

<ul>
  <li>We start at a position [0, 0] (horizontal, depth), then
    <ul>
      <li>forward X increases the horizontal position by X units</li>
      <li>down X increases the depth by X units</li>
      <li>up X decreases the depth by X units</li>
    </ul>
  </li>
</ul>

<p>Result is horizontal * depth.</p>

<h3 id="two">Two</h3>

<ul>
  <li>We start at a position [0, 0, 0] (horizontal, depth,), then
    <ul>
      <li>down X increases your aim by X units</li>
      <li>up X decreases your aim by X units</li>
      <li>forward X does two things :
        <ul>
          <li>It increases your horizontal position by X units</li>
          <li>It increases your depth by your aim multiplied by X</li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

<p>Result is horizontal * depth.</p>

<p>Find the complete instructions at:
<a href="https://adventofcode.com/2021/day/2">https://adventofcode.com/2021/day/2</a>.</p>

<h2 id="reading-inputs">Reading inputs</h2>

<div class="language-r highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Read</span><span class="w">
</span><span class="n">ipt</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="n">read.delim</span><span class="p">(</span><span class="s2">"2021-02-aoc.txt"</span><span class="p">,</span><span class="w"> </span><span class="n">header</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">FALSE</span><span class="p">)</span><span class="o">$</span><span class="n">V1</span><span class="w">
</span></code></pre></div></div>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">fs</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">fs</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">var</span> <span class="nx">ipt</span> <span class="o">=</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">readFileSync</span><span class="p">(</span><span class="dl">"</span><span class="s2">2021-02-aoc.txt</span><span class="dl">"</span><span class="p">,</span> <span class="dl">'</span><span class="s1">utf8</span><span class="dl">'</span><span class="p">).</span><span class="nx">split</span><span class="p">(</span><span class="dl">"</span><span class="se">\n</span><span class="dl">"</span><span class="p">).</span><span class="nx">filter</span><span class="p">(</span><span class="nx">x</span> <span class="o">=&gt;</span> <span class="nx">x</span><span class="p">.</span><span class="nx">length</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">);</span>
</code></pre></div></div>

<h2 id="part-one">Part one</h2>

<h3 id="r-solution">R solution</h3>

<div class="language-r highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Horizontal and depth</span><span class="w">
</span><span class="n">start_pos</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="nf">c</span><span class="p">(</span><span class="n">horizontal</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">0</span><span class="p">,</span><span class="w"> </span><span class="n">depth</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">0</span><span class="p">)</span><span class="w">
</span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="n">moves</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">ipt</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="n">move</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="n">strsplit</span><span class="p">(</span><span class="n">moves</span><span class="p">,</span><span class="w"> </span><span class="s2">" "</span><span class="p">)[[</span><span class="m">1</span><span class="p">]]</span><span class="w">

  </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">move</span><span class="p">[</span><span class="m">1</span><span class="p">]</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="s2">"up"</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">start_pos</span><span class="p">[</span><span class="m">2</span><span class="p">]</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="n">start_pos</span><span class="p">[</span><span class="m">2</span><span class="p">]</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="nf">as.numeric</span><span class="p">(</span><span class="n">move</span><span class="p">[</span><span class="m">2</span><span class="p">])</span><span class="w">
  </span><span class="p">}</span><span class="w">
  </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">move</span><span class="p">[</span><span class="m">1</span><span class="p">]</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="s2">"down"</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">start_pos</span><span class="p">[</span><span class="m">2</span><span class="p">]</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="n">start_pos</span><span class="p">[</span><span class="m">2</span><span class="p">]</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nf">as.numeric</span><span class="p">(</span><span class="n">move</span><span class="p">[</span><span class="m">2</span><span class="p">])</span><span class="w">
  </span><span class="p">}</span><span class="w">
  </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">move</span><span class="p">[</span><span class="m">1</span><span class="p">]</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="s2">"forward"</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">start_pos</span><span class="p">[</span><span class="m">1</span><span class="p">]</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="n">start_pos</span><span class="p">[</span><span class="m">1</span><span class="p">]</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nf">as.numeric</span><span class="p">(</span><span class="n">move</span><span class="p">[</span><span class="m">2</span><span class="p">])</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="n">start_pos</span><span class="p">[</span><span class="m">1</span><span class="p">]</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">start_pos</span><span class="p">[</span><span class="m">2</span><span class="p">]</span><span class="w">
</span></code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>## horizontal
##    1727835
</code></pre></div></div>

<h3 id="js-solution">JS solution</h3>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">start_pos</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">]</span>

<span class="c1">// Gotcha: R for(x in y) is JS for(x of y)</span>

<span class="k">for</span> <span class="p">(</span><span class="nx">moves</span> <span class="k">of</span> <span class="nx">ipt</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// In JS, object have methods, so the split function</span>
  <span class="c1">// is containes inside the string object</span>
  <span class="nx">move</span> <span class="o">=</span> <span class="nx">moves</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="dl">'</span><span class="s1"> </span><span class="dl">'</span><span class="p">)</span>

  <span class="c1">// Small gotcha here, JS i 0 based index</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">move</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="dl">"</span><span class="s2">up</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// R as.numeric is parseInt()</span>
    <span class="nx">start_pos</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="nx">start_pos</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">-</span> <span class="nb">parseInt</span><span class="p">(</span><span class="nx">move</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
  <span class="p">}</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">move</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="dl">"</span><span class="s2">down</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">start_pos</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="nx">start_pos</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="nb">parseInt</span><span class="p">(</span><span class="nx">move</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
  <span class="p">}</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">move</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="dl">"</span><span class="s2">forward</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">start_pos</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="nx">start_pos</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">+</span> <span class="nb">parseInt</span><span class="p">(</span><span class="nx">move</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">start_pos</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">*</span> <span class="nx">start_pos</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>## 1727835
</code></pre></div></div>

<h2 id="part-two">Part two</h2>

<h3 id="r-solution-1">R solution</h3>

<div class="language-r highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Horizontal, depth, and aim</span><span class="w">
</span><span class="n">start_pos</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="nf">c</span><span class="p">(</span><span class="n">horizontal</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">0</span><span class="p">,</span><span class="w"> </span><span class="n">depth</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">0</span><span class="p">,</span><span class="w"> </span><span class="n">aim</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">0</span><span class="p">)</span><span class="w">
</span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="n">moves</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">ipt</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="n">move</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="n">strsplit</span><span class="p">(</span><span class="n">moves</span><span class="p">,</span><span class="w"> </span><span class="s2">" "</span><span class="p">)[[</span><span class="m">1</span><span class="p">]]</span><span class="w">

  </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">move</span><span class="p">[</span><span class="m">1</span><span class="p">]</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="s2">"up"</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">start_pos</span><span class="p">[</span><span class="m">3</span><span class="p">]</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="n">start_pos</span><span class="p">[</span><span class="m">3</span><span class="p">]</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="nf">as.numeric</span><span class="p">(</span><span class="n">move</span><span class="p">[</span><span class="m">2</span><span class="p">])</span><span class="w">
  </span><span class="p">}</span><span class="w">
  </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">move</span><span class="p">[</span><span class="m">1</span><span class="p">]</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="s2">"down"</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">start_pos</span><span class="p">[</span><span class="m">3</span><span class="p">]</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="n">start_pos</span><span class="p">[</span><span class="m">3</span><span class="p">]</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nf">as.numeric</span><span class="p">(</span><span class="n">move</span><span class="p">[</span><span class="m">2</span><span class="p">])</span><span class="w">
  </span><span class="p">}</span><span class="w">
  </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">move</span><span class="p">[</span><span class="m">1</span><span class="p">]</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="s2">"forward"</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">start_pos</span><span class="p">[</span><span class="m">1</span><span class="p">]</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="n">start_pos</span><span class="p">[</span><span class="m">1</span><span class="p">]</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nf">as.numeric</span><span class="p">(</span><span class="n">move</span><span class="p">[</span><span class="m">2</span><span class="p">])</span><span class="w">
    </span><span class="n">start_pos</span><span class="p">[</span><span class="m">2</span><span class="p">]</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="n">start_pos</span><span class="p">[</span><span class="m">2</span><span class="p">]</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="p">(</span><span class="n">start_pos</span><span class="p">[</span><span class="m">3</span><span class="p">]</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nf">as.numeric</span><span class="p">(</span><span class="n">move</span><span class="p">[</span><span class="m">2</span><span class="p">]))</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="n">start_pos</span><span class="p">[</span><span class="m">1</span><span class="p">]</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">start_pos</span><span class="p">[</span><span class="m">2</span><span class="p">]</span><span class="w">
</span></code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>## horizontal
## 1544000595
</code></pre></div></div>

<h3 id="js-solution-1">JS solution</h3>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">start_pos</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">]</span>

<span class="k">for</span> <span class="p">(</span><span class="nx">moves</span> <span class="k">of</span> <span class="nx">ipt</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">move</span> <span class="o">=</span> <span class="nx">moves</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="dl">'</span><span class="s1"> </span><span class="dl">'</span><span class="p">)</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">move</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="dl">"</span><span class="s2">up</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">start_pos</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="nx">start_pos</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">-</span> <span class="nb">parseInt</span><span class="p">(</span><span class="nx">move</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
  <span class="p">}</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">move</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="dl">"</span><span class="s2">down</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">start_pos</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="nx">start_pos</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">+</span> <span class="nb">parseInt</span><span class="p">(</span><span class="nx">move</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
  <span class="p">}</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">move</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="dl">"</span><span class="s2">forward</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">start_pos</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="nx">start_pos</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">+</span> <span class="nb">parseInt</span><span class="p">(</span><span class="nx">move</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
    <span class="nx">start_pos</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="nx">start_pos</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="p">(</span><span class="nx">start_pos</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">*</span> <span class="nb">parseInt</span><span class="p">(</span><span class="nx">move</span><span class="p">[</span><span class="mi">1</span><span class="p">]))</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">start_pos</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">*</span> <span class="nx">start_pos</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>## 1544000595
</code></pre></div></div>]]></content><author><name>Colin FAY</name><email>contact@colinfay.me</email></author><category term="r-blog-en" /><category term="aoc" /><summary type="html"><![CDATA[Solving Advent of Code 2021-02 with R and JavaScript. Like yesterday, trying to write JS code that looks like R code. Disclaimer Disclaimer Obviously, this post contains a big spoiler about Advent of Code, as it gives solutions for solving 2021-02. [Disclaimer bis] I’m no JavaScript expert so this might not be the perfect solution. TBH, that’s also the case for the R solution. About the JavaScript code The JavaScript code has been written in the same RMarkdown as the R code. It runs thanks to the {bubble} package: https://github.com/ColinFay/bubble. bubble::set_node_engine() Instructions One We start at a position [0, 0] (horizontal, depth), then forward X increases the horizontal position by X units down X increases the depth by X units up X decreases the depth by X units Result is horizontal * depth. Two We start at a position [0, 0, 0] (horizontal, depth,), then down X increases your aim by X units up X decreases your aim by X units forward X does two things : It increases your horizontal position by X units It increases your depth by your aim multiplied by X Result is horizontal * depth. Find the complete instructions at: https://adventofcode.com/2021/day/2. Reading inputs # Read ipt &lt;- read.delim("2021-02-aoc.txt", header = FALSE)$V1 const fs = require('fs') var ipt = fs.readFileSync("2021-02-aoc.txt", 'utf8').split("\n").filter(x =&gt; x.length != 0); Part one R solution # Horizontal and depth start_pos &lt;- c(horizontal = 0, depth = 0) for (moves in ipt) { move &lt;- strsplit(moves, " ")[[1]] if (move[1] == "up") { start_pos[2] &lt;- start_pos[2] - as.numeric(move[2]) } if (move[1] == "down") { start_pos[2] &lt;- start_pos[2] + as.numeric(move[2]) } if (move[1] == "forward") { start_pos[1] &lt;- start_pos[1] + as.numeric(move[2]) } } start_pos[1] * start_pos[2] ## horizontal ## 1727835 JS solution var start_pos = [0, 0] // Gotcha: R for(x in y) is JS for(x of y) for (moves of ipt) { // In JS, object have methods, so the split function // is containes inside the string object move = moves.split(' ') // Small gotcha here, JS i 0 based index if (move[0] == "up") { // R as.numeric is parseInt() start_pos[1] = start_pos[1] - parseInt(move[1]) } if (move[0] == "down") { start_pos[1] = start_pos[1] + parseInt(move[1]) } if (move[0] == "forward") { start_pos[0] = start_pos[0] + parseInt(move[1]) } } start_pos[0] * start_pos[1] ## 1727835 Part two R solution # Horizontal, depth, and aim start_pos &lt;- c(horizontal = 0, depth = 0, aim = 0) for (moves in ipt) { move &lt;- strsplit(moves, " ")[[1]] if (move[1] == "up") { start_pos[3] &lt;- start_pos[3] - as.numeric(move[2]) } if (move[1] == "down") { start_pos[3] &lt;- start_pos[3] + as.numeric(move[2]) } if (move[1] == "forward") { start_pos[1] &lt;- start_pos[1] + as.numeric(move[2]) start_pos[2] &lt;- start_pos[2] + (start_pos[3] * as.numeric(move[2])) } } start_pos[1] * start_pos[2] ## horizontal ## 1544000595 JS solution var start_pos = [0, 0, 0] for (moves of ipt) { move = moves.split(' ') if (move[0] == "up") { start_pos[2] = start_pos[2] - parseInt(move[1]) } if (move[0] == "down") { start_pos[2] = start_pos[2] + parseInt(move[1]) } if (move[0] == "forward") { start_pos[0] = start_pos[0] + parseInt(move[1]) start_pos[1] = start_pos[1] + (start_pos[2] * parseInt(move[1])) } } start_pos[0] * start_pos[1] ## 1544000595]]></summary></entry><entry><title type="html">Advent of Code 2021-01 with R &amp;amp; JavaScript</title><link href="https://colinfay.me/aoc-2021-01/" rel="alternate" type="text/html" title="Advent of Code 2021-01 with R &amp;amp; JavaScript" /><published>2021-12-01T00:00:00+00:00</published><updated>2021-12-01T00:00:00+00:00</updated><id>https://colinfay.me/aoc-01</id><content type="html" xml:base="https://colinfay.me/aoc-2021-01/"><![CDATA[<p>Solving Advent of Code 2021-01 with R and JavaScript.</p>

<p>I’ll try in this post to write R code that looks like the JavaScript
one, using <code class="language-plaintext highlighter-rouge">{purrr}</code>, to show how both syntaxes can look alike. The idea
is to highlight how you can leverage your <code class="language-plaintext highlighter-rouge">{purrr}</code> knowledge when
writting JavaScript code.</p>

<h2 id="disclaimer">Disclaimer</h2>

<p><a href="#disclaimer">Disclaimer</a> Obviously, this post contains a big spoiler
about Advent of Code, as it gives solutions for solving day 1.</p>

<p>[Disclaimer bis] I’m no JavaScript expert so this might not be the
perfect solution. TBH, that’s also the case for the R solution.</p>

<h2 id="about-the-javascript-code">About the JavaScript code</h2>

<p>The JavaScript code has been written in the same RMarkdown as the R
code. It runs thanks to the <code class="language-plaintext highlighter-rouge">{bubble}</code> package:
<a href="https://github.com/ColinFay/bubble">https://github.com/ColinFay/bubble</a></p>

<h2 id="instructions">Instructions</h2>

<ul>
  <li>
    <p>Count the number of times n is larger that n-1</p>
  </li>
  <li>
    <p>Count the number of times n + n-1 + n-2 is larger that n-1 + n-2 +
n-3</p>
  </li>
</ul>

<p>Find the complete instructions at:
<a href="https://adventofcode.com/2021/day/1">https://adventofcode.com/2021/day/1</a>.</p>

<h2 id="reading-inputs">Reading inputs</h2>

<div class="language-r highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Read</span><span class="w">
</span><span class="n">library</span><span class="p">(</span><span class="n">purrr</span><span class="p">)</span><span class="w">
</span><span class="n">ipt</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="n">read.delim</span><span class="p">(</span><span class="s2">"2021-01-aoc.txt"</span><span class="p">,</span><span class="w"> </span><span class="n">header</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">FALSE</span><span class="p">)</span><span class="o">$</span><span class="n">V1</span><span class="w">
</span></code></pre></div></div>

<blockquote>
  <p>// Reading the file and converting it to int</p>
</blockquote>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">fs</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">fs</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">var</span> <span class="nx">ipt</span> <span class="o">=</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">readFileSync</span><span class="p">(</span><span class="dl">"</span><span class="s2">2021-01-aoc.txt</span><span class="dl">"</span><span class="p">,</span> <span class="dl">'</span><span class="s1">utf8</span><span class="dl">'</span><span class="p">).</span><span class="nx">split</span><span class="p">(</span><span class="dl">"</span><span class="se">\n</span><span class="dl">"</span><span class="p">).</span><span class="nx">filter</span><span class="p">(</span><span class="nx">x</span> <span class="o">=&gt;</span> <span class="nx">x</span><span class="p">.</span><span class="nx">length</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">);</span>
<span class="nx">ipt</span> <span class="o">=</span> <span class="nx">ipt</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">x</span> <span class="o">=&gt;</span> <span class="nb">parseInt</span><span class="p">(</span><span class="nx">x</span><span class="p">));</span>
</code></pre></div></div>

<h2 id="part-one">Part one</h2>

<h3 id="r-solution">R solution</h3>

<div class="language-r highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="m">2</span><span class="o">:</span><span class="nf">length</span><span class="p">(</span><span class="n">ipt</span><span class="p">)</span><span class="w"> </span><span class="o">|&gt;</span><span class="w">
  </span><span class="n">map_dbl</span><span class="p">(</span><span class="w">
    </span><span class="o">~</span><span class="w"> </span><span class="n">ipt</span><span class="p">[</span><span class="n">.x</span><span class="p">]</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">ipt</span><span class="p">[</span><span class="n">.x</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="m">1</span><span class="p">]</span><span class="w">
  </span><span class="p">)</span><span class="w"> </span><span class="o">|&gt;</span><span class="w">
  </span><span class="n">keep</span><span class="p">(</span><span class="w">
    </span><span class="o">~</span><span class="w"> </span><span class="n">.x</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="m">0</span><span class="w">
  </span><span class="p">)</span><span class="w"> </span><span class="o">|&gt;</span><span class="w">
  </span><span class="nf">length</span><span class="p">()</span><span class="w">
</span></code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>## [1] 1154
</code></pre></div></div>

<h3 id="js-solution">JS solution</h3>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">ipt</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span>
  <span class="c1">// inside map() in JS, the second argument to the anonymous</span>
  <span class="c1">// function is the index inside the array.</span>
  <span class="c1">// It mimics what we have in map(2:length(ipt)) in R</span>
  <span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">ipt</span><span class="p">[</span><span class="nx">y</span><span class="p">]</span> <span class="o">-</span> <span class="nx">ipt</span><span class="p">[</span><span class="nx">y</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>
  <span class="p">).</span><span class="nx">filter</span><span class="p">(</span>
    <span class="c1">// filter() will keep the element that matches the anonymous</span>
    <span class="c1">// predicate function</span>
    <span class="nx">x</span> <span class="o">=&gt;</span> <span class="nx">x</span> <span class="o">&gt;=</span> <span class="mi">0</span>
    <span class="p">).</span><span class="nx">length</span><span class="p">;</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>## 1154
</code></pre></div></div>

<h2 id="part-two">Part two</h2>

<h3 id="r-solution-1">R solution</h3>

<div class="language-r highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">tmp</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="m">3</span><span class="o">:</span><span class="nf">length</span><span class="p">(</span><span class="n">ipt</span><span class="p">)</span><span class="w"> </span><span class="o">|&gt;</span><span class="w">
  </span><span class="n">map_dbl</span><span class="p">(</span><span class="w">
    </span><span class="o">~</span><span class="w"> </span><span class="n">ipt</span><span class="p">[</span><span class="n">.x</span><span class="p">]</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">ipt</span><span class="p">[</span><span class="n">.x</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="m">1</span><span class="p">]</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">ipt</span><span class="p">[</span><span class="n">.x</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="m">2</span><span class="p">]</span><span class="w">
  </span><span class="p">)</span><span class="w">

</span><span class="m">2</span><span class="o">:</span><span class="nf">length</span><span class="p">(</span><span class="n">tmp</span><span class="p">)</span><span class="w"> </span><span class="o">|&gt;</span><span class="w">
  </span><span class="n">map_dbl</span><span class="p">(</span><span class="w">
    </span><span class="o">~</span><span class="w"> </span><span class="n">tmp</span><span class="p">[</span><span class="n">.x</span><span class="p">]</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">tmp</span><span class="p">[</span><span class="n">.x</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="m">1</span><span class="p">]</span><span class="w">
  </span><span class="p">)</span><span class="w"> </span><span class="o">|&gt;</span><span class="w">
  </span><span class="n">keep</span><span class="p">(</span><span class="w">
    </span><span class="o">~</span><span class="w"> </span><span class="n">.x</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="m">0</span><span class="w">
  </span><span class="p">)</span><span class="w"> </span><span class="o">|&gt;</span><span class="w">
  </span><span class="nf">length</span><span class="p">()</span><span class="w">
</span></code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>## [1] 1127
</code></pre></div></div>

<h3 id="js-solution-1">JS solution</h3>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">tmp</span> <span class="o">=</span> <span class="nx">ipt</span><span class="p">.</span>
  <span class="nx">map</span><span class="p">(</span>
    <span class="c1">// Creating n + n-1 + n-2</span>
    <span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">ipt</span><span class="p">[</span><span class="nx">y</span><span class="p">]</span> <span class="o">+</span> <span class="nx">ipt</span><span class="p">[</span><span class="nx">y</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="nx">ipt</span><span class="p">[</span><span class="nx">y</span><span class="o">-</span><span class="mi">2</span><span class="p">]</span>
    <span class="p">);</span>

<span class="nx">tmp</span><span class="p">.</span>
  <span class="nx">map</span><span class="p">(</span>
    <span class="c1">// Same as part 1</span>
    <span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">tmp</span><span class="p">[</span><span class="nx">y</span><span class="p">]</span> <span class="o">-</span> <span class="nx">tmp</span><span class="p">[</span><span class="nx">y</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>
    <span class="p">).</span>
  <span class="nx">filter</span><span class="p">(</span>
    <span class="c1">// keeping the ones that have raised</span>
    <span class="nx">x</span> <span class="o">=&gt;</span> <span class="nx">x</span> <span class="o">&gt;</span> <span class="mi">0</span>
  <span class="p">).</span>
  <span class="nx">length</span><span class="p">;</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>## undefined
## undefined
## 1127
</code></pre></div></div>]]></content><author><name>Colin FAY</name><email>contact@colinfay.me</email></author><category term="r-blog-en" /><category term="aoc" /><summary type="html"><![CDATA[Solving Advent of Code 2021-01 with R and JavaScript. I’ll try in this post to write R code that looks like the JavaScript one, using {purrr}, to show how both syntaxes can look alike. The idea is to highlight how you can leverage your {purrr} knowledge when writting JavaScript code. Disclaimer Disclaimer Obviously, this post contains a big spoiler about Advent of Code, as it gives solutions for solving day 1. [Disclaimer bis] I’m no JavaScript expert so this might not be the perfect solution. TBH, that’s also the case for the R solution. About the JavaScript code The JavaScript code has been written in the same RMarkdown as the R code. It runs thanks to the {bubble} package: https://github.com/ColinFay/bubble Instructions Count the number of times n is larger that n-1 Count the number of times n + n-1 + n-2 is larger that n-1 + n-2 + n-3 Find the complete instructions at: https://adventofcode.com/2021/day/1. Reading inputs # Read library(purrr) ipt &lt;- read.delim("2021-01-aoc.txt", header = FALSE)$V1 // Reading the file and converting it to int const fs = require('fs') var ipt = fs.readFileSync("2021-01-aoc.txt", 'utf8').split("\n").filter(x =&gt; x.length != 0); ipt = ipt.map(x =&gt; parseInt(x)); Part one R solution 2:length(ipt) |&gt; map_dbl( ~ ipt[.x] - ipt[.x - 1] ) |&gt; keep( ~ .x &gt; 0 ) |&gt; length() ## [1] 1154 JS solution ipt.map( // inside map() in JS, the second argument to the anonymous // function is the index inside the array. // It mimics what we have in map(2:length(ipt)) in R (x, y) =&gt; ipt[y] - ipt[y-1] ).filter( // filter() will keep the element that matches the anonymous // predicate function x =&gt; x &gt;= 0 ).length; ## 1154 Part two R solution tmp &lt;- 3:length(ipt) |&gt; map_dbl( ~ ipt[.x] + ipt[.x - 1] + ipt[.x - 2] ) 2:length(tmp) |&gt; map_dbl( ~ tmp[.x] - tmp[.x - 1] ) |&gt; keep( ~ .x &gt; 0 ) |&gt; length() ## [1] 1127 JS solution var tmp = ipt. map( // Creating n + n-1 + n-2 (x, y) =&gt; ipt[y] + ipt[y-1] + ipt[y-2] ); tmp. map( // Same as part 1 (x, y) =&gt; tmp[y] - tmp[y-1] ). filter( // keeping the ones that have raised x =&gt; x &gt; 0 ). length; ## undefined ## undefined ## 1127]]></summary></entry></feed>