

{"id":13860,"date":"2023-03-25T14:57:47","date_gmt":"2023-03-25T19:57:47","guid":{"rendered":"https:\/\/rud.is\/b\/?p=13860"},"modified":"2023-03-25T14:57:47","modified_gmt":"2023-03-25T19:57:47","slug":"linking-lits-lightweight-web-components-and-webr-for-vanilla-js-reactivity","status":"publish","type":"post","link":"https:\/\/rud.is\/b\/2023\/03\/25\/linking-lits-lightweight-web-components-and-webr-for-vanilla-js-reactivity\/","title":{"rendered":"Linking Lit&#8217;s Lightweight Web Components And WebR For Vanilla JS Reactivity"},"content":{"rendered":"<blockquote><p>\n  See it <a href=\"https:\/\/rud.is\/w\/lit-webr\/\">live<\/a> before reading!\n<\/p><\/blockquote>\n<p>This is a Lit + WebR reproduction of the <a href=\"https:\/\/shiny.rstudio.com\/gallery\/telephones-by-region.html\">OG Shiny Demo App<\/a><\/p>\n<p><a href=\"https:\/\/lit.dev\/\">Lit<\/a> is a javascript library that makes it a bit easier to work with <a href=\"https:\/\/dailyfinds.hrbrmstr.dev\/p\/drop-227-2023-03-24-weekend-project\">Web Components<\/a>, and is especially well-suited in reactive environments.<\/p>\n<p>My recent hack-y WebR experiments have been using <a href=\"https:\/\/reefjs.com\/getting-started\/\">Reef<\/a> which is an <em>even ligher<\/em>-weight javascript web components-esque library, and it&#8217;s a bit more (initially) accessible than Lit. Lit&#8217;s focus on &#8220;Web Components-first&#8221; means that you are kind of forced into a structure, which is good, since reactive things can explode if not managed well.<\/p>\n<p>I also think this might Shiny folks feel a bit more at home.<\/p>\n<p>This is the structure of our Lit + WebR example <em>(I keep rejiggering this layout, which likely frustrates alot of folks<\/em> ?)_<\/p>\n<pre><code class=\"language-plain\">lit-webr\n\u251c\u2500\u2500 css\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 style.css             # you know what this is\n\u251c\u2500\u2500 favicon.ico               # when developing locally I want my icon\n\u251c\u2500\u2500 index.html                # you know what this is\n\u251c\u2500\u2500 main.js                   # the core experiment runner\n\u251c\u2500\u2500 md\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 main.md               # the core experiment markdown file\n\u251c\u2500\u2500 r-code\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 region-plot.R         # we keep longer bits of R code in source files here\n\u251c\u2500\u2500 r.js                      # place for WebR work\n\u251c\u2500\u2500 renderers.js              # these experiment templates always use markdown\n\u251c\u2500\u2500 themes\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 ayu-dark.json         # my fav shiki theme\n\u251c\u2500\u2500 utils.js                  # handy utilities (still pretty bare)\n\u251c\u2500\u2500 wc\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 region-plot.js        # ?? WEB COMPONENT for the plot\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 select-list.js        # ??               for the regions popup menu\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 status-message.js     # ??               for the status message\n\u251c\u2500\u2500 webr-serviceworker.js.map # not rly necessary; just for clean DevTools console\n\u2514\u2500\u2500 webr-worker.js.map        # \u261d?\n<\/code><\/pre>\n<p>A great deal has changed (due to using Lit) since the last time you saw one of these experiments. You should <a href=\"https:\/\/github.com\/hrbrmstr\/lit-webr\">scan through the source<\/a> before continuing.<\/p>\n<p>The core changes to <code>index.html<\/code> are just us registering our web components:<\/p>\n<pre><code class=\"language-html\">&lt;script type=\"module\" src=\".\/wc\/select-list.js\"&gt;&lt;\/script&gt;\n&lt;script type=\"module\" src=\".\/wc\/region-plot.js\"&gt;&lt;\/script&gt;\n&lt;script type=\"module\" src=\".\/wc\/status-message.js\"&gt;&lt;\/script&gt;\n<\/code><\/pre>\n<p>We <em>could<\/em> have rolled them up into one JS file and minified them, but we&#8217;re keeping things simple for these experiments.<\/p>\n<p>Web Components (&#8220;components&#8221; from now on) become an &#8220;equal citizen&#8221; in terms of <code>HTMLElements<\/code>, and they&#8217;re registered right in the DOM.<\/p>\n<p>The next big change is in this file (the rendered <code>main.md<\/code>), where we use these new components instead of our <code>&lt;div&gt;<\/code>s. The whittled down version of it is essentially:<\/p>\n<pre><code class=\"language-html\">&lt;status-message id=\"status\"&gt;&lt;\/status-message&gt;\n\n&lt;region-plot id=\"regionsOutput\" svgId=\"lit-regions\"&gt;\n  &lt;select-list label=\"Select a region:\" id=\"regionsInput\"&gt;&lt;\/select-list&gt;\n&lt;\/region-plot&gt;\n<\/code><\/pre>\n<p>The <em>intent<\/em> of those elements is pretty clear (much clearer than the <code>&lt;div&gt;<\/code> versions), which is one aspect of components I like quite a bit.<\/p>\n<p>You&#8217;ll also notice components are <code>-<\/code> (dash) crazy. That&#8217;s part of the Web Components spec and is mandatory.<\/p>\n<p>We&#8217;re using pretty focused components. What I mean by that is that they&#8217;re not very reusable across other projects without copy\/paste. Part of that is on me since I don&#8217;t do web stuff for a living. Part of it was also to make it easier to show how to use them with WebR.<\/p>\n<p>With more modular code, plus separating out giant chunks of R source means that we can actually put the entirety of <code>main.js<\/code> right here <em>(I&#8217;ve removed all the annotations; please look at <code>main.js<\/code> to see them; we will be explaining one thing in depth here, vs there, tho.)<\/em>:<\/p>\n<pre><code class=\"language-js\">import { renderMarkdownInBody } from \".\/renderers.js\";\nimport * as d3 from \"https:\/\/cdn.jsdelivr.net\/npm\/d3@7\/+esm\";\n\nawait renderMarkdownInBody(\n  `main`,\n  \"ayu-dark\",\n  [ 'javascript', 'r', 'json', 'md', 'xml', 'console' ],\n  false\n)\n\nlet message = document.getElementById(\"status\");\nmessage.text = \"WebR Loading\u2026\"\n\nimport * as R from \".\/r.js\";\n\nmessage.text = \"Web R Initialized!\"\n\nawait R.webR.installPackages([ \"svglite\" ])\n\nawait R.library(`svglite`)\nawait R.library(`datasets`)\n\nconst regionRender = await globalThis.webR.evalR(await d3.text(\"r-code\/region-plot.R\"))\n\nmessage.text = \"{svglite} installed\"\n\nconst regions = document.getElementById(\"regionsInput\")\nconst plotOutput = document.getElementById(\"regionsOutput\")\n\nregions.options = await (await R.webR.evalR(`colnames(WorldPhones)`)).toArray()\nplotOutput.region = regions.options[ 0 ]\nplotOutput.renderFunction = regionRender\nplotOutput.render()\n\nmessage.text = \"Ready\"\n<\/code><\/pre>\n<p>I want to talk a bit about this line from <code>main.js<\/code>:<\/p>\n<pre><code class=\"language-js\">const regionRender = await globalThis.webR.evalR(\n  await d3.text(\"r-code\/region-plot.R\")\n)\n<\/code><\/pre>\n<p>That fetches the source of the single R file we have in this app, evaluates it, and returns the evaluated value (which is an R <code>function<\/code> object) to javascript. This is the script:<\/p>\n<pre><code class=\"language-r\">renderRegions &lt;- function(region, id = \"region-plot\") {\n\n  # our base plot theme\n\n  list(\n    panel.fill = \"#001e38\",\n    bar.fill = \"#4a6d88\",\n    axis.color = \"#c6cdd7\",\n    label.color = \"#c6cdd7\",\n    subtitle.color = \"#c6cdd7\",\n    title.color = \"#c6cdd7\",\n    ticks.color = \"#c6cdd7\",\n    axis.color = \"#c6cdd7\"\n  ) -&gt; theme\n\n  # get our svg graphics device amp'd\n  s &lt;- svgstring(width = 8, height = 4, pointsize = 8, id = id, standalone = FALSE)\n\n  # setup theme stuff we can't do in barplot()\n  par(\n    bg = theme$panel.fill,\n    fg = theme$label.color\n  )\n\n  # um, it's a barplot\n  barplot(\n    WorldPhones[, region],\n    main = region,\n    col = theme$bar.fill,\n    sub = \"Data from AT&amp;T (1961) The World's Telephones\",\n    ylab = \"Number of Telephones (K)\",\n    xlab = \"Year\",\n    border = NA,\n    col.axis = theme$axis.color,\n    col.lab = theme$label.color,\n    col.sub = theme$subtitle.color,\n    col.main = theme$title.color\n  )\n\n  dev.off()\n\n  # get the stringified SVG\n  plot_svg &lt;- s()\n\n  # make it responsive\n  plot_svg &lt;- sub(\"width='\\\\d+(\\\\.\\\\d+)?pt'\", \"width='100%'\", plot_svg)\n  plot_svg &lt;- sub(\"height='\\\\d+(\\\\.\\\\d+)?pt'\", \"\", plot_svg)\n\n  # return it\n  plot_svg\n\n}\n<\/code><\/pre>\n<p>That R function is callable <em>right from javascript<\/em>. Creating that ability was super brilliant of George (the Godfather of WebR). We actually end up giving it to the component that plots the barplot (see <code>region-plot.js<\/code>) right here:<\/p>\n<pre><code class=\"language-js\">plotOutput.renderFunction = regionRender\n<\/code><\/pre>\n<p>We&#8217;re getting a bit ahead of ourselves, since we haven&#8217;t talked about the components yet. We&#8217;ll do so, starting with the easiest one to grok, which is in <code>status-message.js<\/code> and is represented by the <code>&lt;status-message&gt;&lt;\/status-message&gt;<\/code> tag.<\/p>\n<p>These custom Lit components get everything <code>HTMLElement<\/code> has, plus whatever else you provide. I&#8217;m not going to show the entire source for <code>status-message.js<\/code> here as it is (lightly) annotated. We&#8217;ll just cover the fundamentals, as Lit components also have alot going on and we&#8217;re just using a fraction of what they can do. Here&#8217;s the outline of what&#8217;s in our <code>status-message<\/code>:<\/p>\n<pre><code class=\"language-js\">export class StatusMessage extends LitElement {\n  static properties = { \/* things you can assign to and read from *\/ }\n  static styles = [ \/* component-scoped CSS *\/ ]\n  constructor() { \/* initialization bits *\/\n  render() { \/* what gets called when things change *\/ }\n}\n\/\/ register it\ncustomElements.define('status-message', StatusMessage);\n<\/code><\/pre>\n<p>Our <code>status-message<\/code> <code>properties<\/code> just has one property:<\/p>\n<pre><code class=\"language-js\">static properties = {\n  text: {type: String}, \/\/ TypeScript annotations are requried by Lit\n};\n<\/code><\/pre>\n<p>This means when we do:<\/p>\n<pre><code class=\"language-js\">let message = document.getElementById(\"status\");\nmessage.text = \"WebR Loading\u2026\"\n<\/code><\/pre>\n<p>we are finding our component in the DOM, then updating the property we defined. That will trigger <code>render()<\/code> each time, and use any component-restricted CSS we&#8217;ve setup.<\/p>\n<p>Things get a <em>tad more complicated<\/em> in <code>select-list.js<\/code>. We&#8217;ll just cover the highlights, starting with the <code>properties<\/code>:<\/p>\n<pre><code class=\"language-js\">static properties = {\n  id: { type: String },    \/\/ gives us easy access to the id we set\n  label: { type: String }, \/\/ lets us define the label up front\n  options: { type: Array } \/\/ where the options for the popup will go\n};\n<\/code><\/pre>\n<p>If you recall, this is how we used them in the source:<\/p>\n<pre><code class=\"language-html\">&lt;region-plot id=\"regionsOutput\" svgId=\"lit-regions\"&gt;\n  &lt;select-list label=\"Select a region:\" id=\"regionsInput\"&gt;&lt;\/select-list&gt;\n&lt;\/region-plot&gt;\n<\/code><\/pre>\n<p>The <code>id<\/code> and <code>label<\/code> properties will be available right away after the custom element creation.<\/p>\n<p>We start <code>option<\/code> with an empty list:<\/p>\n<pre><code class=\"language-js\">constructor() {\n  super()\n  this.options = []\n}\n<\/code><\/pre>\n<p>Our <code>render()<\/code> function places the <code>&lt;label&gt;<\/code> and <code>&lt;select&gt;<\/code> tags in the DOM and will eventually populate the menu once it has data:<\/p>\n<pre><code class=\"language-js\">render() {\n  const selectId = `select-list-${this.id}`;\n  return html`\n  &lt;label for=\"${selectId}\"&gt;${this.label} \n    &lt;select id=\"${selectId}\" @change=${this._dispatch}&gt;\n      ${this.options.map(option =&gt; html`&lt;option&gt;${option}&lt;\/option&gt;`)}\n    &lt;\/select&gt;\n  &lt;\/label&gt;\n  `;\n}\n<\/code><\/pre>\n<p>Their clever use of JS template strings makes it much easier than ugly string concatenation.<\/p>\n<blockquote><p>\n  That <code>html<\/code> in the <code>return<\/code> is doing <em>alot<\/em> of work, and not just returning text. You gotta read up on Lit to get more info b\/c this is already too long.\n<\/p><\/blockquote>\n<p>The way we wired up reactivity in my Reef examples felt kludgy, and even the nicer way to do it in Reef feels kludgy to me. It&#8217;s <em>really<\/em> nice in Lit. This little addition to the <code>&lt;select&gt;<\/code> tag:<\/p>\n<pre><code class=\"language-js\">@change=${this._dispatch}\n<\/code><\/pre>\n<p>says to call a function named <code>_dispatch<\/code> whenever the value changes. That&#8217;s in the component as well:<\/p>\n<pre><code class=\"language-js\">_dispatch(e) {\n  const options = {\n    detail: e.target,\n    bubbles: true,\n    composed: true,\n  };\n  this.dispatchEvent(new CustomEvent(`regionChanged`, options));\n}\n<\/code><\/pre>\n<p>We setup a data structure and then fire off a custom event that our plot component will listen for.  We&#8217;ve just linked them together on one side. Now we just need to populate the <code>options<\/code> list, using some data from R:<\/p>\n<pre><code class=\"language-js\">const regions = document.getElementById(\"regionsInput\")\nregions.options = await (await R.webR.evalR(`colnames(WorldPhones)`)).toArray()\n<\/code><\/pre>\n<p>That&#8217;ll make the menu appear.<\/p>\n<p>Hearkening back to the <code>main.js<\/code> plot setup:<\/p>\n<pre><code class=\"language-js\">const plotOutput = document.getElementById(\"regionsOutput\")\nplotOutput.region = regions.options[ 0 ]\nplotOutput.renderFunction = regionRender\nplotOutput.render()\n<\/code><\/pre>\n<p>we see that we:<\/p>\n<ul>\n<li>find the element<\/li>\n<li>set the default region to the first one in the popup<\/li>\n<li>assign our R-created rendering function to it<\/li>\n<li>and ask it nicely to render right now vs wait for someone to select something<\/li>\n<\/ul>\n<p>The other side of that (<code>region-plot.js<\/code>) is a bit more complex. Let&#8217;s start with the <code>properties<\/code>:<\/p>\n<pre><code class=\"language-js\">static properties = {\n  \/\/ we keep a local copy for fun\n  region: { type: String },\n\n  \/\/ this is where our S\n  asyncSvg: { type: String },\n\n  \/\/ a DOM-accessible id string (cld be handy)\n  svgId: { type: String },\n\n  \/\/ the function to be called to render\n  renderFunction: { type: Function }\n};\n<\/code><\/pre>\n<p>WebR === &#8220;async&#8221;, which is why you see that <code>asyncSvg<\/code>. Async is great and also a pain. There are way more functions in <code>region-plot.js<\/code> as a result.<\/p>\n<p>We have to have something in <code>renderFunction<\/code> before WebR is powered up since the component will be alive before that. We&#8217;ll give it an anonymous async function that returns an empty SVG.<\/p>\n<pre><code class=\"language-js\">this.renderFunction = async () =&gt; `&lt;svg&gt;&lt;\/svg&gt;`\n<\/code><\/pre>\n<p>Oddly enough, our <code>render<\/code> function <em>does not call the plotting function<\/em>. This is what it does:<\/p>\n<pre><code class=\"language-js\">render() {\n  return html`\n  &lt;div&gt;\n  &lt;slot&gt;&lt;\/slot&gt;\n  ${unsafeSVG(this.asyncSvg)}\n  &lt;\/div&gt;`;\n}\n<\/code><\/pre>\n<p>This bit:<\/p>\n<pre><code class=\"language-html\">&lt;slot&gt;&lt;\/slot&gt;\n<\/code><\/pre>\n<p>just tells <code>render()<\/code> to take whatever is wrapped in the tag and shove it there (it&#8217;s a bit more powerful than just that tho).<\/p>\n<p>This bit:<\/p>\n<pre><code class=\"language-js\">${unsafeSVG(this.asyncSvg)}\n<\/code><\/pre>\n<p>is just taking our string with SVG in it and letting Lit know we really want to live dangerously. Lit does its best to help you avoid security issues and SVGs are <em>dangerous<\/em>.<\/p>\n<p>So, how <em>do<\/em> we render the plot? With <strong>two<\/strong> new functions:<\/p>\n<pre><code class=\"language-js\">\/\/ this is a special async callback mechanism that \n\/\/ lets the component behave normally, but do things \n\/\/ asynchronously when necessary.\nasync connectedCallback() {\n\n  super.connectedCallback();\n\n  \/\/ THIS IS WHERE WE CALL THE PLOT FUNCTION\n  this.asyncSvg = await this.renderFunction(this.region, this.svgId);\n\n  \/\/ We'll catch this event when the SELECT list changes or when\n  \/\/ WE fire it, like we do down below.\n  this.addEventListener('regionChanged', async (e) =&gt; {\n    this.region = e.detail.value;\n    const res = await this.renderFunction(this.region, this.svgId);\n    \/\/ if the result of the function call is from the R function and\n    \/\/ not the anonymous one we initialized the oject with\n    \/\/ we need to tap into the `values` slot that gets return\n    \/\/ with any call to WebR's `toJs()`\n    if (res.values) this.asyncSvg = res.values[ 0 ] ;\n  });\n\n}\n\n\/\/ special function that will get called when we \n\/\/ programmatically ask for a forced update\nperformUpdate() {\n  super.performUpdate();\n  const options = {\n    detail: { value: this.region },\n    bubbles: true,\n    composed: true,\n  };\n\n  \/\/ we fire the event so things happen async\n  this.dispatchEvent(new CustomEvent(`regionChanged`, options));\n}\n<\/code><\/pre>\n<p>That finished the wiring up on the plotting end.<\/p>\n<h2>Serving &#8216;Just&#8217; Desserts (Locally)<\/h2>\n<p>I <em>highly<\/em> recommend using the tiny but awesome Rust-powered <a href=\"https:\/\/dailyfinds.hrbrmstr.dev\/i\/87467104\/miniserve-rust\">miniserve<\/a> to serve things locally during development:<\/p>\n<pre><code class=\"language-console\">miniserve \\\n  --header \"Cache-Control: no-cache; max-age=300\" \\\n  --header \"Cross-Origin-Embedder-Policy: require-corp\" \\\n  --header \"Cross-Origin-Opener-Policy: same-origin\" \\\n  --header \"Cross-Origin-Resource-Policy: cross-origin\" \\\n  --index index.html \\\n  .\n<\/code><\/pre>\n<p>You can use that (once installed) from the local <a href=\"https:\/\/github.com\/casey\/just\">justfile<\/a>, which (presently) has four semantically named actions:<\/p>\n<ul>\n<li>install-miniserve<\/li>\n<li>serve<\/li>\n<li>rsync<\/li>\n<li>github<\/li>\n<\/ul>\n<p>You&#8217;ll need to make path changes if you decide to use it.<\/p>\n<h2>FIN<\/h2>\n<p>I realize this is quite a bit to take in, and \u2014 as I keep saying \u2014\u00a0most folks will be better off using WebR in Shiny (when available) or Quarto.<\/p>\n<p>Lit gives us reactivity without the bloat that comes for the ride with Vue and React, so we get to stay in Vanilla JS land. You&#8217;ll notice there&#8217;s no &#8220;npm&#8221; or &#8220;bundling&#8221; or &#8220;rollup&#8221; here. You get to code in whatever environment you want, and serving WebR-powered pages is, then, as simple as an <code>rsync<\/code>.<\/p>\n<p>Drop issues at <a href=\"https:\/\/github.com\/hrbrmstr\/lit-webr\">the repo<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>See it live before reading! This is a Lit + WebR reproduction of the OG Shiny Demo App Lit is a javascript library that makes it a bit easier to work with Web Components, and is especially well-suited in reactive environments. My recent hack-y WebR experiments have been using Reef which is an even ligher-weight [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":13874,"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,851,867],"tags":[],"class_list":["post-13860","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-r","category-rust","category-webr"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.3 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Linking Lit&#039;s Lightweight Web Components And WebR For Vanilla JS Reactivity - 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\/25\/linking-lits-lightweight-web-components-and-webr-for-vanilla-js-reactivity\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Linking Lit&#039;s Lightweight Web Components And WebR For Vanilla JS Reactivity - rud.is\" \/>\n<meta property=\"og:description\" content=\"See it live before reading! This is a Lit + WebR reproduction of the OG Shiny Demo App Lit is a javascript library that makes it a bit easier to work with Web Components, and is especially well-suited in reactive environments. My recent hack-y WebR experiments have been using Reef which is an even ligher-weight [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/rud.is\/b\/2023\/03\/25\/linking-lits-lightweight-web-components-and-webr-for-vanilla-js-reactivity\/\" \/>\n<meta property=\"og:site_name\" content=\"rud.is\" \/>\n<meta property=\"article:published_time\" content=\"2023-03-25T19:57:47+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/03\/lit-webr-phones.png?fit=1582%2C1282&ssl=1\" \/>\n\t<meta property=\"og:image:width\" content=\"1582\" \/>\n\t<meta property=\"og:image:height\" content=\"1282\" \/>\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=\"11 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\\\/25\\\/linking-lits-lightweight-web-components-and-webr-for-vanilla-js-reactivity\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/2023\\\/03\\\/25\\\/linking-lits-lightweight-web-components-and-webr-for-vanilla-js-reactivity\\\/\"},\"author\":{\"name\":\"hrbrmstr\",\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/#\\\/schema\\\/person\\\/d7cb7487ab0527447f7fda5c423ff886\"},\"headline\":\"Linking Lit&#8217;s Lightweight Web Components And WebR For Vanilla JS Reactivity\",\"datePublished\":\"2023-03-25T19:57:47+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/2023\\\/03\\\/25\\\/linking-lits-lightweight-web-components-and-webr-for-vanilla-js-reactivity\\\/\"},\"wordCount\":1183,\"commentCount\":1,\"publisher\":{\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/#\\\/schema\\\/person\\\/d7cb7487ab0527447f7fda5c423ff886\"},\"image\":{\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/2023\\\/03\\\/25\\\/linking-lits-lightweight-web-components-and-webr-for-vanilla-js-reactivity\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/i0.wp.com\\\/rud.is\\\/b\\\/wp-content\\\/uploads\\\/2023\\\/03\\\/lit-webr-phones.png?fit=1582%2C1282&ssl=1\",\"articleSection\":[\"R\",\"Rust\",\"webr\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/rud.is\\\/b\\\/2023\\\/03\\\/25\\\/linking-lits-lightweight-web-components-and-webr-for-vanilla-js-reactivity\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/2023\\\/03\\\/25\\\/linking-lits-lightweight-web-components-and-webr-for-vanilla-js-reactivity\\\/\",\"url\":\"https:\\\/\\\/rud.is\\\/b\\\/2023\\\/03\\\/25\\\/linking-lits-lightweight-web-components-and-webr-for-vanilla-js-reactivity\\\/\",\"name\":\"Linking Lit's Lightweight Web Components And WebR For Vanilla JS Reactivity - rud.is\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/2023\\\/03\\\/25\\\/linking-lits-lightweight-web-components-and-webr-for-vanilla-js-reactivity\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/2023\\\/03\\\/25\\\/linking-lits-lightweight-web-components-and-webr-for-vanilla-js-reactivity\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/i0.wp.com\\\/rud.is\\\/b\\\/wp-content\\\/uploads\\\/2023\\\/03\\\/lit-webr-phones.png?fit=1582%2C1282&ssl=1\",\"datePublished\":\"2023-03-25T19:57:47+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/2023\\\/03\\\/25\\\/linking-lits-lightweight-web-components-and-webr-for-vanilla-js-reactivity\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/rud.is\\\/b\\\/2023\\\/03\\\/25\\\/linking-lits-lightweight-web-components-and-webr-for-vanilla-js-reactivity\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/2023\\\/03\\\/25\\\/linking-lits-lightweight-web-components-and-webr-for-vanilla-js-reactivity\\\/#primaryimage\",\"url\":\"https:\\\/\\\/i0.wp.com\\\/rud.is\\\/b\\\/wp-content\\\/uploads\\\/2023\\\/03\\\/lit-webr-phones.png?fit=1582%2C1282&ssl=1\",\"contentUrl\":\"https:\\\/\\\/i0.wp.com\\\/rud.is\\\/b\\\/wp-content\\\/uploads\\\/2023\\\/03\\\/lit-webr-phones.png?fit=1582%2C1282&ssl=1\",\"width\":1582,\"height\":1282,\"caption\":\"some descriptive text you can read at the linked url in the post. there's also a popup menu and a barplot.\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/2023\\\/03\\\/25\\\/linking-lits-lightweight-web-components-and-webr-for-vanilla-js-reactivity\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/rud.is\\\/b\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Linking Lit&#8217;s Lightweight Web Components And WebR For Vanilla JS Reactivity\"}]},{\"@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":"Linking Lit's Lightweight Web Components And WebR For Vanilla JS Reactivity - 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\/25\/linking-lits-lightweight-web-components-and-webr-for-vanilla-js-reactivity\/","og_locale":"en_US","og_type":"article","og_title":"Linking Lit's Lightweight Web Components And WebR For Vanilla JS Reactivity - rud.is","og_description":"See it live before reading! This is a Lit + WebR reproduction of the OG Shiny Demo App Lit is a javascript library that makes it a bit easier to work with Web Components, and is especially well-suited in reactive environments. My recent hack-y WebR experiments have been using Reef which is an even ligher-weight [&hellip;]","og_url":"https:\/\/rud.is\/b\/2023\/03\/25\/linking-lits-lightweight-web-components-and-webr-for-vanilla-js-reactivity\/","og_site_name":"rud.is","article_published_time":"2023-03-25T19:57:47+00:00","og_image":[{"width":1582,"height":1282,"url":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/03\/lit-webr-phones.png?fit=1582%2C1282&ssl=1","type":"image\/png"}],"author":"hrbrmstr","twitter_card":"summary_large_image","twitter_misc":{"Written by":"hrbrmstr","Est. reading time":"11 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/rud.is\/b\/2023\/03\/25\/linking-lits-lightweight-web-components-and-webr-for-vanilla-js-reactivity\/#article","isPartOf":{"@id":"https:\/\/rud.is\/b\/2023\/03\/25\/linking-lits-lightweight-web-components-and-webr-for-vanilla-js-reactivity\/"},"author":{"name":"hrbrmstr","@id":"https:\/\/rud.is\/b\/#\/schema\/person\/d7cb7487ab0527447f7fda5c423ff886"},"headline":"Linking Lit&#8217;s Lightweight Web Components And WebR For Vanilla JS Reactivity","datePublished":"2023-03-25T19:57:47+00:00","mainEntityOfPage":{"@id":"https:\/\/rud.is\/b\/2023\/03\/25\/linking-lits-lightweight-web-components-and-webr-for-vanilla-js-reactivity\/"},"wordCount":1183,"commentCount":1,"publisher":{"@id":"https:\/\/rud.is\/b\/#\/schema\/person\/d7cb7487ab0527447f7fda5c423ff886"},"image":{"@id":"https:\/\/rud.is\/b\/2023\/03\/25\/linking-lits-lightweight-web-components-and-webr-for-vanilla-js-reactivity\/#primaryimage"},"thumbnailUrl":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/03\/lit-webr-phones.png?fit=1582%2C1282&ssl=1","articleSection":["R","Rust","webr"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/rud.is\/b\/2023\/03\/25\/linking-lits-lightweight-web-components-and-webr-for-vanilla-js-reactivity\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/rud.is\/b\/2023\/03\/25\/linking-lits-lightweight-web-components-and-webr-for-vanilla-js-reactivity\/","url":"https:\/\/rud.is\/b\/2023\/03\/25\/linking-lits-lightweight-web-components-and-webr-for-vanilla-js-reactivity\/","name":"Linking Lit's Lightweight Web Components And WebR For Vanilla JS Reactivity - rud.is","isPartOf":{"@id":"https:\/\/rud.is\/b\/#website"},"primaryImageOfPage":{"@id":"https:\/\/rud.is\/b\/2023\/03\/25\/linking-lits-lightweight-web-components-and-webr-for-vanilla-js-reactivity\/#primaryimage"},"image":{"@id":"https:\/\/rud.is\/b\/2023\/03\/25\/linking-lits-lightweight-web-components-and-webr-for-vanilla-js-reactivity\/#primaryimage"},"thumbnailUrl":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/03\/lit-webr-phones.png?fit=1582%2C1282&ssl=1","datePublished":"2023-03-25T19:57:47+00:00","breadcrumb":{"@id":"https:\/\/rud.is\/b\/2023\/03\/25\/linking-lits-lightweight-web-components-and-webr-for-vanilla-js-reactivity\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/rud.is\/b\/2023\/03\/25\/linking-lits-lightweight-web-components-and-webr-for-vanilla-js-reactivity\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/rud.is\/b\/2023\/03\/25\/linking-lits-lightweight-web-components-and-webr-for-vanilla-js-reactivity\/#primaryimage","url":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/03\/lit-webr-phones.png?fit=1582%2C1282&ssl=1","contentUrl":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/03\/lit-webr-phones.png?fit=1582%2C1282&ssl=1","width":1582,"height":1282,"caption":"some descriptive text you can read at the linked url in the post. there's also a popup menu and a barplot."},{"@type":"BreadcrumbList","@id":"https:\/\/rud.is\/b\/2023\/03\/25\/linking-lits-lightweight-web-components-and-webr-for-vanilla-js-reactivity\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/rud.is\/b\/"},{"@type":"ListItem","position":2,"name":"Linking Lit&#8217;s Lightweight Web Components And WebR For Vanilla JS Reactivity"}]},{"@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\/lit-webr-phones.png?fit=1582%2C1282&ssl=1","jetpack_shortlink":"https:\/\/wp.me\/p23idr-3By","jetpack_likes_enabled":true,"jetpack-related-posts":[{"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":13860,"position":0},"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":13845,"url":"https:\/\/rud.is\/b\/2023\/03\/20\/webr-filesystem-and-reefr\/","url_meta":{"origin":13860,"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":13963,"url":"https:\/\/rud.is\/b\/2023\/04\/16\/start-creating-vanilla-js-webr-apps-with-less-inertia\/","url_meta":{"origin":13860,"position":2},"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":13773,"url":"https:\/\/rud.is\/b\/2023\/03\/09\/webr-is-here\/","url_meta":{"origin":13860,"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":13789,"url":"https:\/\/rud.is\/b\/2023\/03\/12\/almost-bare-bones-webr-starter-app\/","url_meta":{"origin":13860,"position":4},"title":"Almost Bare Bones WebR Starter App","author":"hrbrmstr","date":"2023-03-12","format":false,"excerpt":"Let's walk through how to set up a ~minimal HTML\/JS\/CSS + WebR-powered \"app\" 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\u2026","rel":"","context":"In &quot;R&quot;","block_context":{"text":"R","link":"https:\/\/rud.is\/b\/category\/r\/"},"img":{"alt_text":"screenshot of the example webr app showing a portion of mtcars","src":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/03\/Screenshot-2023-03-12-at-10.24.18.png?fit=1200%2C772&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/03\/Screenshot-2023-03-12-at-10.24.18.png?fit=1200%2C772&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/03\/Screenshot-2023-03-12-at-10.24.18.png?fit=1200%2C772&ssl=1&resize=525%2C300 1.5x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/03\/Screenshot-2023-03-12-at-10.24.18.png?fit=1200%2C772&ssl=1&resize=700%2C400 2x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/03\/Screenshot-2023-03-12-at-10.24.18.png?fit=1200%2C772&ssl=1&resize=1050%2C600 3x"},"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":13860,"position":5},"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":[]}],"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/posts\/13860","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=13860"}],"version-history":[{"count":0,"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/posts\/13860\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/media\/13874"}],"wp:attachment":[{"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/media?parent=13860"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/categories?post=13860"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/tags?post=13860"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}