<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Colin Fay</title>
    <description>The latest articles on DEV Community by Colin Fay (@colinfay).</description>
    <link>https://dev.to/colinfay</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F21644%2Ff3966f29-a36b-43dd-9f4a-81687d61ee2a.jpg</url>
      <title>DEV Community: Colin Fay</title>
      <link>https://dev.to/colinfay</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/colinfay"/>
    <language>en</language>
    <item>
      <title>Rlinguo — Why Did We Build It?</title>
      <dc:creator>Colin Fay</dc:creator>
      <pubDate>Mon, 03 Feb 2025 18:00:00 +0000</pubDate>
      <link>https://dev.to/colinfay/rlinguo-why-did-we-build-it-5eh1</link>
      <guid>https://dev.to/colinfay/rlinguo-why-did-we-build-it-5eh1</guid>
      <description>&lt;p&gt;We recently released something unprecedented: an app that brings R to mobile. Yes, you read that right — R, the statistical programming language, is now in the palm of your hand. Whether you're an environmental researcher, a student, or a logistics professional, R on mobile is here to revolutionize how you work with data on the go.&lt;/p&gt;

&lt;p&gt;You can download Rlinguo today:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📱 &lt;strong&gt;&lt;a href="https://apps.apple.com/fr/app/rlinguo/id6738416608" rel="noopener noreferrer"&gt;iOS on the App Store&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;📱 &lt;strong&gt;&lt;a href="https://play.google.com/store/apps/details?id=com.thinkr.rlinguo" rel="noopener noreferrer"&gt;Android on the Google Play Store&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bringing R to mobile wasn't an easy feat. Like any innovative project, it involved countless hours of trial and error, learning, and refining. But the question remains: &lt;em&gt;Why did we do it?&lt;/em&gt; Why do this in the first place? What’s the real-world impact of having R in your pocket?&lt;/p&gt;

&lt;p&gt;The answer is simple: &lt;strong&gt;mobility matters&lt;/strong&gt;. In a world where data drives decisions, having the ability to analyze, visualize, and act on data anytime, anywhere, is a game-changer. Let’s dive into five real-world scenarios where R on mobile can make a difference.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Field Data Collection and Analysis for Environmental Researchers
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Who?&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Environmental scientists working in remote locations—forests, mountains, oceans—collecting data on-site.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;The Problem&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;These locations typically lack reliable internet access, making it impossible to analyze data in real time with cloud-based tools.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;How R on Mobile Helps&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;With R running on their phone, researchers can input data, perform statistical analyses, and generate visualizations directly on-site. This real-time feedback helps them adjust data collection methods on the fly, improving efficiency and accuracy in their research.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. A Portable Learning Tool for Statistics Students
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Who?&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Students and educators who want to learn or teach statistics using R.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;The Problem&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Not everyone has access to a computer or a stable internet connection, especially when commuting or traveling.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;How R on mobile Helps&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;R on Mobile can turn a phone into an interactive learning tool. Students can input datasets, apply statistical methods, and visualize results in real time. It’s an interactive, hands-on way to learn R and statistics — no laptop required.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Real-Time Quality Control in Manufacturing
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Who?&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Quality assurance teams monitoring production lines in manufacturing plants.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;The Problem&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;These teams often don’t have access to computers while inspecting production lines, making it difficult to analyze data on the spot.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;How R on Mobile Helps&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;With R on mobile, QA teams can analyze sensor data, apply statistical quality control methods, and detect anomalies on the spot. Immediate alerts help them take action before defects escalate, minimizing waste and improving efficiency.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Supply Chain Optimization for Logistics
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Who?&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Logistics professionals managing delivery routes, warehouse operations, or fleet management.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;The Problem&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Supply chains are dynamic, and decisions often need to be made on the fly — but without the right tools, optimization is challenging.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;How R on Mobile Helps&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;R on Mobile enables logistics managers to apply predictive models and optimization algorithms directly from their phones. Whether it’s adjusting delivery routes, managing inventory, or responding to disruptions, Rlinguo ensures you’re always one step ahead.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Healthcare Data Analysis for Field Medics
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Who?&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Healthcare professionals working in remote or underserved areas.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;The Problem&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Field medics often lack access to cloud-based systems for analyzing patient data, which can delay critical decisions.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;How R on mobile Helps&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;R on mobile allows medics to analyze patient health metrics, generate risk scores (e.g., for cardiac events or infectious diseases), and visualize trends directly on their devices. This enables rapid, data-driven decision-making, even in the most challenging environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why R on Mobile?
&lt;/h2&gt;

&lt;p&gt;R on mobile isn’t just for fun — it’s a tool that empowers professionals and learners alike to harness the power of R wherever they are. Whether you’re in a forest, a factory, or a classroom, R on mobile ensures that data analysis is always within reach.&lt;/p&gt;

&lt;p&gt;Have questions or feedback? We’d love to hear from you! &lt;a href="https://rtask.thinkr.fr/fr/contact/" rel="noopener noreferrer"&gt;Reach out to us!&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;🚀 &lt;strong&gt;download Rlinguo:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📱 &lt;strong&gt;&lt;a href="https://apps.apple.com/fr/app/rlinguo/id6738416608" rel="noopener noreferrer"&gt;iOS on the App Store&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;📱 &lt;strong&gt;&lt;a href="https://play.google.com/store/apps/details?id=com.thinkr.rlinguo" rel="noopener noreferrer"&gt;Android on the Google Play Store&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>rstats</category>
      <category>webr</category>
    </item>
    <item>
      <title>Setting values in R6 classes, and testing with shiny::MockShinySession</title>
      <dc:creator>Colin Fay</dc:creator>
      <pubDate>Wed, 08 Jan 2025 17:00:00 +0000</pubDate>
      <link>https://dev.to/colinfay/setting-values-in-r6-classes-and-testing-with-shinymockshinysession-23l1</link>
      <guid>https://dev.to/colinfay/setting-values-in-r6-classes-and-testing-with-shinymockshinysession-23l1</guid>
      <description>&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;Recently, we worked on testing a &lt;code&gt;{shiny}&lt;/code&gt; app that relies on values stored within the &lt;code&gt;session$request&lt;/code&gt; object. This object is an environment that captures the details of the HTTP exchange between R and the browser. Without diving too deeply into the technicalities (as much as I’d love to 😅), it’s important to understand that &lt;code&gt;session$request&lt;/code&gt; contains information provided by both the browser and any proxy redirecting the requests. Our app is deployed behind a proxy in a Microsoft Azure environment. Here, the authentication service attaches several headers to validate user identity (see &lt;a href="https://learn.microsoft.com/en-us/azure/app-service/configure-authentication-user-identities" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; for details). Headers like &lt;code&gt;X-MS-CLIENT-PRINCIPAL&lt;/code&gt; and &lt;code&gt;X-MS-CLIENT-PRINCIPAL-ID&lt;/code&gt; are critical for identifying users, and the &lt;code&gt;{shiny}&lt;/code&gt; app depends on these to manage authentication.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing headers
&lt;/h2&gt;

&lt;p&gt;When a user connects to the app, their identifiers are retrieved from a header and stored for use throughout the app. Here's a simplified example of how this might work:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;library(shiny)

ui &amp;lt;- fluidPage(
  textOutput("user_id")
)

server &amp;lt;- function(input, output, session) {
  r &amp;lt;- reactiveValues(
    email = NULL
  )

  observe({
    r$email &amp;lt;- session$request$HTTP_X_MS_CLIENT_PRINCIPAL_NAME
  })

  output$user_id &amp;lt;- renderText({
    req(r$email)
    sprintf("Hello %s", r$email)
  })
}

