

{"id":13789,"date":"2023-03-12T09:28:48","date_gmt":"2023-03-12T14:28:48","guid":{"rendered":"https:\/\/rud.is\/b\/?p=13789"},"modified":"2023-03-15T03:26:15","modified_gmt":"2023-03-15T08:26:15","slug":"almost-bare-bones-webr-starter-app","status":"publish","type":"post","link":"https:\/\/rud.is\/b\/2023\/03\/12\/almost-bare-bones-webr-starter-app\/","title":{"rendered":"Almost Bare Bones WebR Starter App"},"content":{"rendered":"<p>Let&#8217;s walk through how to set up a <a href=\"https:\/\/rud.is\/webr-app\/\">~minimal HTML\/JS\/CSS + WebR-powered &#8220;app&#8221;<\/a> on a server you own. This will be vanilla JS (i.e. no React\/Vue\/npm\/bundler) you can hack on at-will.<\/p>\n<p>TL;DR: You can find the source to the app and track changes to it <a href=\"https:\/\/github.com\/hrbrmstr\/webr-app\/tree\/batman\">over on GitHub<\/a> if you want to jump right in.<\/p>\n<p>In the <code>docs\/<\/code> directory in the GH repo you&#8217;ll see an example of using this in GH Pages.Here it is live: <a href=\"https:\/\/hrbrmstr.github.io\/webr-app\/index.html\">https:\/\/hrbrmstr.github.io\/webr-app\/index.html<\/a>. Info on what you need to do for that is below.<\/p>\n<p>If all went well, you should see the output of a call to WebR right here (it may take a few seconds):<\/p>\n<p><iframe src=\"https:\/\/rud.is\/b\/iframes\/webr\/sysinfo.html\" style=\"width:1px; min-width:100%; border-width: 0px;\" allowtransparency=\"true\" seamless><\/iframe><\/p>\n<h2>Getting Your Server Set Up<\/h2>\n<p>I&#8217;ll try to keep updating this with newer WebR releases. Current version is 0.1.0 and you can grab that from: <a href=\"https:\/\/github.com\/r-wasm\/webr\/releases\/download\/v0.1.0\/webr-0.1.0.tar.gz\">https:\/\/github.com\/r-wasm\/webr\/releases\/download\/v0.1.0\/webr-0.1.0.tar.gz<\/a>.<\/p>\n<h3>System-Wide WebR<\/h3>\n<blockquote><p>\n  You should <a href=\"https:\/\/docs.r-wasm.org\/webr\/v0.1.0\/serving.html\">read this section<\/a> in the official WebR documentation before continuing.\n<\/p><\/blockquote>\n<p>I&#8217;m using a server-wide <code>\/webr<\/code> directory on my <code>rud.is<\/code> domain so I can use it on any page I serve.<\/p>\n<p>WebR performance will suffer if it can&#8217;t use <code>SharedArrayBuffer<\/code>s. So, I have these headers enabled on my <code>\/webr<\/code> directory:<\/p>\n<pre><code class=\"language-plain\">Cross-Origin-Opener-Policy: same-origin\nCross-Origin-Embedder-Policy: require-corp\n<\/code><\/pre>\n<p>I use nginx, so that looks like:<\/p>\n<pre><code class=\"language-nginx\">location ^~ \/webr {\n  add_header \"Cross-Origin-Opener-Policy\" \"same-origin\";\n  add_header \"Cross-Origin-Embedder-Policy\" \"require-corp\";\n}\n<\/code><\/pre>\n<p>YMMV.<\/p>\n<p>For good measure (and in case I move things around), I stick those headers on my any app dir that will use WebR. I don&#8217;t use them server-wide, though.<\/p>\n<h3>And They Call It a MIME. A MIME!<\/h3>\n<p>WebR is a <a href=\"https:\/\/www.w3schools.com\/js\/js_modules.asp\">JavaScript module<\/a>, and you need to make sure that files with an <code>mjs<\/code> extension have a <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Basics_of_HTTP\/MIME_types\">MIME type<\/a> of <code>text\/javascript<\/code>, or some browsers won&#8217;t be happy.<\/p>\n<p>A typical way for webservers to know how to communicate this is via a <code>mime.types<\/code> file. That is not true for all webservers, and I&#8217;ll add steps for ones that use a different way to configure this. The entry should look like this:<\/p>\n<pre><code class=\"language-plain\">text\/javascript  mjs;\n<\/code><\/pre>\n<h3>Testing The WebR Set Up<\/h3>\n<p>You should be able to hit that path on your webserver in your browser and see the WebR console app. If you do, you can continue. If not, leave an issue and I can try to help you debug it, but that&#8217;s on a best-effort basis for me.<\/p>\n<h2>Installing The App<\/h2>\n<p>We&#8217;ll dig into the app in a bit, but you probably want to see it working, so let&#8217;s install this ~minimal app.<\/p>\n<p>My personal demo app is anchored off of <code>\/webr-app<\/code> on my <code>rud.is<\/code> web server. Here&#8217;s how to replicate it:<\/p>\n<pre><code class=\"language-shell\"># Go someplace safe\n$ cd $TMPDIR\n\n# Get the app bundle\n# You can also use the GH release version, just delete the README after installing it.\n$ curl -o webr-app.tgz https:\/\/rud.is\/dl\/webr-app.tgz\n\n# Expand it\n$ tar -xvzf webr-app.tgz\nx .\/webr-app\/\nx .\/webr-app\/modules\/\nx .\/webr-app\/modules\/webr-app.js\nx .\/webr-app\/modules\/webr-helpers.js\nx .\/webr-app\/css\/\nx .\/webr-app\/css\/simple.min.css\nx .\/webr-app\/css\/app.css\nx .\/webr-app\/main.js\nx .\/webr-app\/index.html\n\n# ? GO THROUGH EACH FILE\n# ? to make sure I'm not pwning you!\n# ? Don't trust anything or anyone.\n\n# Go to the webserver root\n$ cd $PATH_TO_WEBSERVER_DOC_ROOT_PATH\n\n# Move the directory\n$ mv $TMPDIR\/webr-app .\n\n# Delete the tarball (optional)\n$ rm $TMPDIR\/webr-app.tgz\n<\/code><\/pre>\n<p>Hit up that path on <em>your<\/em> web server and you should see what you saw on mine.<\/p>\n<h2>WebR-Powered App Structure<\/h2>\n<pre><code class=\"language-plain\">.\n\u251c\u2500\u2500 css                  # CSS (obvsly)\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 app.css          # app-specific ones\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 simple.min.css   # more on this in a bit\n\u251c\u2500\u2500 index.html           # The main app page\n\u251c\u2500\u2500 main.js              # The main app JS\n\u2514\u2500\u2500 modules              # We use ES6 JS modules\n    \u251c\u2500\u2500 webr-app.js      # Main app module\n    \u2514\u2500\u2500 webr-helpers.js  # Some WebR JS Helpers I wrote\n<\/code><\/pre>\n<h3>Simple CSS<\/h3>\n<p>If you sub to <a href=\"https:\/\/dailyfinds.hrbrmstr.dev\/\">my newsletter<\/a>, you know I play with tons of tools and frameworks. Please use what you prefer.For folks who don&#8217;t normally do this type of stuff, I included a copy of <a href=\"https:\/\/github.com\/kevquirk\/simple.css\">Simple CSS<\/a> b\/c, well, it is <em>simple<\/em> to use. Please <a href=\"https:\/\/simplecss.org\/demo\">use this resource<\/a> to get familiar with it if you do continue to use it.<\/p>\n<h3>JavaScript Modules<\/h3>\n<p>When I&#8217;m in &#8220;hack&#8221; mode (like I was for the first few days after WebR&#8217;s launch), I revert to old, bad habits. We will not replicate those here.<\/p>\n<p>We&#8217;re using <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Guide\/Modules\">JavaScript Modules<\/a> as the project structure. We aren&#8217;t &#8220;bundling&#8221; (slurping up all app support files into a single, minified file) since not every R person is a JS tooling expert. We&#8217;re also not using them as they really aren&#8217;t needed, and I like to keep things simple and as dependency-free as possible.<\/p>\n<p>In <code>index.html<\/code> you&#8217;ll see this line:<\/p>\n<pre><code class=\"language-javascript\">&lt;script type=\"module\" src=\".\/main.js\"&gt;&lt;\/script&gt; \n<\/code><\/pre>\n<p>This tells the browser to load that JS file as if it were a module. As you read (you <em>did<\/em> read the MDN link, above, <em>right<\/em>?), modules give us locally-scoped names\/objects\/features and protection from clobbering imported names.<\/p>\n<p>Our main module contains all the crunchy goodness core functionality of our app, which does nothing more than:<\/p>\n<ul>\n<li>loads WebR<\/li>\n<li>Tells you how fast it loaded + instantiated<\/li>\n<li>Yanks <code>mtcars<\/code> from the instantiated R session (<code>mtcars<\/code> was the third &#8220;thing&#8221; I typed into R, ever, so my brain defaults to it).<\/li>\n<li>Makes an HTML table from it using D3.<\/li>\n<\/ul>\n<p>It&#8217;s small enough to include here:<\/p>\n<pre><code class=\"language-javascript\">import { format } from \"https:\/\/cdn.skypack.dev\/d3-format@3\";\nimport * as HelpR from '.\/modules\/webr-helpers.js'; \/\/ WebR-specific helpers\n\/\/ import * as App from '.\/modules\/webr-app.js'; \/\/ our app's functions, if it had some\n\nconsole.time('Execution Time'); \/\/ keeps on tickin'\nconst timerStart = performance.now();\n\nimport { WebR } from '\/webr\/webr.mjs'; \/\/ service workers == full path starting with \/\n\nglobalThis.webR = new WebR({\n    WEBR_URL: \"\/webr\/\", # our system-wide WebR\n    SW_URL: \"\/webr\/\"    # what ^^ said\n}); \nawait globalThis.webR.init(); \n\n\/\/ WebR is ready to use. So, brag about it!\n\nconst timerEnd = performance.now();\nconsole.timeEnd('Execution Time');\n\ndocument.getElementById('loading').innerText = `WebR Loaded! (${format(\",.2r\")((timerEnd - timerStart) \/ 1000)} seconds)`;\n\nconst mtcars = await HelpR.getDataFrame(globalThis.webR, \"mtcars\");\nconsole.table(mtcars);\nHelpR.simpleDataFrameTable(\"#tbl\", mtcars);\n<\/code><\/pre>\n<p><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/globalThis\"><code>globalThis<\/code><\/a> is a special JS object that lets you shove stuff into the global JS environment. Not 100% needed, but if you want to use the same WebR context in in other app module blocks, this is how you&#8217;d do it.<\/p>\n<p>Let&#8217;s focus on the last three lines.<\/p>\n<pre><code class=\"language-javascript\">const mtcars = await HelpR.getDataFrame(globalThis.webR, \"mtcars\");\n<\/code><\/pre>\n<p>This uses a helper function I made to get a data frame object from R in a way more compatible for most JS and JS libraries than <a href=\"https:\/\/docs.r-wasm.org\/webr\/v0.1.0\/convert-r-to-js.html\">the default JS object WebR&#8217;s <code>toJs()<\/code> function converts all R objects to<\/a>.<\/p>\n<pre><code class=\"language-javascript\">console.table(mtcars);\n<\/code><\/pre>\n<p>This makes a nice table in the browser&#8217;s Developer Tools console. I did this so I could have you open up the console to see it, but I also want you to inspect the contents of the object (just type <code>mtcars<\/code> and hit enter\/return) to see this nice format.<\/p>\n<p>We pass in a WebR context we know will work, and then <em>any<\/em> R code that will evaluate and return a data frame. It is all on you (for the moment) to ensure the code runs and that it returns a data frame.<\/p>\n<p>The last line:<\/p>\n<pre><code class=\"language-javascript\">HelpR.simpleDataFrameTable(\"#tbl\", mtcars);\n<\/code><\/pre>\n<p>calls another helper function to make the table.<\/p>\n<h3>HelpR<\/h3>\n<p>I may eventually blather eloquently and completely about what&#8217;s in <code>modules\/webr-helpers.js<\/code>. For now, let me focus on just a couple things, especially since it&#8217;s got some <em>sweet<\/em> <a href=\"https:\/\/jsdoc.app\/\">JSDoc comments<\/a>.<\/p>\n<p>First off, let&#8217;s talk more about those comments.<\/p>\n<p>I use VS Code for ~60% of my daily ops, and used it for this project. If you open up the project root in VS Code and select\/hover over <code>simpleDataFrameTable<\/code> in that last line, you&#8217;ll get some sweet lookin&#8217;formatted help. VS Code is wired up for this (other editors\/IDEs are too), so I encourage you to make liberal use of JSDoc comments in your own functions\/modules.<\/p>\n<p>Now, let&#8217;s peek behind the curtain of <code>getDataFrame<\/code>:<\/p>\n<pre><code class=\"language-javascript\">export async function getDataFrame(ctx, rEvalCode) {\n    let result = await ctx.evalR(`${rEvalCode}`);\n    let output = await result.toJs();\n    return (Promise.resolve(webRDataFrameToJS(output)));\n}\n<\/code><\/pre>\n<p>The <code>export<\/code> tells the JS environment that that function is available if imported properly. Without the <code>export<\/code> the function is local to the module.<\/p>\n<pre><code class=\"language-javascript\">let result = await ctx.evalR(`${rEvalCode}`);\n<\/code><\/pre>\n<p>A proper app would use JS <code>try<\/code>\/<code>catch<\/code> potential errors. There&#8217;s an example of that in the fancy React app code <a href=\"https:\/\/docs.r-wasm.org\/webr\/v0.1.0\/examples.html#fully-worked-examples\">over at WebR&#8217;s site<\/a>. We just throw caution to the wind and evaluate whatever we&#8217;re given. In theory, we should have R ensure it&#8217;s a data frame which we kind of can&#8217;t do on the JS side since the next line:<\/p>\n<pre><code class=\"language-javascript\">let output = await result.toJs();\n<\/code><\/pre>\n<p>will show the type as a <code>list<\/code> (b\/c <code>data.frame<\/code>s are <code>list<\/code>s).<\/p>\n<p>I&#8217;ll likely add some more helpers to a more standalone helper module, but I suspect that corporate R will beat me to that, so I will likely also not invest too much time on it, at least externally.<\/p>\n<h4>Await! Await! Do Tell Me (about <code>await<\/code>)!<\/h4>\n<p>Before we can talk about the last line:<\/p>\n<pre><code class=\"language-javascript\">return (Promise.resolve(webRDataFrameToJS(output)));\n<\/code><\/pre>\n<p>let&#8217;s briefly talk about <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Statements\/async_function\">async<\/a> ops in JS.<\/p>\n<p>The JavaScript environment in your browser is single-threaded. <code>async<\/code>-hronous ops let pass of code to threads to avoid blocking page operations. These get executed &#8220;whenever&#8221;, so all you get is a vapid and shallow promise to of code execution and potentially giving you something back.<\/p>\n<p>We explicitly use <code>await<\/code> for when we <em>really<\/em> need the code to run and, in this case, give us something back. We can keep chaining async function calls, but &mdash; if we need to make sure the code runs and\/or we get data back &mdash; we will eventually need to keep our promise to do so; hence, <code>Promise.resolve<\/code>.<\/p>\n<h2>Serving WebR From GitHub Pages<\/h2>\n<p>The <code>docs\/<\/code> directory in the repo shows a working version on GH pages.<\/p>\n<p><code>main.js<\/code> needs a few tweaks:<\/p>\n<pre><code class=\"language-javascript\">\/\/ This will use Posit's CDN\n\nimport('https:\/\/webr.r-wasm.org\/latest\/webr.mjs').then( \/\/ this wraps the main app code\n    async ({ WebR }) =&gt; {\n\n        globalThis.webR = new WebR({\n            SW_URL: \"\/webr-app\/\"            \/\/ ?? needs to be your GHP main path\n        });\n        await globalThis.webR.init();\n\n        const timerEnd = performance.now();\n        console.timeEnd('Execution Time');\n\n        document.getElementById('loading').innerText = `WebR Loaded! (${format(\",.2r\")((timerEnd - timerStart) \/ 1000)} seconds)`;\n\n        const mtcars = await HelpR.getDataFrame(globalThis.webR, \"mtcars\");\n        console.table(mtcars);\n        HelpR.simpleDataFrameTable(\"#tbl\", mtcars);\n\n  }\n);\n<\/code><\/pre>\n<h2>Moar To Come<\/h2>\n<p>Please hit up <a href=\"https:\/\/rud.is\/webr-dash\/no-dplyr.html\">this terribly coded dashboard app<\/a> to see some fancier use. I\u2019ll be converting that to modules and expanding git a bit.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Let&#8217;s walk through how to set up a ~minimal HTML\/JS\/CSS + WebR-powered &#8220;app&#8221; on a server you own. This will be vanilla JS (i.e. no React\/Vue\/npm\/bundler) you can hack on at-will. TL;DR: You can find the source to the app and track changes to it over on GitHub if you want to jump right in. [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":13791,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"activitypub_content_warning":"","activitypub_content_visibility":"","activitypub_max_image_attachments":3,"activitypub_interaction_policy_quote":"anyone","activitypub_status":"","footnotes":""},"categories":[91],"tags":[866],"class_list":["post-13789","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-r","tag-webr"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.2 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Almost Bare Bones WebR Starter App - rud.is<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/rud.is\/b\/2023\/03\/12\/almost-bare-bones-webr-starter-app\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Almost Bare Bones WebR Starter App - rud.is\" \/>\n<meta property=\"og:description\" content=\"Let&#8217;s walk through how to set up a ~minimal HTML\/JS\/CSS + WebR-powered &#8220;app&#8221; on a server you own. This will be vanilla JS (i.e. no React\/Vue\/npm\/bundler) you can hack on at-will. TL;DR: You can find the source to the app and track changes to it over on GitHub if you want to jump right in. [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/rud.is\/b\/2023\/03\/12\/almost-bare-bones-webr-starter-app\/\" \/>\n<meta property=\"og:site_name\" content=\"rud.is\" \/>\n<meta property=\"article:published_time\" content=\"2023-03-12T14:28:48+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2023-03-15T08:26:15+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/03\/Screenshot-2023-03-12-at-10.24.18.png?fit=1216%2C782&ssl=1\" \/>\n\t<meta property=\"og:image:width\" content=\"1216\" \/>\n\t<meta property=\"og:image:height\" content=\"782\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"hrbrmstr\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"hrbrmstr\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"9 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/rud.is\/b\/2023\/03\/12\/almost-bare-bones-webr-starter-app\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/rud.is\/b\/2023\/03\/12\/almost-bare-bones-webr-starter-app\/\"},\"author\":{\"name\":\"hrbrmstr\",\"@id\":\"https:\/\/rud.is\/b\/#\/schema\/person\/d7cb7487ab0527447f7fda5c423ff886\"},\"headline\":\"Almost Bare Bones WebR Starter App\",\"datePublished\":\"2023-03-12T14:28:48+00:00\",\"dateModified\":\"2023-03-15T08:26:15+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/rud.is\/b\/2023\/03\/12\/almost-bare-bones-webr-starter-app\/\"},\"wordCount\":1336,\"commentCount\":3,\"publisher\":{\"@id\":\"https:\/\/rud.is\/b\/#\/schema\/person\/d7cb7487ab0527447f7fda5c423ff886\"},\"image\":{\"@id\":\"https:\/\/rud.is\/b\/2023\/03\/12\/almost-bare-bones-webr-starter-app\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/03\/Screenshot-2023-03-12-at-10.24.18.png?fit=1216%2C782&ssl=1\",\"keywords\":[\"webr\"],\"articleSection\":[\"R\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/rud.is\/b\/2023\/03\/12\/almost-bare-bones-webr-starter-app\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/rud.is\/b\/2023\/03\/12\/almost-bare-bones-webr-starter-app\/\",\"url\":\"https:\/\/rud.is\/b\/2023\/03\/12\/almost-bare-bones-webr-starter-app\/\",\"name\":\"Almost Bare Bones WebR Starter App - rud.is\",\"isPartOf\":{\"@id\":\"https:\/\/rud.is\/b\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/rud.is\/b\/2023\/03\/12\/almost-bare-bones-webr-starter-app\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/rud.is\/b\/2023\/03\/12\/almost-bare-bones-webr-starter-app\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/03\/Screenshot-2023-03-12-at-10.24.18.png?fit=1216%2C782&ssl=1\",\"datePublished\":\"2023-03-12T14:28:48+00:00\",\"dateModified\":\"2023-03-15T08:26:15+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/rud.is\/b\/2023\/03\/12\/almost-bare-bones-webr-starter-app\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/rud.is\/b\/2023\/03\/12\/almost-bare-bones-webr-starter-app\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/rud.is\/b\/2023\/03\/12\/almost-bare-bones-webr-starter-app\/#primaryimage\",\"url\":\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/03\/Screenshot-2023-03-12-at-10.24.18.png?fit=1216%2C782&ssl=1\",\"contentUrl\":\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/03\/Screenshot-2023-03-12-at-10.24.18.png?fit=1216%2C782&ssl=1\",\"width\":1216,\"height\":782,\"caption\":\"screenshot of the example webr app showing a portion of mtcars\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/rud.is\/b\/2023\/03\/12\/almost-bare-bones-webr-starter-app\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/rud.is\/b\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Almost Bare Bones WebR Starter App\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/rud.is\/b\/#website\",\"url\":\"https:\/\/rud.is\/b\/\",\"name\":\"rud.is\",\"description\":\"&quot;In God we trust. All others must bring data&quot;\",\"publisher\":{\"@id\":\"https:\/\/rud.is\/b\/#\/schema\/person\/d7cb7487ab0527447f7fda5c423ff886\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/rud.is\/b\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\/\/rud.is\/b\/#\/schema\/person\/d7cb7487ab0527447f7fda5c423ff886\",\"name\":\"hrbrmstr\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/10\/ukr-shield.png?fit=460%2C460&ssl=1\",\"url\":\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/10\/ukr-shield.png?fit=460%2C460&ssl=1\",\"contentUrl\":\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/10\/ukr-shield.png?fit=460%2C460&ssl=1\",\"width\":460,\"height\":460,\"caption\":\"hrbrmstr\"},\"logo\":{\"@id\":\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/10\/ukr-shield.png?fit=460%2C460&ssl=1\"},\"description\":\"Don't look at me\u2026I do what he does \u2014 just slower. #rstats avuncular \u2022 ?Resistance Fighter \u2022 Cook \u2022 Christian \u2022 [Master] Chef des Donn\u00e9es de S\u00e9curit\u00e9 @ @rapid7\",\"sameAs\":[\"http:\/\/rud.is\"],\"url\":\"https:\/\/rud.is\/b\/author\/hrbrmstr\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Almost Bare Bones WebR Starter App - rud.is","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/rud.is\/b\/2023\/03\/12\/almost-bare-bones-webr-starter-app\/","og_locale":"en_US","og_type":"article","og_title":"Almost Bare Bones WebR Starter App - rud.is","og_description":"Let&#8217;s walk through how to set up a ~minimal HTML\/JS\/CSS + WebR-powered &#8220;app&#8221; on a server you own. This will be vanilla JS (i.e. no React\/Vue\/npm\/bundler) you can hack on at-will. TL;DR: You can find the source to the app and track changes to it over on GitHub if you want to jump right in. [&hellip;]","og_url":"https:\/\/rud.is\/b\/2023\/03\/12\/almost-bare-bones-webr-starter-app\/","og_site_name":"rud.is","article_published_time":"2023-03-12T14:28:48+00:00","article_modified_time":"2023-03-15T08:26:15+00:00","og_image":[{"width":1216,"height":782,"url":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/03\/Screenshot-2023-03-12-at-10.24.18.png?fit=1216%2C782&ssl=1","type":"image\/png"}],"author":"hrbrmstr","twitter_card":"summary_large_image","twitter_misc":{"Written by":"hrbrmstr","Est. reading time":"9 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/rud.is\/b\/2023\/03\/12\/almost-bare-bones-webr-starter-app\/#article","isPartOf":{"@id":"https:\/\/rud.is\/b\/2023\/03\/12\/almost-bare-bones-webr-starter-app\/"},"author":{"name":"hrbrmstr","@id":"https:\/\/rud.is\/b\/#\/schema\/person\/d7cb7487ab0527447f7fda5c423ff886"},"headline":"Almost Bare Bones WebR Starter App","datePublished":"2023-03-12T14:28:48+00:00","dateModified":"2023-03-15T08:26:15+00:00","mainEntityOfPage":{"@id":"https:\/\/rud.is\/b\/2023\/03\/12\/almost-bare-bones-webr-starter-app\/"},"wordCount":1336,"commentCount":3,"publisher":{"@id":"https:\/\/rud.is\/b\/#\/schema\/person\/d7cb7487ab0527447f7fda5c423ff886"},"image":{"@id":"https:\/\/rud.is\/b\/2023\/03\/12\/almost-bare-bones-webr-starter-app\/#primaryimage"},"thumbnailUrl":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/03\/Screenshot-2023-03-12-at-10.24.18.png?fit=1216%2C782&ssl=1","keywords":["webr"],"articleSection":["R"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/rud.is\/b\/2023\/03\/12\/almost-bare-bones-webr-starter-app\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/rud.is\/b\/2023\/03\/12\/almost-bare-bones-webr-starter-app\/","url":"https:\/\/rud.is\/b\/2023\/03\/12\/almost-bare-bones-webr-starter-app\/","name":"Almost Bare Bones WebR Starter App - rud.is","isPartOf":{"@id":"https:\/\/rud.is\/b\/#website"},"primaryImageOfPage":{"@id":"https:\/\/rud.is\/b\/2023\/03\/12\/almost-bare-bones-webr-starter-app\/#primaryimage"},"image":{"@id":"https:\/\/rud.is\/b\/2023\/03\/12\/almost-bare-bones-webr-starter-app\/#primaryimage"},"thumbnailUrl":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/03\/Screenshot-2023-03-12-at-10.24.18.png?fit=1216%2C782&ssl=1","datePublished":"2023-03-12T14:28:48+00:00","dateModified":"2023-03-15T08:26:15+00:00","breadcrumb":{"@id":"https:\/\/rud.is\/b\/2023\/03\/12\/almost-bare-bones-webr-starter-app\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/rud.is\/b\/2023\/03\/12\/almost-bare-bones-webr-starter-app\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/rud.is\/b\/2023\/03\/12\/almost-bare-bones-webr-starter-app\/#primaryimage","url":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/03\/Screenshot-2023-03-12-at-10.24.18.png?fit=1216%2C782&ssl=1","contentUrl":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/03\/Screenshot-2023-03-12-at-10.24.18.png?fit=1216%2C782&ssl=1","width":1216,"height":782,"caption":"screenshot of the example webr app showing a portion of mtcars"},{"@type":"BreadcrumbList","@id":"https:\/\/rud.is\/b\/2023\/03\/12\/almost-bare-bones-webr-starter-app\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/rud.is\/b\/"},{"@type":"ListItem","position":2,"name":"Almost Bare Bones WebR Starter App"}]},{"@type":"WebSite","@id":"https:\/\/rud.is\/b\/#website","url":"https:\/\/rud.is\/b\/","name":"rud.is","description":"&quot;In God we trust. All others must bring data&quot;","publisher":{"@id":"https:\/\/rud.is\/b\/#\/schema\/person\/d7cb7487ab0527447f7fda5c423ff886"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/rud.is\/b\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/rud.is\/b\/#\/schema\/person\/d7cb7487ab0527447f7fda5c423ff886","name":"hrbrmstr","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/10\/ukr-shield.png?fit=460%2C460&ssl=1","url":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/10\/ukr-shield.png?fit=460%2C460&ssl=1","contentUrl":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/10\/ukr-shield.png?fit=460%2C460&ssl=1","width":460,"height":460,"caption":"hrbrmstr"},"logo":{"@id":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/10\/ukr-shield.png?fit=460%2C460&ssl=1"},"description":"Don't look at me\u2026I do what he does \u2014 just slower. #rstats avuncular \u2022 ?Resistance Fighter \u2022 Cook \u2022 Christian \u2022 [Master] Chef des Donn\u00e9es de S\u00e9curit\u00e9 @ @rapid7","sameAs":["http:\/\/rud.is"],"url":"https:\/\/rud.is\/b\/author\/hrbrmstr\/"}]}},"jetpack_featured_media_url":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/03\/Screenshot-2023-03-12-at-10.24.18.png?fit=1216%2C782&ssl=1","jetpack_shortlink":"https:\/\/wp.me\/p23idr-3Ap","jetpack_likes_enabled":true,"jetpack-related-posts":[{"id":13963,"url":"https:\/\/rud.is\/b\/2023\/04\/16\/start-creating-vanilla-js-webr-apps-with-less-inertia\/","url_meta":{"origin":13789,"position":0},"title":"Start Creating Vanilla JS WebR Apps With Less Inertia","author":"hrbrmstr","date":"2023-04-16","format":false,"excerpt":"WebR has a template for React, but I'm not a fan of it or Vue (a fact longtime readers are likely tired of hearing right about now). It's my opinion and experience that Lit webcomponents come closer to \u201cbare metal\u201d webcomponents, which means the \u201clock-in\u201d with Lit is way less\u2026","rel":"","context":"In &quot;R&quot;","block_context":{"text":"R","link":"https:\/\/rud.is\/b\/category\/r\/"},"img":{"alt_text":"dark mode webr web app","src":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/04\/dark.png?fit=1200%2C754&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/04\/dark.png?fit=1200%2C754&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/04\/dark.png?fit=1200%2C754&ssl=1&resize=525%2C300 1.5x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/04\/dark.png?fit=1200%2C754&ssl=1&resize=700%2C400 2x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/04\/dark.png?fit=1200%2C754&ssl=1&resize=1050%2C600 3x"},"classes":[]},{"id":13845,"url":"https:\/\/rud.is\/b\/2023\/03\/20\/webr-filesystem-and-reefr\/","url_meta":{"origin":13789,"position":1},"title":"WebR Filesystem Machinations &#038; ReefR","author":"hrbrmstr","date":"2023-03-20","format":false,"excerpt":"It's difficult to believe it has only been a couple of weeks since WebR has been around. But that might just be my perception. The spike protein invasion has significantly increased sedentary time, and that has enabled me to focus on this new toy to keep my attention focused on\u2026","rel":"","context":"In &quot;R&quot;","block_context":{"text":"R","link":"https:\/\/rud.is\/b\/category\/r\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":13927,"url":"https:\/\/rud.is\/b\/2023\/04\/10\/introducing-webrider-the-webr-ide-ish-repl-you-didnt-know-you-needed\/","url_meta":{"origin":13789,"position":2},"title":"Introducing WebRIDEr: The WebR &#8220;IDE&#8221;-ish REPL You Didn&#8217;t Know You Needed","author":"hrbrmstr","date":"2023-04-10","format":false,"excerpt":"The official example WebR REPL is definitely cool and useful to get the feel for WebR. But, it is far from an ideal way to deal with it interactively, even as just a REPL. As y'all know, I've been conducing numerous experiments with WebR and various web technologies. I started\u2026","rel":"","context":"In &quot;R&quot;","block_context":{"text":"R","link":"https:\/\/rud.is\/b\/category\/r\/"},"img":{"alt_text":"ide-like view","src":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/04\/preview.png?fit=1200%2C754&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/04\/preview.png?fit=1200%2C754&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/04\/preview.png?fit=1200%2C754&ssl=1&resize=525%2C300 1.5x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/04\/preview.png?fit=1200%2C754&ssl=1&resize=700%2C400 2x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/04\/preview.png?fit=1200%2C754&ssl=1&resize=1050%2C600 3x"},"classes":[]},{"id":13773,"url":"https:\/\/rud.is\/b\/2023\/03\/09\/webr-is-here\/","url_meta":{"origin":13789,"position":3},"title":"WebR IS HERE!","author":"hrbrmstr","date":"2023-03-09","format":false,"excerpt":"WebR 0.1.0 was released! I had been git-stalking George (the absolute genius who we all must thank for this) for a while and noticed the GH org and repos being updated earlier this week, So, I was already pretty excited. It dropped today, and you can hit that link for\u2026","rel":"","context":"In &quot;d3&quot;","block_context":{"text":"d3","link":"https:\/\/rud.is\/b\/category\/d3\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":13885,"url":"https:\/\/rud.is\/b\/2023\/03\/26\/%f0%9f%a7%aa-lit-webr-observable-plot-linking-lits-lightweight-web-components-and-webr-for-vanilla-js-reactivity-js-datavis\/","url_meta":{"origin":13789,"position":4},"title":"? Lit + WebR + Observable Plot: Linking Lit&#8217;s Lightweight Web Components And WebR For Vanilla JS Reactivity &#038; JS DataVis","author":"hrbrmstr","date":"2023-03-26","format":false,"excerpt":"See it live before reading! The previous post brought lit-webr, to introduce Lit and basic reactivity. Today, is more of the same, but we bring the OG Shiny demo plot into the modern age by using Observbable Plot to make the charts. We're still pulling data from R, but we're\u2026","rel":"","context":"In &quot;R&quot;","block_context":{"text":"R","link":"https:\/\/rud.is\/b\/category\/r\/"},"img":{"alt_text":"popup menu + barplot","src":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/03\/preview.png?fit=1200%2C1174&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/03\/preview.png?fit=1200%2C1174&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/03\/preview.png?fit=1200%2C1174&ssl=1&resize=525%2C300 1.5x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/03\/preview.png?fit=1200%2C1174&ssl=1&resize=700%2C400 2x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/03\/preview.png?fit=1200%2C1174&ssl=1&resize=1050%2C600 3x"},"classes":[]},{"id":13849,"url":"https:\/\/rud.is\/b\/2023\/03\/21\/youre-one-javascript-function-call-away-from-using-most-webr-r-functions-in-your-webr-powered-apps-sites\/","url_meta":{"origin":13789,"position":5},"title":"You&#8217;re One JavaScript Function Call Away From Using (Most) WebR R Functions In Your WebR-Powered Apps\/Sites","author":"hrbrmstr","date":"2023-03-21","format":false,"excerpt":"After writing the initial version of a tutorial on wrapping and binding R functions on the javascript side of WebR, I had a number of other WebR projects on the TODO list. But, I stuck around noodling on the whole \"wrapping & binding\" thing, and it dawned on me that\u2026","rel":"","context":"In &quot;R&quot;","block_context":{"text":"R","link":"https:\/\/rud.is\/b\/category\/r\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/posts\/13789","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/comments?post=13789"}],"version-history":[{"count":0,"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/posts\/13789\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/media\/13791"}],"wp:attachment":[{"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/media?parent=13789"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/categories?post=13789"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/tags?post=13789"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}