shinyApp(ui, server)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Testing this functionality, particularly in Continuous Integration (CI) environments, can be challenging. In our use case, we’d love to have something like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test_that("app server", {

  # Tweaking the session here

  testServer(app_server, {
    # Waiting for the session to be fired up
    session$elapse(1)

    expect_equal(
      r$email,
      "myemail@company.com"
    )
  })
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;But Authentication headers like &lt;code&gt;HTTP_X_MS_CLIENT_PRINCIPAL_NAME&lt;/code&gt; are absent during automated tests, so we need a way to simulate their presence. &lt;code&gt;{shiny}&lt;/code&gt; provides the &lt;code&gt;MockShinySession&lt;/code&gt; class for testing, but it doesn’t natively simulate a realistic &lt;code&gt;session$request&lt;/code&gt; object. Let’s explore how to work around this limitation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Overriding &lt;code&gt;session$request&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;We first attempt to directly modify &lt;code&gt;session$request&lt;/code&gt;, but it doesn't work:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; session &amp;lt;- MockShinySession$new()
&amp;gt; session$request
&amp;lt;environment: 0x13a032600&amp;gt;
Warning message:
In (function (value)  :
  session$request doesn't currently simulate a realistic request on MockShinySession
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Ok, maybe we can assign a new entry here?&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; session$request$HTTP_X_MS_CLIENT_PRINCIPAL_NAME &amp;lt;- "test"
Error in (function (value)  : session$request can't be assigned to
In addition: Warning message:
In (function (value)  :
  session$request doesn't currently simulate a realistic request on MockShinySession
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Ouch, it doesn't work, it can't be assigned to. But let's continue our exploration. What is &lt;code&gt;session&lt;/code&gt;?&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; class(session)
[1] "MockShinySession" "R6"
&amp;gt; class(session$request)
[1] "environment"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As we can see, it's an R6 object, an instance of the &lt;code&gt;MockShinySession&lt;/code&gt; class, and &lt;code&gt;session$request&lt;/code&gt; an env. What we want is being able to access, in our app, to &lt;code&gt;session$request$HTTP_X_MS_CLIENT_PRINCIPAL_NAME&lt;/code&gt;. Maybe we could override &lt;code&gt;request&lt;/code&gt;? &lt;code&gt;request&lt;/code&gt; is contained in the &lt;code&gt;active&lt;/code&gt; field of the R6 class:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; MockShinySession$active
# [...]

$request
function (value)
{
    if (!missing(value)) {
        stop("session$request can't be assigned to")
    }
    warning("session$request doesn't currently simulate a realistic request on MockShinySession")
    new.env(parent = emptyenv())
}
&amp;lt;bytecode: 0x11f25d8a8&amp;gt;
&amp;lt;environment: namespace:shiny
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To override the &lt;code&gt;request&lt;/code&gt; object, we can use the &lt;code&gt;set()&lt;/code&gt; method of the R6 class. Here’s how we redefine the behavior:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MockShinySession$set(
    "active",
    "request",
    function(value) {
      return(
        list(
          "HTTP_X_MS_CLIENT_PRINCIPAL_NAME" = "myemail@company.com"
        )
      )
    },
    overwrite = TRUE
  )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now, the session behaves as expected:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; session &amp;lt;- MockShinySession$new()
&amp;gt; session$request
$HTTP_X_MS_CLIENT_PRINCIPAL_NAME
[1] "myemail@company.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Writing the Test
&lt;/h3&gt;

&lt;p&gt;With the overridden &lt;code&gt;request&lt;/code&gt;, we can now write a functional test:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test_that("app server", {
  MockShinySession$set(
    "active",
    "request",
    function(value) {
      return(
        list(
          "HTTP_X_MS_CLIENT_PRINCIPAL_NAME" = "myemail@company.com"
        )
      )
    },
    overwrite = TRUE
  )

  testServer(app_server, {
    # Waiting for the session to be fired up
    session$elapse(1)

    expect_equal(
      r$email,
      "myemail@company.com"
    )
  })
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Cleaning Up After Tests
&lt;/h3&gt;

&lt;p&gt;But, just one more thing: we need to clean our test so that the session object stays the same after our test. For this, we'll use &lt;code&gt;on.exit&lt;/code&gt; to restore the old behavior:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test_that("app server", {
  old_request &amp;lt;- MockShinySession$active$request
  on.exit({
    MockShinySession$set(
      "active",
      "request",
      old_request,
      overwrite = TRUE
    )
  })
  MockShinySession$set(
    "active",
    "request",
    function(value) {
      return(
        list(
          "HTTP_X_MS_CLIENT_PRINCIPAL_NAME" = "myemail@company.com"
        )
      )
    },
    overwrite = TRUE
  )

  testServer(app_server, {
    # Waiting for the session to be fired up
    session$elapse(1)

    expect_equal(
      r$email,
      "myemail@company.com"
    )
  })
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This setup ensures that our tests remain isolated and reliable, even in CI environments. By leveraging R6’s flexibility, we can fully control and mock &lt;code&gt;session$request&lt;/code&gt; to test authentication-dependent logic. If you want to dig more into the details, you can &lt;a href="https://github.com/ThinkR-open/MockShinySessionInTest" rel="noopener noreferrer"&gt;visit this repo, where you'll find a reproducible example&lt;/a&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  Do you need help with testing your apps?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Still unsure how to implement a good testing strategy for your app?  &lt;a href="https://rtask.thinkr.fr/contact/" rel="noopener noreferrer"&gt;Let’s chat!&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>rstats</category>
      <category>shiny</category>
      <category>testing</category>
    </item>
    <item>
      <title>Introducing Rlinguo, a native mobile app that runs R</title>
      <dc:creator>Colin Fay</dc:creator>
      <pubDate>Mon, 23 Dec 2024 15:00:00 +0000</pubDate>
      <link>https://dev.to/colinfay/introducing-rlinguo-a-native-mobile-app-that-runs-r-2bl3</link>
      <guid>https://dev.to/colinfay/introducing-rlinguo-a-native-mobile-app-that-runs-r-2bl3</guid>
      <description>&lt;p&gt;TL;DR&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Rlinguo&lt;/code&gt; is a groundbreaking mobile application &lt;strong&gt;that uses R in the backend to handle its business logic&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Available for download now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://apps.apple.com/fr/app/rlinguo/id6738416608?" rel="noopener noreferrer"&gt;iOs on the AppStore&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://play.google.com/store/apps/details?id=com.thinkr.rlinguo" rel="noopener noreferrer"&gt;Android on the Google Play Store&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yes, you read that right: &lt;strong&gt;the future is now, and we can run R on your mobile phone&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Once upon a time…
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;I've been building web apps with R for quite some time now&lt;/strong&gt;—probably longer than I’d like to admit. At ThinkR, we’ve been developing &lt;code&gt;{shiny}&lt;/code&gt; apps for a while, ranging from small widgets for researchers to large-scale dashboards used by thousands of bankers. But web development hasn’t been limited to &lt;code&gt;{shiny}&lt;/code&gt; for us. We’ve also created REST APIs, flex/quarto dashboards, and dynamic documents generated with R. Over the past few years, we’ve explored various ways to deliver data products to our clients, primarily using &lt;code&gt;{shiny}&lt;/code&gt; apps but also through other formats. Amid this diversity of formats, one platform has consistently evaded us: mobile. Yes, &lt;strong&gt;&lt;code&gt;{shiny}&lt;/code&gt; apps can be optimized for mobile use, and JavaScript dashboards can display nicely on a phone. However, they’re not native mobile apps&lt;/strong&gt;. They can’t be installed from an app store, don’t have access to your phone’s APIs, and making them work offline is a significant challenge.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;{shiny}&lt;/code&gt; on my mobile
&lt;/h2&gt;

&lt;p&gt;"&lt;code&gt;{shiny}&lt;/code&gt; on my mobile" has been a recurring request from our clients for some time now. And it’s easy to see why: &lt;strong&gt;you might have a fantastic model or efficient data wrangling process, and you need people to access it on their smartphones&lt;/strong&gt;—even in the middle of the forest with no cell connection (yes, that was an actual request from one of our clients). Yet, our response has always been the same: "Sorry, that’s not possible." &lt;code&gt;{shiny}&lt;/code&gt; relies on a server, and since R can’t run directly on a phone, a remote server is required. That means an internet connection is necessary. And no, you can’t publish it to an app store. In recent months, however, something new has emerged—a development that R enthusiasts have been dreaming of for years: R, compiled for WebAssembly. If you’re unfamiliar with WebAssembly, think of it as a way to run R inside JavaScript. If you’re already acquainted with it, you’ll know it’s a bit more nuanced than that, but I’ll spare the technical details for now. This new tool is called &lt;a href="https://docs.r-wasm.org/webr/latest/" rel="noopener noreferrer"&gt;&lt;code&gt;webR&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The “What If” Moment
&lt;/h2&gt;

&lt;p&gt;I’ve been experimenting with &lt;code&gt;webR&lt;/code&gt; for several months now, primarily from a Node.js perspective. During this time, I developed two tools, &lt;a href="https://github.com/ColinFay/webrcli" rel="noopener noreferrer"&gt;&lt;code&gt;webrcli&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/ColinFay/spidyr" rel="noopener noreferrer"&gt;&lt;code&gt;spidyr&lt;/code&gt;&lt;/a&gt;, specifically designed for projects that integrate R and Node.js. While these tools have the potential to revolutionize app development, they are still tied to a remote web-based approach: R runs on a server elsewhere, inside a Node.js process. This means there’s still no offline functionality, no app store deployment, and no native mobile features. This summer, we received yet another request for "&lt;code&gt;{shiny}&lt;/code&gt; as a native mobile app." As we were about to deliver our usual response, I had a "What if?" moment. &lt;strong&gt;&lt;em&gt;What if we could use &lt;code&gt;webR&lt;/code&gt; in a JavaScript framework that creates mobile apps?&lt;/em&gt;&lt;/strong&gt; With this idea, we developed a proof of concept (PoC): a mobile app capable of running one line of R code. A fully functional app of this nature had never been done before, but we believed it was achievable. Unfortunately, the client chose not to pursue the project. Building a mobile app requires skills beyond those typical of &lt;code&gt;{shiny}&lt;/code&gt; developers, and they needed software they could maintain independently. Nevertheless, &lt;strong&gt;having created a PoC, we decided to go all in and develop a complete application&lt;/strong&gt;—something user-friendly, slightly playful, and distributable via app stores, to prove that this could indeed be done. Over the next few weeks, the team at ThinkR brought this vision to life. &lt;a href="https://rtask.thinkr.fr/consultants-r-experts/margot-brard/" rel="noopener noreferrer"&gt;Margot&lt;/a&gt; shaped the project and wrote the concept paper (more on that in a future blog post), &lt;a href="https://rtask.thinkr.fr/consultants-r-experts/arthur-breant/" rel="noopener noreferrer"&gt;Arthur&lt;/a&gt; crafted the app mock-up using Excalidraw and Figma, showcasing his exceptional UI skills, and I (&lt;a href="https://rtask.thinkr.fr/consultants-r-experts/colin-fay/" rel="noopener noreferrer"&gt;Colin&lt;/a&gt;) focused on backend development (largely because my design skills are... lacking, and frontend work is not my forte). The result of this exciting journey is &lt;strong&gt;Rlinguo&lt;/strong&gt;, a native mobile app that runs R. &lt;strong&gt;Yes, you read that correctly—R can now run on your phone, and this app is a proof of concept turned reality&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Shape of R to Come
&lt;/h2&gt;

&lt;p&gt;Rlinguo is a playful and quirky game that challenges users to identify which package a given R function belongs to. Admittedly, this may not be the most practical skill—knowing that &lt;code&gt;mean()&lt;/code&gt; is in &lt;code&gt;{base}&lt;/code&gt; while &lt;code&gt;median()&lt;/code&gt; resides in &lt;code&gt;{stats}&lt;/code&gt; won’t necessarily make you a better developer (although, I must admit, it still irks me that they’re in separate packages). The primary goal of this app was to serve as proof that native mobile apps leveraging the power of R are not only possible but can also pave the way for broader applications. The app demonstrates several groundbreaking capabilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;R at your fingertips, straight from your pocket:&lt;/strong&gt; The app showcases R’s functionality in multiple ways, such as performing a &lt;code&gt;sample()&lt;/code&gt; from a list of installed packages and another from exported functions. On the About page, users can even execute a &lt;code&gt;runif()&lt;/code&gt; to confirm that Rlinguo truly runs R behind the scenes.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Offline Functionality:&lt;/strong&gt; Designed to work without an internet connection, Rlinguo makes it possible to deploy R models on smartphones—even for users in remote, disconnected locations, such as deep in the woods&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Enabling a Real Data-Science Workflow:&lt;/strong&gt; Rlinguo mirrors a typical data-science pipeline. Data is stored locally in an SQL database, queried, processed in R (via counting and pivoting), and then returned to JavaScript for visualization. The result? A user-friendly display of your game performance.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Integrating R Packages:&lt;/strong&gt; The app uses R packages creatively. When you guess a function correctly, you’re rewarded with a &lt;code&gt;praise::praise()&lt;/code&gt; message; if you’re wrong, a custom function provides feedback (though I’ve yet to find a package dedicated to offering words of encouragement).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Delivering a Great User Experience:&lt;/strong&gt; Rlinguo prioritizes usability, addressing a common shortfall in tools incorporating R. The goal was to craft an app that feels polished and enjoyable to use.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Deploying R-Based Apps to Stores:&lt;/strong&gt; Rlinguo demonstrates that it’s entirely feasible to create a mobile app powered by R and make it available for download on major app stores..&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rlinguo is available now on the Apple Store and Google Play. We see this as the first step in a new wave of innovation, and to borrow a phrase, "baby, you never felt this good."&lt;/p&gt;

&lt;h2&gt;
  
  
  You've got a use case?
&lt;/h2&gt;

&lt;p&gt;The idea of running R on a phone has been a popular request from our clients — there are countless possibilities. Is this something you need? Do you have a fantastic use case for R on your phone? &lt;a href="https://rtask.thinkr.fr/contact/" rel="noopener noreferrer"&gt;Perfect, let's chat!&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Notes:&lt;/p&gt;

&lt;p&gt;(*) We identified two applications on the App Store that incorporate &lt;code&gt;webR&lt;/code&gt;. However, these appear to function primarily as wrappers around JavaScript integrated development environments (IDEs) and do not address our specific objective: creating a "real-life" mobile application that utilizes R for its core business logic.&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

</description>
      <category>rstats</category>
      <category>webr</category>
    </item>
    <item>
      <title>webrcli &amp; spidyr: What’s new</title>
      <dc:creator>Colin Fay</dc:creator>
      <pubDate>Fri, 14 Jun 2024 00:00:00 +0000</pubDate>
      <link>https://dev.to/colinfay/webrcli-spidyr-whats-new-34oh</link>
      <guid>https://dev.to/colinfay/webrcli-spidyr-whats-new-34oh</guid>
      <description>&lt;p&gt;Find all my posts about webR &lt;a href="https://colinfay.me/categories/#webr" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: the first post of this series explaining roughly what &lt;code&gt;webR&lt;/code&gt; is, I won’t introduce it again here.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this blogpost, you’ll find some updates about some recent changes made in &lt;code&gt;webrcli&lt;/code&gt; &amp;amp; &lt;code&gt;spidyr&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What webrcli &amp;amp; spidy wants to achieve
&lt;/h2&gt;

&lt;p&gt;In a nutshell, the goal of &lt;code&gt;webrcli&lt;/code&gt; &amp;amp; &lt;code&gt;spidyr&lt;/code&gt; is to provide an interface for bringing your R functions inside a NodeJS runtime.&lt;/p&gt;

&lt;p&gt;Think of it as something that wants to achieve the following interation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;An R dev writes, builds and shares a package called &lt;code&gt;{rfuns}&lt;/code&gt;, which contains a function called &lt;code&gt;hello_world()&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A NodeJS dev takes this package, can call it with &lt;code&gt;rfuns.hello_world()&lt;/code&gt;, access and manipulate the results with JavaScript.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fet2max4pjjh6j771zi9o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fet2max4pjjh6j771zi9o.png" width="800" height="278"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  An important note
&lt;/h2&gt;

&lt;p&gt;With &lt;code&gt;webR&lt;/code&gt; &amp;amp; the tools described below, there is no R session running while the NodeJS app is running. &lt;code&gt;spidyr&lt;/code&gt; imports an R function &lt;strong&gt;converted to a native JS&lt;/strong&gt;.&lt;code&gt;rfuns.hello_world&lt;/code&gt; does not call an R session running somewhere else.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Below are some updates recently made to both &lt;code&gt;webrcli&lt;/code&gt; &amp;amp; &lt;code&gt;spidyr&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the template code smaller
&lt;/h2&gt;

&lt;p&gt;When drafting the first code of &lt;code&gt;webrcli&lt;/code&gt; &amp;amp; &lt;code&gt;spidyr&lt;/code&gt;, I focused on providing a minimal template for an &lt;code&gt;index.js&lt;/code&gt; loading your R functions, contained in a subdirectory inside your node project.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;index.js&lt;/code&gt; look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const {
  initSpidyr,
  mountLocalPackage
} = require('spidyr');

(async () =&amp;gt; {

  await initSpidyr()

  const rfuns = await mountLocalPackage("./rfuns");

  const hw = await rfuns.hello_world()

  console.log(hw.values);

  console.log("✅ Everything is ready!");
})();

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This minimal template makes it easier to read and understand what the code does. Note that you’ll get this template if you run &lt;code&gt;webrcli init myproject&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let’s decompose what this code does :&lt;/p&gt;

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

&lt;h2&gt;
  
  
  &lt;code&gt;new Library()&lt;/code&gt; is now &lt;code&gt;library()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Calling a package is now &lt;code&gt;const spongebob = library("spongebob")&lt;/code&gt; instead of the previous approach which was &lt;code&gt;const spongebob = new Library("spongebob");await spongebob.load(globalThis.webR);&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Better installation from package.json &amp;amp; &lt;code&gt;DESCRIPTION&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The previous version of &lt;code&gt;webrcli&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;Now, a project initiated with &lt;code&gt;webrcli&lt;/code&gt; will store all the packages installed with &lt;code&gt;webrcli install&lt;/code&gt; inside the &lt;code&gt;package.json&lt;/code&gt; file. Then, when running the &lt;code&gt;npm install&lt;/code&gt; command, the R packages will also be redownloaded.&lt;/p&gt;

&lt;p&gt;You can also installed a series of packages based on a &lt;code&gt;DESCRIPTION&lt;/code&gt; file with &lt;code&gt;webrcli installFromDesc&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other tools
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;shareEnv&lt;/code&gt; allows to &lt;strong&gt;copy&lt;/strong&gt; one, several or all env var from node to the webR instance.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Workflow summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;webrcli init myproject&lt;/code&gt; will create a project skeleton at &lt;code&gt;./myproject&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;webrcli install spongebob&lt;/code&gt; will download the version of &lt;code&gt;{spongebob}&lt;/code&gt; compiled for &lt;code&gt;webR&lt;/code&gt;, and add it to &lt;code&gt;./webr_packages&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can then modify you R code with your own functions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Call your R function from &lt;code&gt;rfuns.xyz()&lt;/code&gt; in Node&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run with &lt;code&gt;npm start&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ox26lqcif0hwjrbw4p1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ox26lqcif0hwjrbw4p1.png" width="800" height="269"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Please do give it a shot
&lt;/h2&gt;

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

&lt;h2&gt;
  
  
  Future works
&lt;/h2&gt;

&lt;p&gt;Here are what I plan on working in the upcoming weeks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;webrcli init&lt;/code&gt; should allow for specifying a &lt;code&gt;webr&lt;/code&gt; version from npm &lt;a href="https://github.com/ColinFay/webrcli/issues/17" rel="noopener noreferrer"&gt;issue 17&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;initSpidyr&lt;/code&gt; should allow to pass params to &lt;code&gt;webR.init()&lt;/code&gt; &lt;a href="https://github.com/ColinFay/spidyr/issues/12" rel="noopener noreferrer"&gt;issue 12&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Allow to install from &lt;code&gt;r-universe&lt;/code&gt;, something like &lt;code&gt;webrcli installFromRUniverse&lt;/code&gt; &lt;a href="https://github.com/ColinFay/webrcli/issues/16" rel="noopener noreferrer"&gt;issue 16&lt;/a&gt;. Probably in the future it will be merged to &lt;code&gt;webrcli install&lt;/code&gt; and we’ll have a way to guess what the user wants based on the input.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A small converter from R named list to JS objects &lt;a href="https://github.com/ColinFay/spidyr/issues/3" rel="noopener noreferrer"&gt;Issue 3&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;More documentation, and probably documenting the conversion of an existing &lt;code&gt;{shiny}&lt;/code&gt; app to a &lt;code&gt;spidyr&lt;/code&gt; based one.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>rblogen</category>
      <category>webr</category>
    </item>
    <item>
      <title>webrcli &amp; spidyr: A starter pack for building NodeJS projects with webR inside</title>
      <dc:creator>Colin Fay</dc:creator>
      <pubDate>Wed, 06 Mar 2024 00:00:00 +0000</pubDate>
      <link>https://dev.to/colinfay/webrcli-spidyr-a-starter-pack-for-building-nodejs-projects-with-webr-inside-5d79</link>
      <guid>https://dev.to/colinfay/webrcli-spidyr-a-starter-pack-for-building-nodejs-projects-with-webr-inside-5d79</guid>
      <description>&lt;p&gt;This post is the sixth one of a series of post about webR:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/colinfay/using-webr-in-an-express-js-rest-api-2j0i"&gt;Using webR in an Express JS REST API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/colinfay/the-old-faithful-geyser-data-shiny-app-with-webr-bootstrap-expressjs-48cc"&gt;The Old Faithful Geyser Data shiny app with webR, Bootstrap &amp;amp; ExpressJS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/colinfay/preloading-your-r-packages-in-webr-in-an-express-js-api-34ok"&gt;Preloading your R packages in webR in an Express JS API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/colinfay/using-my-own-r-functions-in-webr-in-an-express-js-api-and-thoughts-on-building-web-apps-with-node-webr-5f12"&gt;Using my own R functions in webR in an Express JS API, and thoughts on building web apps with Node &amp;amp; webR&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/colinfay/rethinking-packages-functions-preloading-in-webr-022-c5k"&gt;Rethinking webR package &amp;amp; functions preloading with webR 0.2.2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;A starter pack for building NodeJS projects with webR inside&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: the first post of this series explaining roughly what &lt;code&gt;webR&lt;/code&gt; is, I won’t introduce it again here.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Again, a new project?
&lt;/h2&gt;

&lt;p&gt;Most of the previous posts have been about poking around with &lt;code&gt;webR&lt;/code&gt; inside NodeJS, but nothing stable on how to build an app.&lt;/p&gt;

&lt;p&gt;That’s something I’m trying to change. Based on the experience gathered building apps in R (in &lt;code&gt;{shiny}&lt;/code&gt; with &lt;code&gt;{golem}&lt;/code&gt;) 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 &lt;code&gt;webR&lt;/code&gt;/&lt;code&gt;NodeJS&lt;/code&gt; project.&lt;/p&gt;

&lt;p&gt;The idea is to have one project with inside:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One R package&lt;/li&gt;
&lt;li&gt;One JS app&lt;/li&gt;
&lt;li&gt;A tool that brings the R package into the NodeJS app and allow to use the R functions inside JavaScript&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This skeleton needed a toolkit to perform the following task:&lt;/p&gt;

&lt;p&gt;1️⃣ From a cli point of view:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;init the project with a specific skeleton&lt;/li&gt;
&lt;li&gt;allow to install packages from the &lt;code&gt;webr&lt;/code&gt;CRAN to a local &lt;code&gt;webr&lt;/code&gt; library, in the spirit of &lt;a href="https://dev.to/colinfay/rethinking-packages-functions-preloading-in-webr-022-c5k"&gt;previous post&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;allow to install the deps from a DESCRIPTION file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;2️⃣ From a NodeJS point of view:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Load the packages downloaded in a local &lt;code&gt;webr&lt;/code&gt; library&lt;/li&gt;
&lt;li&gt;Create an interface to load and manipulate the functions from a package downloaded from CRAN, and use them in JS.&lt;/li&gt;
&lt;li&gt;Same but with a local package folder&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the function manipulation mechanism, I wanted something that would allow to think in terms of &lt;code&gt;pkg::fun()&lt;/code&gt; but in JavaScript, which would translate as &lt;code&gt;pkg.fun()&lt;/code&gt; in your Node app. Something I had implemented in another old NodeJS module called &lt;a href="https://github.com/ColinFay/hordes" rel="noopener noreferrer"&gt;hordes&lt;/a&gt;, which I can now safely deprecate in favor of the following tools.&lt;/p&gt;

&lt;p&gt;The reasoning being: the R dev writes a standalone package that exports an &lt;code&gt;xyz&lt;/code&gt; function, then the web team can load this package, and launch &lt;code&gt;xyz()&lt;/code&gt; without writing any R code.&lt;/p&gt;

&lt;h2&gt;
  
  
  webrcli &amp;amp; spidyr
&lt;/h2&gt;

&lt;p&gt;So, here comes &lt;a href="https://www.npmjs.com/package/webrcli" rel="noopener noreferrer"&gt;webrcli&lt;/a&gt; and &lt;a href="https://www.npmjs.com/package/spidyr" rel="noopener noreferrer"&gt;spidyr&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;❗️ 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 ❗️&lt;/p&gt;

&lt;h3&gt;
  
  
  Project init
&lt;/h3&gt;

&lt;p&gt;These packages are made to be used together, and here is an example of how to use them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 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 ----

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The project is now created, let’s move into it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd ./webrspongebob
tree -L 1


.
├── index.js
├── node_modules
├── package-lock.json
├── package.json
├── rfuns
└── webr_packages

4 directories, 3 files

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is how it’s organized:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;index.js&lt;/code&gt; is the main file for the app, &lt;code&gt;node_modules&lt;/code&gt; the standard folder for the node deps&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;package-lock.json&lt;/code&gt; / &lt;code&gt;package.json&lt;/code&gt; are standard Node metadata files&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rfuns&lt;/code&gt; contains the R package that will be added to the NodeJS app&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;webr_packages&lt;/code&gt; contains the R dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can launch the app with &lt;code&gt;node index.js&lt;/code&gt; and it will output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node index.js
👉 Loading WebR ----
👉 Loading R packages ----
ℹ Loading rfuns
['Hello, world!']
✅ Everything is ready

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s dive a bit inside the &lt;code&gt;index.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const path = require('path');
const { WebR } = require('webr');
const { loadPackages, LibraryFromLocalFolder } = require('spidyr');

const rfuns = new LibraryFromLocalFolder("rfuns");

(async () =&amp;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!");

})();

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here are the bits that are specific to a &lt;code&gt;spidyr&lt;/code&gt; project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const rfuns = new LibraryFromLocalFolder("rfuns");

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function will take a local folder containing an R package, and load the functions from this package into the &lt;code&gt;rfuns&lt;/code&gt; object. Here, for example, our R package contains one R function, &lt;code&gt;hello_world()&lt;/code&gt;, it will then be available in NodeJS as &lt;code&gt;rfuns.hello_world()&lt;/code&gt; once the &lt;code&gt;mountAndLoad&lt;/code&gt; function is called.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;await loadPackages(
  globalThis.webR,
  path.join(__dirname, 'webr_packages')
)

await rfuns.mountAndLoad(
  globalThis.webR,
  path.join(__dirname, 'rfuns')
);

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first bit loads the &lt;code&gt;webr_packages&lt;/code&gt; folder, containing all the R dependencies, then the second bit mount the local folder into the &lt;code&gt;webR&lt;/code&gt; file system and load the functions.&lt;/p&gt;

&lt;p&gt;Finally, &lt;code&gt;const hw = await rfuns.hello_world()&lt;/code&gt; calls the function from the R package, and its value is &lt;code&gt;console.log&lt;/code&gt;ed just after that.&lt;/p&gt;

&lt;h3&gt;
  
  
  With a CRAN package
&lt;/h3&gt;

&lt;p&gt;And now, how do I load a CRAN package? For example, let’s say I want to use &lt;code&gt;{spongebob}&lt;/code&gt; in my app?&lt;/p&gt;

&lt;p&gt;First, let’s install &lt;code&gt;{spongebob}&lt;/code&gt; via &lt;code&gt;webrcli&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;webrcli install spongebob


✅ {spongebob} downloaded and extracted ----

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, let’s update our &lt;code&gt;index.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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 () =&amp;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!");;

})();

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;const spongebob = new Library("spongebob");&lt;/code&gt; will create a lib, ready to mount a package&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;await spongebob.load(globalThis.webR)&lt;/code&gt; will load all the functions from the &lt;code&gt;{spongebob}&lt;/code&gt; package&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;const said = await spongebob.tospongebob("hello from spongebob")&lt;/code&gt; will run the &lt;code&gt;spongebob::tospongebob()&lt;/code&gt; R function&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node index.js


👉 Loading WebR ----
👉 Loading R packages ----
ℹ Loading rfuns
['Hello, world!']
['helLo fROm spONgEbOb']
✅ Everything is ready!

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Some random notes
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;webrspongebob&lt;/code&gt; example is available at &lt;a href="https://github.com/ColinFay/webrspongebob" rel="noopener noreferrer"&gt;https://github.com/ColinFay/webrspongebob&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  About the current implementation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Right now I’m not really sure how this handles the infix function like &lt;code&gt;%&amp;gt;%&lt;/code&gt; for example. That being said, this might not be a function you’d want to use in the current way of building things.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The exported functions are read via &lt;code&gt;getNamespaceExports("pkg")&lt;/code&gt; and &lt;code&gt;getExportedValue("pkg", "fun")&lt;/code&gt;, 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.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;webrcli&lt;/code&gt; and &lt;code&gt;spidyr&lt;/code&gt; both encapsulate code run in &lt;code&gt;webR&lt;/code&gt;, especially &lt;code&gt;webrcli&lt;/code&gt; which has an &lt;a href="https://github.com/ColinFay/webrcli/blob/main/rpkg/R/get_list_of_tar_gz_dependencies_for_package.R" rel="noopener noreferrer"&gt;R script&lt;/a&gt; which is sourced and run in a &lt;code&gt;webR&lt;/code&gt; instance.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Future
&lt;/h3&gt;

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

&lt;p&gt;If ever you find a bug or have an idea, feel free to open issues at :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/ColinFay/webrcli" rel="noopener noreferrer"&gt;https://github.com/ColinFay/webrcli&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/ColinFay/spidyr" rel="noopener noreferrer"&gt;https://github.com/ColinFay/spidyr&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webr</category>
      <category>rstats</category>
    </item>
    <item>
      <title>Rethinking packages &amp; functions preloading in webR 0.2.2</title>
      <dc:creator>Colin Fay</dc:creator>
      <pubDate>Tue, 21 Nov 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/colinfay/rethinking-packages-functions-preloading-in-webr-022-c5k</link>
      <guid>https://dev.to/colinfay/rethinking-packages-functions-preloading-in-webr-022-c5k</guid>
      <description>&lt;p&gt;This post is the fifth one of a series of post about webR:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/calling-webr-from-expressjs/"&gt;Using webR in an Express JS REST API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/old-faithful-express-bootstrap-webr/"&gt;The Old Faithful Geyser Data shiny app with webR, Bootstrap &amp;amp; ExpressJS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/preloading-your-r-packages-in-webr-in-an-express-js-api/"&gt;Preloading your R packages in webR in an Express JS API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/using-own-functions-in-webr-node-js/"&gt;Using my own R functions in webR in an Express JS API, and thoughts on building web apps with Node &amp;amp; webR&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Rethinking webR package &amp;amp; functions preloading with webR 0.2.2&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: the first post of this series explaining roughly what webR is, I won’t introduce it again here.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When I wrote my blogpost about &lt;a href="https://dev.to/preloading-your-r-packages-in-webr-in-an-express-js-api/"&gt;Preloading your R packages in webR in an Express JS API&lt;/a&gt;, 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 &lt;a href="https://github.com/r-wasm/webr/issues/260" rel="noopener noreferrer"&gt;Github issue&lt;/a&gt;). This also meant that I couldn’t easily take my own functions and run them in the webR environment, as described in &lt;a href="https://dev.to/using-own-functions-in-webr-node-js"&gt;Using my own R functions in webR in an Express JS API, and thoughts on building web apps with Node &amp;amp; webR&lt;/a&gt;. These needs made me write the &lt;a href="https://www.npmjs.com/package/webrtools" rel="noopener noreferrer"&gt;webrtools&lt;/a&gt; NodeJS package, to do just that:&lt;/p&gt;

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

&lt;p&gt;The last webR version now exposes Emscripten’s &lt;code&gt;FS.mount&lt;/code&gt;, 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.&lt;/p&gt;

&lt;p&gt;That implied some rework (now integrated to &lt;code&gt;webrtools&lt;/code&gt;) :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;webR.FS.mkdir&lt;/code&gt; 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).&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;webR.FS.mount&lt;/code&gt; to mount the local directory into the newly created library&lt;/li&gt;
&lt;li&gt;Ensure that this new library is in the libPath()&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the new code for &lt;code&gt;loadPackages&lt;/code&gt;, bundled into &lt;code&gt;webrtools&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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()))`);
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I’ve also decided to deprecate the &lt;code&gt;loadFolder&lt;/code&gt; function, as it is now native with &lt;code&gt;webR.FS.mkdir&lt;/code&gt; + &lt;code&gt;webR.FS.mount&lt;/code&gt;.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const app = require('express')()
const path = require('path');
const { loadPackages } = require('webrtools');
const { WebR } = require('webr');

(async () =&amp;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', () =&amp;gt; {
    console.log('http://localhost:3000')
  })

})();

app.get('/', async (req, res) =&amp;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) =&amp;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);
  }
});

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full code is at &lt;a href="https://github.com/ColinFay/webr-examples/tree/main/webr-preload-funs" rel="noopener noreferrer"&gt;ColinFay/webr-examples/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;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 &lt;a href="https://docs.r-wasm.org/webr/latest/mounting.html#emscripten-filesystem-images" rel="noopener noreferrer"&gt;here&lt;/a&gt;. This might come handy for our current structure, but I’ll have to explore it a bit more.&lt;/p&gt;

</description>
      <category>rblogen</category>
    </item>
    <item>
      <title>Using my own R functions in webR in an Express JS API, and thoughts on building web apps with Node &amp; webR</title>
      <dc:creator>Colin Fay</dc:creator>
      <pubDate>Tue, 17 Oct 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/colinfay/using-my-own-r-functions-in-webr-in-an-express-js-api-and-thoughts-on-building-web-apps-with-node-webr-5f12</link>
      <guid>https://dev.to/colinfay/using-my-own-r-functions-in-webr-in-an-express-js-api-and-thoughts-on-building-web-apps-with-node-webr-5f12</guid>
      <description>&lt;p&gt;This post is the fourth one of a series of post about webR:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/calling-webr-from-expressjs/"&gt;Using webR in an Express JS REST API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/old-faithful-express-bootstrap-webr/"&gt;The Old Faithful Geyser Data shiny app with webR, Bootstrap &amp;amp; ExpressJS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/preloading-your-r-packages-in-webr-in-an-express-js-api/"&gt;Preloading your R packages in webR in an Express JS API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Using my own R functions in webR in an Express JS API, and thoughts on building web apps with Node &amp;amp; webR&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: the first post of this series explaining roughly what webR is, I won’t introduce it again here.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;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 &lt;code&gt;webR&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;How can I do that?&lt;/p&gt;

&lt;p&gt;There are at least three ways I could think of:&lt;/p&gt;

&lt;p&gt;1️⃣ Writing a function inside the JS code to define a function, something like :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;await globalThis.webR.evalR("my_fun &amp;lt;- function(x){...}");

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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 😅&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ Well organized&lt;/li&gt;
&lt;li&gt;❌ Documented&lt;/li&gt;
&lt;li&gt;❌ Tested&lt;/li&gt;
&lt;li&gt;❌ Safely installable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;2️⃣ Simply create an R script and source it. Something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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')");

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s a bit better, we can at least organize our code in a script and it will be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Well organized (more or less)&lt;/li&gt;
&lt;li&gt;✅ Documented (more or less)&lt;/li&gt;
&lt;li&gt;❌ Tested&lt;/li&gt;
&lt;li&gt;❌ Safely installable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Well organized&lt;/li&gt;
&lt;li&gt;✅ Documented&lt;/li&gt;
&lt;li&gt;✅ Tested&lt;/li&gt;
&lt;li&gt;✅ Safely installable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Jeroen has written a &lt;a href="https://github.com/r-universe-org/build-wasm" rel="noopener noreferrer"&gt;Docker image&lt;/a&gt; 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).&lt;/p&gt;

&lt;p&gt;So here is what I’m thinking should be a well structured NodeJS / WebR app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Putting all the web stuff inside the NodeJS app, because, well, NodeJS is really good at doing that.&lt;/li&gt;
&lt;li&gt;Putting all the “business logic”, data-crunching, modeling stuff (and everything R is really good at) into an R package.&lt;/li&gt;
&lt;li&gt;load webR, write my R package to the webR file system, and &lt;code&gt;pkgload::load_all()&lt;/code&gt; it into webR.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That way, I can enjoy the best of both worlds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NodeJS is really good at doing web related things, and there are plenty of ways to test and deploy the code.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The how
&lt;/h2&gt;

&lt;p&gt;Let’s start by creating our project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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)"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s now create a simple function :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;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 &amp;lt;- function(species){
  dplyr::starwars |&amp;gt;
    filter(species == )
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now add &lt;code&gt;{dplyr}&lt;/code&gt; and &lt;code&gt;{pkgload}&lt;/code&gt; to our &lt;code&gt;DESCRIPTION&lt;/code&gt; (we’ll need &lt;code&gt;{pkgload}&lt;/code&gt; to &lt;code&gt;load_all()&lt;/code&gt; the package).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;usethis::use_package("dplyr")
usethis::use_package("pkgload")
devtools::document()

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have a package skeleton, we’ll have to upload it to webR.&lt;/p&gt;

&lt;p&gt;As described in the previous post, I’ve started a &lt;code&gt;webrtools&lt;/code&gt; NodeJS module, which will contains function to play with webR. Before this post, it had one function, &lt;code&gt;loadPackages&lt;/code&gt;, that was used to build a webR dependency library (see &lt;a href="https://dev.to/colinfay/preloading-your-r-packages-in-webr-in-an-express-js-api-klf-temp-slug-5561981"&gt;Preloading your R packages in webR in an Express JS API&lt;/a&gt; for more info).&lt;/p&gt;

&lt;p&gt;We’ll need to add two features :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install deps from DESCRIPTION (not just a package name), so a wrapper around the &lt;code&gt;Rscript ./node_modules/webrtools/r/install.R dplyr&lt;/code&gt; from before&lt;/li&gt;
&lt;li&gt;Copy the package folder in NodeJS, so a more generic version of &lt;code&gt;loadPackages&lt;/code&gt;, that can load any folder to the webR filesystem.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First, in R, we’ll need to read the &lt;code&gt;DESCRIPTION&lt;/code&gt; and build the lib:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;download_packs_and_deps_from_desc &amp;lt;- function (
  description,
  path_to_installation = "./webr_packages"
)
{
    if (!file.exists(description)) {
        stop("DESCRIPTION file not found")
    }
    deps &amp;lt;- desc::desc_get_deps(description)
    for (pak in deps$package) {
        webrtools::download_packs_and_deps(pak, path_to_installation = path_to_installation)
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: the code of &lt;code&gt;webrtools::download_packs_and_deps()&lt;/code&gt; is a wrapper around the R code described in &lt;a href="https://dev.to/preloading-your-r-packages-in-webr-in-an-express-js-api/"&gt;Preloading your R packages in webR in an Express JS API&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And in Node, we’ll rework our &lt;code&gt;loadPackages&lt;/code&gt; and split it into two functions — one to load into any folder, and one to load into the package library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function loadFolder(webR, dirPath, outputdir = "/usr/lib/R/library") {
  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");
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The end app
&lt;/h2&gt;

&lt;p&gt;We now have everything we need!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i webrtools@0.0.2
Rscript ./node_modules/webrtools/r/install_from_desc.R $(pwd)/rfuns/DESCRIPTION

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now, to our index.js&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const app = require('express')()
const path = require('path');
const { loadPackages, loadFolder } = require('webrtools');
const { WebR } = require('webr');

(async () =&amp;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', () =&amp;gt; {
    console.log('http://localhost:3000')
  })

})();

app.get('/', async (req, res) =&amp;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) =&amp;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);
  }
});

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s now try from another terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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":[]}]}]}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yeay 🎉 .&lt;/p&gt;

&lt;p&gt;You can find the code &lt;a href="https://github.com/ColinFay/webr-examples/tree/main/webr-preload-funs" rel="noopener noreferrer"&gt;here&lt;/a&gt;, and see it live at &lt;a href="https://srv.colinfay.me/webr-preload-funs/Rodian" rel="noopener noreferrer"&gt;srv.colinfay.me/webr-preload-funs/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can also try it with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run -it -p 3000:3000 colinfay/webr-preload-funs

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>rblogen</category>
    </item>
    <item>
      <title>Preloading your R packages in webR in an Express JS API</title>
      <dc:creator>Colin Fay</dc:creator>
      <pubDate>Mon, 04 Sep 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/colinfay/preloading-your-r-packages-in-webr-in-an-express-js-api-34ok</link>
      <guid>https://dev.to/colinfay/preloading-your-r-packages-in-webr-in-an-express-js-api-34ok</guid>
      <description>&lt;p&gt;This post is the third one of a series of post about webR:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/calling-webr-from-expressjs/"&gt;Using webR in an Express JS REST API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/old-faithful-express-bootstrap-webr/"&gt;The Old Faithful Geyser Data shiny app with webR, Bootstrap &amp;amp; ExpressJS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Preloading your R packages in webR in an Express JS API&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: the first post of this series explaining roughly what webR is, I won’t introduce it again here.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;tools::toTitleCase()&lt;/code&gt; in &lt;a href="https://srv.colinfay.me/express-webr-hello-world" rel="noopener noreferrer"&gt;express-webr-hello-world&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cut()&lt;/code&gt; &amp;amp; &lt;code&gt;faithful&lt;/code&gt; for &lt;a href="https://srv.colinfay.me/express-webr-old-faithful/" rel="noopener noreferrer"&gt;express-webr-old-faithful&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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 &lt;code&gt;webR&lt;/code&gt; to create a web applications or API without taking advantage of this extensive library of packages: R wouldn’t be R without its packages.&lt;/p&gt;

&lt;p&gt;Yet, bringing packages to &lt;code&gt;webR&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;Thankfully we don’t need to scratch our head too much anymore: the &lt;code&gt;webR&lt;/code&gt; team has precompiled more than 18000 (at the time of writing this post) packages that we can install by simply calling &lt;code&gt;webr::install()&lt;/code&gt; inside the &lt;code&gt;webR&lt;/code&gt; instance.&lt;/p&gt;

&lt;p&gt;That’s awesome, but there’s an issue with that: you can only install it from the &lt;code&gt;webR&lt;/code&gt; JavaScript object As far as I know, there is no way to download them beforehand and upload them to the &lt;code&gt;webR&lt;/code&gt; filesystem, as I reported in &lt;a href="https://github.com/r-wasm/webr/issues/260" rel="noopener noreferrer"&gt;this issue&lt;/a&gt;. (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).&lt;/p&gt;

&lt;p&gt;That means that every time the API is launched, &lt;code&gt;webR&lt;/code&gt; has to download and reinstall the packages, as shown by this simple example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const app = require('express')()
const { WebR } = require('webr');

(async () =&amp;gt; {
  globalThis.webR = new WebR();
  await globalThis.webR.init();
  await globalThis.webR.installPackages(['attempt'])
  app.listen(3000, '0.0.0.0', () =&amp;gt; {
    console.log('http://localhost:3000')
  })
})();

app.get('/', async (req, res) =&amp;gt; {
  res.send("hello")
});

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If I launch the app twice, the packages are downloaded twice 👇&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ 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

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach is definitely suboptimal: imagine having to reinstall packages every time you &lt;code&gt;source()&lt;/code&gt; an R script, instead of benefiting from pre-installed packages in your lib. That’s kind of what is happening right now in this &lt;code&gt;webR&lt;/code&gt; example.&lt;/p&gt;

&lt;p&gt;Sure, it works, but it means that:&lt;/p&gt;

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

&lt;p&gt;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 &lt;code&gt;webR&lt;/code&gt;, without having to re-download them every time. The rest of this blogpost explains this process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preloading R packages in my ExpressJS/webR API
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Building the R lib
&lt;/h3&gt;

&lt;p&gt;If you look at the internal of the folder on your computer that contains your packages (which is at &lt;code&gt;.libPaths()&lt;/code&gt;), you’ll see that it’s a series of folders with a bunch of files. Whenever you load R and call &lt;code&gt;library()&lt;/code&gt;, it looks there to load the code.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;{dplyr}&lt;/code&gt;, I need to find a way to download the pre-compiled version of &lt;code&gt;{dplyr}&lt;/code&gt; and all its dependencies from &lt;code&gt;https://repo.r-wasm.org/&lt;/code&gt;, and untar them into the folder I wanna use inside my project.&lt;/p&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;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 &lt;code&gt;install.packages("dplyr", lib="webr_packages")&lt;/code&gt;, R will only download the packages that are not already on my computer.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;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 &lt;a href="https://github.com/r-lib/withr/issues/122" rel="noopener noreferrer"&gt;&lt;code&gt;{withr}&lt;/code&gt; repo&lt;/a&gt; if you want to look into this, but basically the idea is that avoiding default lib is quite a challenge.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;That means that if I try to do something as simple as &lt;code&gt;install.packages("dplyr", lib="webr_packages", repos = "https://repo.r-wasm.org/")&lt;/code&gt;, it will only install &lt;code&gt;{dplyr}&lt;/code&gt;, given that all the other dependencies already exist on my machine :&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; setwd(tempdir())
&amp;gt; dir.create("webr_packages")
&amp;gt; install.packages("dplyr", lib="webr_packages", repos = "https://repo.r-wasm.org/")
# [...TRUNCATED]
* DONE (dplyr)
&amp;gt; list.files("webr_packages/")
[1] "dplyr"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I’ve tried the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Hacking &lt;code&gt;.lib.loc&lt;/code&gt; as suggest by Andrie &lt;a href="https://stackoverflow.com/a/36873741" rel="noopener noreferrer"&gt;here&lt;/a&gt;, 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.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Using &lt;code&gt;{renv}&lt;/code&gt; 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, &lt;code&gt;{renv}&lt;/code&gt; failed to parse the dep tree in a NodeJS project for some reasons.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/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 &amp;lt;- commandArgs(trailingOnly = TRUE)[1]
}

# If no second value, defaulting to webr_packages
if (is.na(commandArgs(trailingOnly = TRUE)[2])) {
  path_to_installation &amp;lt;- "./webr_packages"
} else {
  path_to_installation &amp;lt;- commandArgs(trailingOnly = TRUE)[2]
}

# Build the repos url
repos &amp;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 &amp;lt;- unique(
  unlist(
    use.names = FALSE,
    tools::package_dependencies(
      recursive = TRUE,
      pk_to_install
    )
  )
)

# Now the package list
pkg_deps &amp;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 &amp;lt;- list.files(
  path_to_installation
)

# Getting the list of available package
info &amp;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 &amp;lt;- paste0(pkg, "_", info[info[, "Package"] == pkg, "Version"], ".tgz")
  # Location of file on the repo
  url &amp;lt;- paste0(repos, "/", targz_file)
  # Download the file and untar the archive on the local lib
  tmp &amp;lt;- tempfile(fileext = ".tgz")
  download.file(url, destfile = tmp)
  untar(tmp, exdir = path_to_installation)
  unlink(tmp)
}

message("Done")

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now, let’s try this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Woot woot, seems to work 🎉.&lt;/p&gt;

&lt;h3&gt;
  
  
  Moving everything to the webR lib when the API is launched
&lt;/h3&gt;

&lt;p&gt;And now, challenge number two: rebuilding this library in the &lt;code&gt;webR&lt;/code&gt; filesystem.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Create the package folders/subfolders&lt;/li&gt;
&lt;li&gt;Copy the files from the local machine (contained in a &lt;code&gt;./webr_packages&lt;/code&gt; folder) into the &lt;code&gt;webR&lt;/code&gt; filesystem when launching the Node API.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Creating the directory tree
&lt;/h4&gt;

&lt;p&gt;Creating a directory tree is not an easy task — it implies recursive function, keeping track of parents, and other weird things. R has &lt;code&gt;list.files(recursive = TRUE)&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;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:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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;
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is what it looks like on our &lt;code&gt;./webr_packages&lt;/code&gt; folder from before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;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' }
]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;🚀 Yeay 🚀&lt;/p&gt;

&lt;p&gt;Now I have a JavaScript object I can use to move my &lt;code&gt;./webr_packages&lt;/code&gt; library to a &lt;code&gt;webR&lt;/code&gt; instance library.&lt;/p&gt;

&lt;p&gt;Let’s write a function for that (no need for chatGPT here 😅):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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
      );
    }
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have our whole process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;downloading the precompiled packages&lt;/li&gt;
&lt;li&gt;listing the dir tree in Node&lt;/li&gt;
&lt;li&gt;bringing everything into the &lt;code&gt;webR&lt;/code&gt; filesystem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🚀 Yeay (bis) 🚀&lt;/p&gt;

&lt;p&gt;To make things simple, I have put these into a node package, at &lt;a href="https://www.npmjs.com/package/webrtools" rel="noopener noreferrer"&gt;npmjs.com/package/webrtools&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bringing everything together
&lt;/h3&gt;

&lt;p&gt;Workflow for building the app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const app = require('express')()
const path = require('path');
const { loadPackages } = require('webrtools');
const { WebR } = require('webr');

(async () =&amp;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', () =&amp;gt; {
    console.log('http://localhost:3000')
  })
})();

app.get('/', async (req, res) =&amp;gt; {
  let result = await globalThis.webR.evalR('sample_n(mtcars, 10)');
  let output = await result.toJs();
  res.send(output.values)
});


node index.js

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in another console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ 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]}]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🚀 Yeay (ter) 🚀&lt;/p&gt;

&lt;h3&gt;
  
  
  And now for the Dockerfile
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM r-base:4.3.1

# Create app directory
WORKDIR /usr/src/app

RUN apt update &amp;amp;&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"]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can find the code &lt;a href="https://github.com/ColinFay/webr-examples/tree/main/webr-preloadr" rel="noopener noreferrer"&gt;here&lt;/a&gt;, and see it live at &lt;a href="https://srv.colinfay.me/webr-preloadr/" rel="noopener noreferrer"&gt;srv.colinfay.me/webr-preloadr&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can also try it with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run -it -p 3000:3000 colinfay/webr-preloadr

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Final notes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Should I use it in prod?
&lt;/h3&gt;

&lt;p&gt;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 &lt;code&gt;webrtools&lt;/code&gt; node module is now prod ready. Who knows!&lt;/p&gt;

&lt;h3&gt;
  
  
  Further work
&lt;/h3&gt;

&lt;p&gt;After some discussion on the issue on the &lt;code&gt;webR&lt;/code&gt; repo, it seems that a promising path to do what I’d like to do is to use the &lt;a href="https://emscripten.org/docs/api_reference/Filesystem-API.html#FS.mount" rel="noopener noreferrer"&gt;&lt;code&gt;FS.mount&lt;/code&gt; interface from WASM&lt;/a&gt;, but it needs to be expose by &lt;code&gt;webR&lt;/code&gt;. Keep an eye on the repo!&lt;/p&gt;

&lt;p&gt;Finally, I’m a bit displeased with writing the installR fun in R and having to install R in the &lt;code&gt;Dockerfile&lt;/code&gt; to do it, so I should probably re-write it in full NodeJS. We could read the package tree in R with &lt;code&gt;webR&lt;/code&gt; and then download/untar with Node.&lt;/p&gt;

&lt;p&gt;Food for thoughts.&lt;/p&gt;

</description>
      <category>rblogen</category>
    </item>
    <item>
      <title>The Old Faithful Geyser Data shiny app with webR, Bootstrap &amp; ExpressJS</title>
      <dc:creator>Colin Fay</dc:creator>
      <pubDate>Tue, 25 Jul 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/colinfay/the-old-faithful-geyser-data-shiny-app-with-webr-bootstrap-expressjs-48cc</link>
      <guid>https://dev.to/colinfay/the-old-faithful-geyser-data-shiny-app-with-webr-bootstrap-expressjs-48cc</guid>
      <description>&lt;p&gt;This post is the second one of a series of post about webR:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://colinfay.me/calling-webr-from-expressjs/" rel="noopener noreferrer"&gt;Using webR in an Express JS REST API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The Old Faithful Geyser Data shiny app with webR, Bootstrap &amp;amp; ExpressJS&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: the first post of this series explaining roughly what webR is, I won’t introduce it again here.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;h2&gt;
  
  
  Introductory notes
&lt;/h2&gt;

&lt;p&gt;Before starting, here are two notes regarding the app built in this post:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;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…).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;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.&lt;a href="https://dev.to/hrbrmstr/the-road-to-ggplot2-in-webr-part-1-the-road-is-paved-with-good-base-r-plots-1e98-temp-slug-3061593"&gt;boB has a great blogpost&lt;/a&gt; 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 &lt;code&gt;cut()&lt;/code&gt; function from R) and draw with &lt;a href="https://www.chartjs.org/" rel="noopener noreferrer"&gt;chart.js&lt;/a&gt;, instead of trying to display the “not so user friendly” base plot.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Project init
&lt;/h2&gt;

&lt;p&gt;Let’s start by creating a new Express app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Server
&lt;/h2&gt;

&lt;p&gt;Let’s move to the server side first (&lt;code&gt;index.js&lt;/code&gt; ). We’ll start by taking the file from the previous blog post, and modify it to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;serve index.html on &lt;code&gt;/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;create a route that returns the data of the bins for our histogram&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s start with our code to init &lt;code&gt;webR&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;'use strict';
const express = require("express")
// For serving the html
const path = require("path")
const app = express()
const { WebR } = require('@r-wasm/webr');

(async () =&amp;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 &amp;lt;- faithful[, 2]')
  console.log("webR is ready");
  app.listen(3000, '0.0.0.0', () =&amp;gt; {
    console.log('http://localhost:3000')
  })
})();

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, an endpoint that serves &lt;code&gt;index.html&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app.get("/", (req, res) =&amp;gt; {
  res.sendFile(path.join(__dirname, "index.html"))
})

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And an endpoint that sends the bins for our plot, using ExpressJS parameter notation (&lt;code&gt;path/:n&lt;/code&gt;). The value is sent to webR via the &lt;code&gt;env&lt;/code&gt; option of &lt;code&gt;evalR&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app.get("/hist-data/:n", async (req, res) =&amp;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)
})

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that our backend is ready, let’s check that we can now call it from the command line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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]}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Front
&lt;/h2&gt;

&lt;p&gt;Now, time to build the front.&lt;/p&gt;

&lt;p&gt;We’ll start with the Boostrap boilerplate from &lt;a href="https://getbootstrap.com/docs/5.3/getting-started/introduction/#quick-start" rel="noopener noreferrer"&gt;https://getbootstrap.com/docs/5.3/getting-started/introduction/#quick-start&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;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;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!doctype html&amp;gt;
&amp;lt;html lang="en"&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset="utf-8"&amp;gt;
    &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1"&amp;gt;
    &amp;lt;title&amp;gt;Bootstrap demo&amp;lt;/title&amp;gt;
    &amp;lt;link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous"&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;Hello, world!&amp;lt;/h1&amp;gt;
    &amp;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"&amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s change the title, and add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the range input&lt;/li&gt;
&lt;li&gt;a div to receive the chart
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Let’s start by adding the lib with &lt;code&gt;&amp;lt;script src="https://cdn.jsdelivr.net/npm/chart.js"&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We then need some JavaScript to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Initiate the chart at launch
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// https://colinfay.me/api-from-client-shiny/
// Default to 10 bins
fetch("hist-data/10")
      .then((data) =&amp;gt;{
        // Convert the data to json and
        // create a chart
        data.json().then((res) =&amp;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) =&amp;gt; {
          alert("Error catchin result from R")
        })
      })
      .catch((error) =&amp;gt; {
        alert("Error catchin result from R")
      })

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Update the chart when the slider is moved
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    const update = function (n = 10) {
      fetch(`hist-data/${n}`).then((data) =&amp;gt; {
        data.json().then((res) =&amp;gt; {
          globalThis.chart.data.labels = res.names;
          globalThis.chart.data.datasets.forEach(dataset =&amp;gt; {
            dataset.data = res.values;
          })
          globalThis.chart.update();
        })
      })
      document.querySelector('#bins').innerHTML = `Selected: ${n}`;

    }
document.querySelector('#customRange1').addEventListener(
  'change',
  function() { update(this.value); }
 );

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;You can also try it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run -it -p 3000:3000 colinfay/express-webr-old-faithful

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>r</category>
      <category>webr</category>
      <category>webassembly</category>
    </item>
    <item>
      <title>Using webR in an Express JS REST API</title>
      <dc:creator>Colin Fay</dc:creator>
      <pubDate>Tue, 04 Jul 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/colinfay/using-webr-in-an-express-js-rest-api-2j0i</link>
      <guid>https://dev.to/colinfay/using-webr-in-an-express-js-rest-api-2j0i</guid>
      <description>&lt;h2&gt;
  
  
  webR? wat again?
&lt;/h2&gt;

&lt;p&gt;As described in the &lt;a href="https://docs.r-wasm.org/webr/latest/" rel="noopener noreferrer"&gt;doc&lt;/a&gt;:&lt;/p&gt;

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

&lt;p&gt;In this post, I won’t go into details into what &lt;code&gt;webR&lt;/code&gt; is, as if you want to know more, you’ll probably get a better understanding by going through the documentation.&lt;/p&gt;

&lt;p&gt;But if like a normal person you don’t read the doc, let’s sum up what &lt;code&gt;webR&lt;/code&gt; is. Simply put, &lt;code&gt;WebAssembly&lt;/code&gt; (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.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;WASM is a way to use non-Javascript code and run it in your browser.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I also suggest reading the blogposts by &lt;a href="https://rud.is/b/" rel="noopener noreferrer"&gt;boB&lt;/a&gt; on the subject.&lt;/p&gt;

&lt;p&gt;The rest of this post assume that you understand what wasm &amp;amp; &lt;code&gt;webR&lt;/code&gt; are.&lt;/p&gt;

&lt;h2&gt;
  
  
  Playing with webR
&lt;/h2&gt;

&lt;p&gt;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 😅&lt;/p&gt;

&lt;p&gt;Today’s story is about exploring how to build tools in JavaScript that will call &lt;code&gt;webR&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hello World! - Creating an API where we call webR
&lt;/h2&gt;

&lt;p&gt;I’ll start with a simple &lt;a href="https://expressjs.com/" rel="noopener noreferrer"&gt;Express JS&lt;/a&gt; REST API that uses webR to run R code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 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

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’ll now install the webR nodejs package&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i @r-wasm/webr

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now, let’s move to our index.js file. We’ll start with the basics of Express JS: creating the &lt;code&gt;app&lt;/code&gt; object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Creating the express app
const app = require('express')()

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we’ll import &lt;code&gt;WebR&lt;/code&gt; from &lt;code&gt;@r-wasm/webr&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// importing the `WebR` class inside the runtime
const { WebR } = require('@r-wasm/webr');

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’ll now be initiating the webR session by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;creating an instance of webR&lt;/li&gt;
&lt;li&gt;binding it to &lt;code&gt;globalThis&lt;/code&gt; so that it’s available everywhere&lt;/li&gt;
&lt;li&gt;calling the &lt;code&gt;init&lt;/code&gt; method&lt;/li&gt;
&lt;li&gt;Once &lt;code&gt;webR&lt;/code&gt; is ready, we’ll launch the &lt;code&gt;app&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As webR communicates via the worker thread asynchronously, we need to use &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(async () =&amp;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, () =&amp;gt; {
    console.log('http://localhost:3000')
  })
})();

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we add a route that returns an hello world:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Creating a route for the express app
// that will return a titleCase hello world from r
app.get('/', async (req, res) =&amp;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)
});

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now, let’s run our app!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node index.js

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can find the code &lt;a href="https://github.com/ColinFay/webr-examples/tree/main/express-webr-hello-world" rel="noopener noreferrer"&gt;here&lt;/a&gt;, and see it live at &lt;a href="https://srv.colinfay.me/express-webr-hello-world" rel="noopener noreferrer"&gt;srv.colinfay.me/express-webr-hello-world&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can also try it with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run -it -p 3000:3000 colinfay/express-webr-hello-world

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>r</category>
      <category>webr</category>
      <category>webassembly</category>
    </item>
    <item>
      <title>The ‘Engineering Production-Grade Shiny Apps’ book is available in print!</title>
      <dc:creator>Colin Fay</dc:creator>
      <pubDate>Mon, 04 Oct 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/colinfay/the-engineering-production-grade-shiny-apps-book-is-available-in-print-abe</link>
      <guid>https://dev.to/colinfay/the-engineering-production-grade-shiny-apps-book-is-available-in-print-abe</guid>
      <description>&lt;p&gt;I’m so, so excited to announce that the &lt;em&gt;‘Engineering Production-Grade Shiny Apps’&lt;/em&gt; book is now available in print! This book is the result of two years of work, and I couldn’t be more happy to see it available in print.&lt;/p&gt;

&lt;p&gt;As its title suggests, &lt;em&gt;‘Engineering Production-Grade Shiny Apps’&lt;/em&gt; is a book about building &lt;code&gt;{shiny}&lt;/code&gt; apps that will be sent to production. It goes deep into &lt;code&gt;{golem}&lt;/code&gt;, of course, but it’s not a book about &lt;code&gt;{golem}&lt;/code&gt; only — it tries to cover a large panel of topics, and I’m pretty sure that if you are a &lt;code&gt;{shiny}&lt;/code&gt; developer, there is something for you in this book: project management, code organization, team work, best practices for front end development, CI and CD, deployment, code optimization…&lt;/p&gt;

&lt;p&gt;This book will remain available online for free at &lt;a href="https://engineering-shiny.org/" rel="noopener noreferrer"&gt;https://engineering-shiny.org/&lt;/a&gt; (and I want to thank Chapman and Hall/CRC for this), but if you want to grab a copy, you can go on &lt;a href="https://www.routledge.com/Engineering-Production-Grade-Shiny-Apps/Fay-Rochette-Guyader-Girard/p/book/9780367466022" rel="noopener noreferrer"&gt;routledge.com&lt;/a&gt; and order one. I know it can feel counter-intuitive to buy a print copy of a book which is available online, but if you want this to continue to happen, please continue to support the publisher by buying a copy of this book, or of any other from Chapman and Hall/CRC — they have been doing a massive work when it comes to organizing, editing and reviewing.&lt;/p&gt;

&lt;p&gt;I want to personally thank everybody that has been contributing to this book, with some special thanks to &lt;a href="https://twitter.com/theRcast" rel="noopener noreferrer"&gt;Eric Nantz&lt;/a&gt; which has been a &lt;code&gt;{golem}&lt;/code&gt; early adapter and an all time supporter of everything from the &lt;code&gt;golemverse&lt;/code&gt;, to &lt;a href="https://twitter.com/divadnojnarg" rel="noopener noreferrer"&gt;David Granjon&lt;/a&gt; for all his feedback on the book, and to &lt;a href="https://twitter.com/chrisderv" rel="noopener noreferrer"&gt;Christophe Dervieux&lt;/a&gt; for his precious help during my battles with Markdown. Thanks also to &lt;a href="https://twitter.com/crcgrubbsd" rel="noopener noreferrer"&gt;David Grubbs&lt;/a&gt; from Chapman and Hall/CRC for making this happen.&lt;/p&gt;

&lt;p&gt;That being said, I still can’t believe I’ve added my name to the list of authors in “The R Series” books 😱&lt;/p&gt;

&lt;p&gt;I hope you’ll enjoy it!&lt;/p&gt;

</description>
      <category>rstats</category>
    </item>
    <item>
      <title>Multi-page {shiny} Applications with {brochure}</title>
      <dc:creator>Colin Fay</dc:creator>
      <pubDate>Thu, 18 Feb 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/colinfay/multi-page-shiny-applications-with-brochure-5an7</link>
      <guid>https://dev.to/colinfay/multi-page-shiny-applications-with-brochure-5an7</guid>
      <description>&lt;p&gt;[Disclaimer] The package &lt;strong&gt;presented in this blog post is still experimental&lt;/strong&gt; at the time of writing these lines (2021-02-18), so it might face some API changes in the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-page in {shiny}, and random thoughts about designing web applications
&lt;/h2&gt;

&lt;p&gt;Something that has been bugging me for a while is the inability to really share a specific part of a &lt;code&gt;{shiny}&lt;/code&gt; dashboard, at least natively, using a specific path. In other words, I’ve always wanted to be able to do something like &lt;code&gt;my-uberapp.io/contact&lt;/code&gt; to share with someone a specific part of the app. And only this part.&lt;/p&gt;

&lt;p&gt;Another need I was facing, probably born out of building web applications using NodeJS, is a &lt;strong&gt;natural way to manipulate endpoints, http requests and responses&lt;/strong&gt; , so that I could do something pretty common in web application: a home page, and next to it a login page that verifies your identity, and redirects you to another page after setting a cookie. That of course, leads to the necessity of having the &lt;strong&gt;ability to define various endpoints with a specific behavior for each&lt;/strong&gt; : &lt;strong&gt;a unique UI, and a 18 server response&lt;/strong&gt;. Pure, native, multi-endpoint applications, not one big ball of UI with parts hidden using JavaScript &amp;amp; CSS, and a global server function that might launch computation you don’t need for your page.&lt;/p&gt;

&lt;p&gt;A few weeks back, I’ve decided to focus on this question. Multi-page in&lt;code&gt;{shiny}&lt;/code&gt; is not new: I’ve found both&lt;a href="https://appsilon.com/shiny-router-020/" rel="noopener noreferrer"&gt;&lt;code&gt;{shiny.router}&lt;/code&gt;&lt;/a&gt;, and&lt;a href="https://github.com/nteetor/blaze" rel="noopener noreferrer"&gt;&lt;code&gt;{blaze}&lt;/code&gt;&lt;/a&gt; that already implement a form of “multi-page”.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: both these packages work perfectly, and they definitely do what they are designed for. There are the product of tremendous work with smart implementations and my goal is in no way to undermine these packages. They just don’t answer the need I was having, hence my new approach to this question. &lt;code&gt;{brochure}&lt;/code&gt; will answer the needs for more large and complex applications built with &lt;code&gt;{shiny}&lt;/code&gt;, while both the&lt;code&gt;{shiny.router}&lt;/code&gt; &amp;amp; &lt;code&gt;{blaze}&lt;/code&gt; should be easier to get started with.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As far as I saw it, these two packages didn’t answer what I was looking for: “real” multi-page. Both &lt;code&gt;{shiny.router}&lt;/code&gt; &amp;amp; &lt;code&gt;{blaze}&lt;/code&gt; &lt;strong&gt;still produce a form of Single Page Application&lt;/strong&gt; , but plays with the URL to make you feel like you’re on a multi-page application.&lt;/p&gt;

&lt;p&gt;For example, &lt;code&gt;{shiny.router}&lt;/code&gt; hides the parts of the app via JS &amp;amp; CSS – if you browse the source code, you’ll still see the hidden HTML. That also implies that they are potential conflicts between ids: you have to be sure that all your pages have unique id on their ouputs/inputs, otherwise the app doesn’t render, meaning that &lt;strong&gt;a “page” doesn’t have its own server function&lt;/strong&gt;. And of course, if you look at the &lt;code&gt;Network&lt;/code&gt;tab of your browser developer tool, you’ll see that there is no new&lt;code&gt;GET&lt;/code&gt; request made whenever a new “page” is loaded: this new page is still the same application, with parts hidden through JavaScript.&lt;/p&gt;

&lt;p&gt;Hiding UI parts is a practice that has always been bothering me in term of performance – on a large &lt;code&gt;{shinydashboard}&lt;/code&gt;, for example, &lt;strong&gt;you’ll be transferring the full HTML, CSS and JavaScript for the whole application when the app launches, even if the user never visits all the tabs&lt;/strong&gt;.&lt;code&gt;{shiny}&lt;/code&gt; is very smart when it comes to transferring CSS and JS files: for example, it will only serve the external resources for the&lt;code&gt;sliderInput()&lt;/code&gt; if there is one in the app. But if you’ve got a&lt;code&gt;{shinydashboard}&lt;/code&gt; dashboard with 19 pages, and the &lt;code&gt;sliderInput()&lt;/code&gt; is on page 19 and will only be seen by 0.1% of the visitors, it will still be transferred to every user, regardless of whether or not they need it. This might result in lowering the performance of the app, as it might be raising the time to ‘First Meaningful Paint’, and of course it is a waste of resources, especially if your application is served to people with low bandwidth, and/or browsing your app using cellular data. I know this is a question you’ve probably never asked yourself before ;) - but &lt;strong&gt;performance is a pretty common question in the web development world, and something to be aware if you’re serving your app at scale&lt;/strong&gt;. If this is something you’re interested in, I suggest reading &lt;a href="https://web.dev/why-speed-matters/" rel="noopener noreferrer"&gt;Why does speed matter?&lt;/a&gt; from the Google dev center, and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Performance" rel="noopener noreferrer"&gt;Web Performance&lt;/a&gt;from Mozilla Web Docs. See also &lt;a href="https://engineering-shiny.org/need-for-optimization.html#b.-shiny-front-end" rel="noopener noreferrer"&gt;14.2.2 Profiling {shiny}&lt;/a&gt;from the &lt;em&gt;Engineering Shiny&lt;/em&gt; book.&lt;/p&gt;

&lt;p&gt;And, another final thing I wanted is a way to “flush” objects when you change page: in the current implementation of &lt;code&gt;{shinydashboard}&lt;/code&gt;, if you create something on &lt;code&gt;tab1&lt;/code&gt;, it will still be there in &lt;code&gt;tab2&lt;/code&gt;, taking some space in the RAM, even if you don’t need it. It’s rather convenient because you don’t have to think about these things: you start a&lt;code&gt;{shiny}&lt;/code&gt; session, and all the objects will still be there as long as the session lives. But that also means that as soon as the session stops, there is no way to get the values back as they only live in the RAM, inside the session.&lt;/p&gt;

&lt;p&gt;On the other hand, with an implementation where every page gets its own&lt;code&gt;{shiny}&lt;/code&gt; session, you need to find a way to identify the user with a form of &lt;code&gt;id&lt;/code&gt;, save the relevant values (potentially write in a DB), then fetch these values again using the &lt;code&gt;id&lt;/code&gt; stored in the browser (typically, you have a session cookie in the browser that is used as a key to search the DB). &lt;strong&gt;A process forcing you to think more carefully about the data flow of your app&lt;/strong&gt;. What that also means is that as the&lt;code&gt;id&lt;/code&gt; is a cookie in the browser, whenever the app crashes, it will be able to reload to the exact same place as long as the cookie is still in the browser (well, not exactly but you understand the idea).&lt;/p&gt;

&lt;p&gt;So, back to our first topic – what was I looking for? My goal was to find a way to build &lt;code&gt;{shiny}&lt;/code&gt; applications that are &lt;strong&gt;natively&lt;/strong&gt; multi-page. In other words, an application that &lt;em&gt;answers&lt;/em&gt; to a request on &lt;code&gt;/page2&lt;/code&gt;, not an app that silently redirect to &lt;code&gt;/&lt;/code&gt;, nor an application that requires playing with &lt;code&gt;/#!/page2&lt;/code&gt; in the url. (Reminder, &lt;code&gt;#&lt;/code&gt; is traditionally used as an anchor tag in a web page). And, of course, &lt;strong&gt;I wanted each page to have its own shiny session, its own UI and server functions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I was also looking for a way to manipulate both the &lt;code&gt;GET&lt;/code&gt; request from the server and the &lt;code&gt;httpResponse&lt;/code&gt; that is sent back, for example in order to &lt;strong&gt;set &lt;code&gt;httpOnly&lt;/code&gt; cookies in the header of the HTTP Response or change the HTTP status code&lt;/strong&gt; , the same way you can manipulate these when building an application with NodeJS for example. This is something that you’d find in the&lt;a href="https://github.com/JohnCoene/ambiorix" rel="noopener noreferrer"&gt;&lt;code&gt;{ambiorix}&lt;/code&gt;&lt;/a&gt; project by the amazing &lt;a href="https://john-coene.com/" rel="noopener noreferrer"&gt;John Coene&lt;/a&gt; (who will get some SEO juice thanks to this backlink). It’s an amazing approach that definitely resonated with me as I’ve been a NodeJS &amp;amp; &lt;code&gt;express.js&lt;/code&gt; user for some time now, but that’s still far from the way you’d build things in&lt;code&gt;{shiny}&lt;/code&gt;, and will probably require a lot of coding to get an application up and running (which is good, because I love coding, but let’s use our &lt;code&gt;{shiny}&lt;/code&gt; knowledge for now :) ).&lt;/p&gt;

&lt;p&gt;To sum up, I wanted a way to get closer to how you build applications in other languages, but will as little deviation as possible from the&lt;code&gt;{shiny}&lt;/code&gt; way of building apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Here comes &lt;code&gt;{brochure}&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;As said on the top of the article, this package is still a work in progress, so you might see some changes in the near future :)&lt;/p&gt;

&lt;p&gt;[Disclaimer, again] The way you will build applications with&lt;code&gt;{brochure}&lt;/code&gt; is different from the way you usually build &lt;code&gt;{shiny}&lt;/code&gt; apps, as we no longer operate under the single page app paradigm. Please keep this in mind and everything should be fine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;You can install the development version of &lt;code&gt;{brochure}&lt;/code&gt; with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;remotes::install_github("ColinFay/brochure")

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Minimal &lt;code&gt;{brochure}&lt;/code&gt; App
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;library(shiny)
library(brochure)


## 
## Attaching package: 'brochure'

## The following object is masked from 'package:utils':
## 
## page

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;code&gt;page()&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;A &lt;code&gt;brochureApp&lt;/code&gt; is a series of &lt;code&gt;page&lt;/code&gt;s that are defined by an &lt;code&gt;href&lt;/code&gt;(the path/endpoint where the page is available), a UI and a &lt;code&gt;server&lt;/code&gt;function. This is conceptually important: &lt;strong&gt;each page has its own shiny session, its own UI, and its own server&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;brochureApp(
  # First page
  page(
    href = "/",
    ui = fluidPage(
      h1("This is my first page"), 
      plotOutput("plot")
    ),
    server = function(input, output, session){
      output$plot &amp;lt;- renderPlot({
        plot(iris)
      })
    }
  ), 
  # Second page, without any server-side function
  page(
    href = "/page2", 
    ui = fluidPage(
      h1("This is my second page"), 
      tags$p("There is no server function in this one")
    )
  )
)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;-&amp;gt; In your browser, you can now navigate to &lt;code&gt;/&lt;/code&gt;, and to &lt;code&gt;/page2&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;redirect()&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Redirections can be used to redirect from one endpoint to the other:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;brochureApp(
  page(
    href = "/",
    ui = tagList(
      h1("This is my first page")
    )
  ),
  redirect(
    from = "/nothere",
    to = "/"
  ),
  redirect(
    from = "/colinfay",
    to = "https://colinfay.me"
  )
)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;-&amp;gt; In your browser, if you got to &lt;code&gt;/nothere&lt;/code&gt;, and you’ll be redirected to &lt;code&gt;/&lt;/code&gt; .&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;req_handlers&lt;/code&gt; &amp;amp; &lt;code&gt;res_handlers&lt;/code&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Sorry what?
&lt;/h3&gt;

&lt;p&gt;This is where things get more interesting.&lt;/p&gt;

&lt;p&gt;Each page, and the global app, have a &lt;code&gt;req_handlers&lt;/code&gt; and &lt;code&gt;res_handlers&lt;/code&gt;parameters, that can take a &lt;strong&gt;list of functions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;An &lt;code&gt;*_handler&lt;/code&gt; is a function that takes as parameter(s):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For &lt;code&gt;req_handlers&lt;/code&gt;, &lt;code&gt;req&lt;/code&gt;, which is the request object (see below for when these objects are created). For example &lt;code&gt;function(req){&lt;br&gt;
print(req$PATH_INFO); return(req)}&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For &lt;code&gt;res_handlers&lt;/code&gt; , &lt;code&gt;res&lt;/code&gt;, the response object, &amp;amp; &lt;code&gt;req&lt;/code&gt; , the request object. For example &lt;code&gt;function(res, req){ print(res$content);&lt;br&gt;
return(res)}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;req_handlers&lt;/code&gt; &lt;strong&gt;must&lt;/strong&gt; return &lt;code&gt;req&lt;/code&gt; &amp;amp; &lt;code&gt;res_handlers&lt;/code&gt; &lt;strong&gt;must&lt;/strong&gt; return&lt;code&gt;res&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;They can be used to register log, or to modify the objects, or any kind of things you can think of. If you are familiar with &lt;code&gt;express.js&lt;/code&gt;, &lt;strong&gt;you can think of &lt;code&gt;req_handlers&lt;/code&gt; as what&lt;/strong&gt; &lt;code&gt;express.js&lt;/code&gt; &lt;strong&gt;calls “middleware”&lt;/strong&gt;. These functions are run when R is building the HTTP response to send to the browser (i.e, no server code has been run yet), following this process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;R receives a &lt;code&gt;GET&lt;/code&gt; request from the browser, creating a request object, called &lt;code&gt;req&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;req_handlers&lt;/code&gt; are run using this &lt;code&gt;req&lt;/code&gt; object, potentially modifying it&lt;/li&gt;
&lt;li&gt;R creates an &lt;code&gt;httpResponse&lt;/code&gt;, using this &lt;code&gt;req&lt;/code&gt; and how you defined the UI&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;res_handlers&lt;/code&gt; are run on this &lt;code&gt;httpResponse&lt;/code&gt; (first app level&lt;code&gt;res_handlers&lt;/code&gt;, then page level &lt;code&gt;res_handlers&lt;/code&gt;), , potentially modifying it&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;httpResponse&lt;/code&gt; is sent back to the browser&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Note that if any &lt;code&gt;req_handlers&lt;/code&gt; returns an &lt;code&gt;httpResponse&lt;/code&gt; object, it will be returned to the browser immediately, without any further computation. This early &lt;code&gt;httpResponse&lt;/code&gt; will not be passed to the&lt;code&gt;res_handlers&lt;/code&gt; of the app or the page. This process can for example be used to send custom &lt;code&gt;httpResponse&lt;/code&gt;, as shown below with the&lt;code&gt;healthcheck&lt;/code&gt; endpoint.&lt;/p&gt;

&lt;p&gt;You can use formulas inside your handlers. &lt;code&gt;.x&lt;/code&gt; and &lt;code&gt;..1&lt;/code&gt; will be &lt;code&gt;req&lt;/code&gt;for &lt;code&gt;req_handlers&lt;/code&gt;, &lt;code&gt;.x&lt;/code&gt; and &lt;code&gt;..1&lt;/code&gt; will be &lt;code&gt;res&lt;/code&gt; &amp;amp; &lt;code&gt;.y&lt;/code&gt; and &lt;code&gt;..2&lt;/code&gt; will be &lt;code&gt;req&lt;/code&gt; for &lt;code&gt;res_handlers&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Design pattern side-note: you’d probably want to define the handlers outside of the app, for better code organization (as with &lt;code&gt;log_where&lt;/code&gt;below).&lt;/p&gt;
&lt;h3&gt;
  
  
  Example: Logging with &lt;code&gt;req_handlers()&lt;/code&gt;, and building a healthcheck point
&lt;/h3&gt;

&lt;p&gt;In this app, we’ll log to the console every page and the time it is called, using the &lt;code&gt;log_where()&lt;/code&gt; function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;log_where &amp;lt;- function(req){
  cli::cat_rule(
    sprintf(
      "%s - %s", 
      Sys.time(), 
      req$PATH_INFO
    )
  )
  req
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the sake of organization, we’ll create functions that return pages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nav_links &amp;lt;- tags$ul(
  tags$li(
    tags$a(href = "/", "home"), 
  ),
  tags$li(
    tags$a(href = "/page2", "page2"), 
  ),
  tags$li(
    tags$a(href = "/contact", "contact"), 
  )
)

page_1 &amp;lt;- function(){
  page(
    href = "/",
    ui = function(request){
      tagList(
        h1("This is my first page"),
        nav_links,
        plotOutput("plot")
      )
    },
    server = function(input, output, session){
      output$plot &amp;lt;- renderPlot({
        plot(mtcars)
      })
    }
  )
}

page_2 &amp;lt;- function(){
  page(
    href = "/page2",
    ui = function(request){
      tagList(
        h1("This is my second page"),
        nav_links,
        plotOutput("plot")
      )
    }, 
    server = function(input, output, session){
      output$plot &amp;lt;- renderPlot({
        plot(mtcars)
      })
    }
  )
}

page_contact &amp;lt;- function(){
  page(
    href = "/contact",
    ui = tagList(
      h1("Contact us"),
      nav_links,
      tags$ul(
        tags$li("Here"),
        tags$li("There")
      )
    )
  )
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’ll also build an &lt;code&gt;healthcheck&lt;/code&gt; endpoint that simply returns a&lt;code&gt;httpResponse&lt;/code&gt; with the 200 HTTP code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Reusing the pages from before
brochureApp(
  req_handlers = list(
    log_where
  ),
  # Pages
  page_1(),
  page_2(),
  page_contact(),
  page(
    href = "/healthcheck",
    # As this is a pure backend exchange, 
    # We don't need a UI
    ui = tagList(), 
    # As this req_handler returns an httpResponse,
    # This response will be returned directly to the browser, 
    # without passing through the usual dance
    req_handlers = list(
      # If you have shiny &amp;lt; 1.6.0, you'll need to 
      # do shiny:::httpResponse (triple `:`) 
      # as it is not exported until 1.6.0.
      # Otherwise, see ?shiny::httpResponse
      ~ shiny::httpResponse( 200, content = "OK")
    )
  )
)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you navigate to each page, you’ll see this in the console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Listening on http://127.0.0.1:4879
── 2021-02-17 21:52:16 - / ──────────────────────────────
── 2021-02-17 21:52:17 - /page2 ─────────────────────────
── 2021-02-17 21:52:19 - /contact ───────────────────────

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you go to another R session, you can check that you’ve got a 200 on&lt;code&gt;healthcheck&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; httr::GET("http://127.0.0.1:4879/healthcheck")
Response [http://127.0.0.1:4879/healthcheck]
  Date: 2021-02-17 21:55
  Status: 200
  Content-Type: text/html; charset=UTF-8
  Size: 2 B

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Handling cookies using &lt;code&gt;res_handlers&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;res_handlers&lt;/code&gt; can be used to set cookies, by adding a &lt;code&gt;Set-Cookie&lt;/code&gt;header.&lt;/p&gt;

&lt;p&gt;Note that you can parse the cookie using &lt;code&gt;parse_cookie_string&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;brochure::parse_cookie_string( "a=12;session=blabla" )


## a session 
## "12" "blabla"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the example, we’ll also use &lt;code&gt;brochure::server_redirect("/")&lt;/code&gt;, from the server-side to redirect the user after login.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Creating a navlink
nav_links &amp;lt;- tags$ul(
  tags$li(
    tags$a(href = "/", "home"), 
  ),
  tags$li(
    tags$a(href = "/login", "login"), 
  ),
  tags$li(
    tags$a(href = "/logout", "logout"), 
  )
)

home &amp;lt;- function(){
  page(
    href = "/",
    ui = tagList(
      h1("This is my first page"), 
      tags$p("It will contain BROCHURECOOKIE depending on the last page you've visited (/login or /logout)"),
      verbatimTextOutput("cookie"),
      nav_links
    ),
    server = function(input, output, session){
      output$cookie &amp;lt;- renderPrint({
        parse_cookie_string(
          session$request$HTTP_COOKIE
        )
      })
    }
  )
}

login &amp;lt;- function(){
  page(
    href = "/login",
    ui = tagList(
      h1("You've just logged!"),
      verbatimTextOutput("cookie"),
      actionButton("redirect", "Redirect to the home page"),
      nav_links
    ), 
    server = function(input, output, session){
      output$cookie &amp;lt;- renderPrint({
        parse_cookie_string(
          session$request$HTTP_COOKIE
        )
      })
      observeEvent( input$redirect , {
        # Using brochure to redirect to another page
        server_redirect("/")
      })

    },
    res_handlers = list(
      # We'll add a cookie here
      function(res, req){
        res$headers$`Set-Cookie` &amp;lt;- "BROCHURECOOKIE=12; HttpOnly;"
        res
      }
    )
  )
}

logout &amp;lt;- function(){
  page(
    href = "/logout",
    ui = tagList(
      h1("You've logged out"),
      nav_links,
      verbatimTextOutput("cookie"),
      actionButton("redirect", "Redirect to the home page"),
    ), 
    server = function(input, output, session){
      output$cookie &amp;lt;- renderPrint({
        parse_cookie_string(
          session$request$HTTP_COOKIE
        )
      })
      observeEvent( input$redirect , {
        # Using brochure to redirect to another page
        server_redirect("/")
      })
    },
    res_handlers = list(
      # We'll add a cookie here
      function(res, req){
        res$headers$`Set-Cookie` &amp;lt;- "BROCHURECOOKIE=12; Expires=Wed, 21 Oct 1950 07:28:00 GMT"
        res
      }
    )
  )
}

brochureApp(
  # Pages
  home(),
  login(),
  logout()
)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Random notes about design patterns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Data persistence
&lt;/h3&gt;

&lt;p&gt;Every time you open a new page, a &lt;strong&gt;new shiny session is launched&lt;/strong&gt;. This is different from what you usually do when you are building a&lt;code&gt;{shiny}&lt;/code&gt; app that works as a single page application. This is no longer the case in &lt;code&gt;{brochure}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What that means is that &lt;strong&gt;there is no data persistence in R when navigating from one page to the other&lt;/strong&gt;. That might seem like a downside, but I believe that it will actually be for the best: &lt;strong&gt;it will make developers think more carefully about the data flow of their application, and allow a faster flush of R sessions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That being said, how do we keep track of a user though pages, so that if they do something in a page, it’s available on another page?&lt;/p&gt;

&lt;p&gt;To do that, you’d need to add a form of session identifier, like a cookie: this can for example be done using the&lt;a href="https://github.com/colinfay/glouton" rel="noopener noreferrer"&gt;&lt;code&gt;{glouton}&lt;/code&gt;&lt;/a&gt; package if you want to manage it with JS only. You can also use the cookie example from before.&lt;/p&gt;

&lt;p&gt;You’ll also need a form of backend storage (for example with&lt;a href="https://github.com/r-lib/cachem" rel="noopener noreferrer"&gt;&lt;code&gt;{cachem}&lt;/code&gt;&lt;/a&gt; here in the example, but you can also use an external DB like SQLite or MongoDB).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;library(glouton)
# Creating a storage system
cache_system &amp;lt;- cachem::cache_disk(tempdir())

nav_links &amp;lt;- tags$ul(
  tags$li(
    tags$a(href = "/", "home"), 
  ),
  tags$li(
    tags$a(href = "/page2", "page2"), 
  )
)

cookie_set &amp;lt;- function(){
  r &amp;lt;- reactiveValues()

  observeEvent(TRUE, {
    # Fetch the cookies using {glouton}
    r$cook &amp;lt;- fetch_cookies()

    # If there is no stored cookie for {brochure}, we generate it
    if (is.null(r$cook$brochure_cookie)){
      # Generate a random id
      session_id &amp;lt;- digest::sha1(paste(Sys.time(), sample(letters, 16)))
      # Add this id as a cookie
      add_cookie("brochure_cookie", session_id)
      # Store in in the reactiveValues list
      r$cook$brochure_cookie &amp;lt;- session_id
    }
    # For debugging purpose
    print(r$cook$brochure_cookie )
  }, once = TRUE)
  return(r)
}

page_1 &amp;lt;- function(){
  page(
    href = "/",
    ui = tagList(
      h1("This is my first page"),
      nav_links,
      # The text enter on page 1 will be available on page 2, using
      # a session cookie and a storage system
      textInput("textenter", "Enter a text"), 
      actionButton("save", "Save my text and go to page2")
    ),
    server = function(input, output, session){
      r &amp;lt;- cookie_set()
      observeEvent( input$save , {
        # Use the session id to save on the cache system
        cache_system$set(
          paste0(
            r$cook$brochure_cookie,
            "text"
          ),
          input$textenter
        )
        server_redirect("/page2")
      })
    }
  )
}

page_2 &amp;lt;- function(){
  page(
    href = "/page2",
    ui = tagList(
      h1("This is my second page"),
      nav_links,
      # The text enter on page 1 will be available here, reading
      # the storage system
      verbatimTextOutput("textdisplay")
    ), 
    server = function(input, output, session){
      r &amp;lt;- cookie_set()
      output$textdisplay &amp;lt;- renderPrint({
        # Getting the content value based on the session cookie
        cache_system$get(
          paste0(
            r$cook$brochure_cookie,
            "text"
          )
        )
      })
    }
  )
}

brochureApp(
  # Setting {glouton} globally
  use_glouton(),
  # Pages
  page_1(),
  page_2()
  # Redirections
)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Working with golem
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;{golem}&lt;/code&gt; is a small project that is starting to get some traction in the &lt;code&gt;{shiny}&lt;/code&gt; world. I’m still not sure of its relevance, but I’ve heard that some of you are using it for some reasons, so I suppose you might want to make &lt;code&gt;{brochure}&lt;/code&gt; work inside a &lt;code&gt;{golem}&lt;/code&gt; based app.&lt;/p&gt;

&lt;p&gt;To adapt your &lt;code&gt;{golem}&lt;/code&gt; based application to &lt;code&gt;{brochure}&lt;/code&gt;, here are the two steps to follow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Remove the app_server.R file, and the top of app_ui =&amp;gt; You’ll still need &lt;code&gt;golem_add_external_resources()&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Build the pages inside separate R scripts, following the example from this &lt;code&gt;README&lt;/code&gt;.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── DESCRIPTION
├── NAMESPACE
├── R
│ ├── app_config.R
│ ├── home.R ### YOUR PAGE 
│ ├── login.R ### YOUR PAGE 
│ ├── logout.R ### YOUR PAGE 
│ └── run_app.R ### YOUR PAGE 
├── dev
│ ├── 01_start.R
│ ├── 02_dev.R
│ ├── 03_deploy.R
│ └── run_dev.R
├── inst
│ ├── app
│ │ └── www
│ │ ├── favicon.ico
│ └── golem-config.yml
├── man
│ └── run_app.Rd

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Replace &lt;code&gt;shinyApp&lt;/code&gt; with &lt;code&gt;brochureApp&lt;/code&gt; in &lt;code&gt;run_app()&lt;/code&gt;, add the external resources, then your pages.
&amp;lt;!-- end list --&amp;gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;run_app &amp;lt;- function(
  onStart = NULL,
  options = list(), 
  enableBookmarking = NULL,
  ...
) {
  with_golem_options(
    app = brochureApp(
      # Putting the resources here
      golem_add_external_resources(),
      home(),
      login(),
      logout(),
      onStart = onStart,
      options = options, 
      enableBookmarking = enableBookmarking
    ), 
    golem_opts = list(...)
  )
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Improvement &amp;amp; further work
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Testers and apps. If you want to give &lt;code&gt;{brochure}&lt;/code&gt; a shot, please do! It’s still a young project that needs more testing, so if you feel like you want to build an app with it, please do. I’d be happy to help you if you have some questions so feel free to reach me on&lt;a href="https://twitter.com/_ColinFay" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;, or comment on the post below.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Functions to manipulate cookies (create and delete them).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Built-in handlers for &lt;code&gt;req&lt;/code&gt; &amp;amp; &lt;code&gt;res&lt;/code&gt; that will answer common use cases.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Allow a way to implement &lt;code&gt;:id:&lt;/code&gt; in the URL, the way &lt;code&gt;express.js&lt;/code&gt;does: you can set a route as &lt;code&gt;/profile/:id:&lt;/code&gt; and the app will answers to &lt;code&gt;/profile/colin&lt;/code&gt;, &lt;code&gt;/profile/jean&lt;/code&gt;, &lt;code&gt;/profile/whatever&lt;/code&gt;, setting the &lt;code&gt;id&lt;/code&gt; as a variable that can be reused in the response. In the context of &lt;code&gt;{shiny}&lt;/code&gt;, this would of course be available the server.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Auto build a proxy based on the application. This will allow to create one R session by page, then create a proxy that will plug each endpoint to an R session, allowing a little bit of scalability “for free”.&lt;/p&gt;

&lt;p&gt;If you have any idea or question, please &lt;a href="https://github.com/ColinFay/brochure/issues" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt; on the GitHub repo!&lt;/p&gt;

</description>
      <category>rstats</category>
    </item>
  </channel>
</rss>
