<?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: Konnor Rogers</title>
    <description>The latest articles on DEV Community by Konnor Rogers (@konnorrogers).</description>
    <link>https://dev.to/konnorrogers</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%2F361414%2F6e8b8c96-5d0c-497a-a2ce-f041cbc1da99.jpeg</url>
      <title>DEV Community: Konnor Rogers</title>
      <link>https://dev.to/konnorrogers</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/konnorrogers"/>
    <language>en</language>
    <item>
      <title>Pulling your dev.to posts down locally</title>
      <dc:creator>Konnor Rogers</dc:creator>
      <pubDate>Tue, 20 Jun 2023 21:30:52 +0000</pubDate>
      <link>https://dev.to/konnorrogers/pulling-your-devto-posts-down-locally-565g</link>
      <guid>https://dev.to/konnorrogers/pulling-your-devto-posts-down-locally-565g</guid>
      <description>&lt;p&gt;Alright kids! Strap in! This is kind of a meta post since I'm writing it here on dev.to, but I'm about to show you how I pulled all my writings on dev.to down locally into a new &lt;a href="https://bridgetownrb.com"&gt;Bridgetown&lt;/a&gt; site I made! (Which may feature a blog...who knows...)&lt;/p&gt;

&lt;p&gt;First step, create a file to run your script. I'll be using Ruby here, but feel free to use whatever language you fancy.&lt;/p&gt;

&lt;p&gt;First, let's grab all the articles I've written. Feel free to change &lt;code&gt;username&lt;/code&gt; to match your dev.to username.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env ruby&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"json"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"net/http"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"time"&lt;/span&gt;

&lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"konnorrogers"&lt;/span&gt;

&lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://dev.to/api/articles?username
=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Done right?!&lt;/p&gt;

&lt;p&gt;Not quite! We still need to loop through all of our articles and gather the content we need. To help with exploring the dev.to API, you can write the return JSON to a file like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./articles.json"&lt;/span&gt;
&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pretty_generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://dev.to/api/articles?username
=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, I already did this part and know what I need for my Bridgetown site, but feel free to use the snippet above for exploring the API. We use &lt;code&gt;pretty_generate&lt;/code&gt; on the JSON so its easier to read.&lt;/p&gt;

&lt;p&gt;Anyways, when looking through the data returned we don't get back the &lt;code&gt;body_markdown&lt;/code&gt; which has the raw markdown for our posts. What we need to do is loop through all of our "articles" and then grab the &lt;code&gt;body_markdown&lt;/code&gt; property for each one.&lt;/p&gt;

&lt;p&gt;Here we go:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="c1"&gt;# turn anything thats not a number or letter into a hyphen, then squash reoccurring hypens into 1&lt;/span&gt;
  &lt;span class="c1"&gt;# Example: &lt;/span&gt;
  &lt;span class="c1"&gt;#   "How can I pull my data from dev.to?"&lt;/span&gt;
  &lt;span class="c1"&gt;#=&amp;gt; "how-can-i-pull-my-data-from-dev-to"&lt;/span&gt;
  &lt;span class="n"&gt;file_title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;downcase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/[^0-9a-z]/i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"-"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/-+/&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"-"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;# Produces a string like this: 2023-06-20 17:24:40 -0400&lt;/span&gt;
  &lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"published_at"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;

  &lt;span class="c1"&gt;# Pulls only yyyy-mm-dd&lt;/span&gt;
  &lt;span class="n"&gt;file_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;" "&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;

  &lt;span class="c1"&gt;# produces a path like this:&lt;/span&gt;
  &lt;span class="c1"&gt;# "src/_posts/2023-06-20-pulling-your-devto-posts-down-locally.md"&lt;/span&gt;
  &lt;span class="n"&gt;file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"src/_posts/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;file_date&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;file_title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.md"&lt;/span&gt;

  &lt;span class="c1"&gt;# don't waste an API call!&lt;/span&gt;
  &lt;span class="k"&gt;next&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="c1"&gt;# Comma separated string&lt;/span&gt;
  &lt;span class="n"&gt;categories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="n"&gt;article_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;article_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://dev.to/api/articles&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;article_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;# One second seems to be the secret sauce to get around rate limiting.&lt;/span&gt;
  &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

  &lt;span class="c1"&gt;# We can't get the info we need from the initial API call so we need to go to the article_url&lt;/span&gt;
  &lt;span class="c1"&gt;# to get the raw markdown.&lt;/span&gt;
  &lt;span class="n"&gt;article_json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;article_url&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="n"&gt;body_markdown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;article_json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"body_markdown"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"---&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"title: "&lt;/span&gt;&lt;span class="c1"&gt;#{title}\n\""&lt;/span&gt;
  &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"categories: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;categories&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"date: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"description: "&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="p"&gt;\&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;  &lt;span class="c1"&gt;#{description}\n\""&lt;/span&gt;
  &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"---&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;body_markdown&lt;/span&gt;

  &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;mode: &lt;/span&gt;&lt;span class="s2"&gt;"w"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's run our script and watch the magic happen. This may take a while because dev.to rate limits to what seems to be about 1 API call per second, so if you have say 60 posts, it'll take roughly 1minute to gather all your files.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ruby my-script.rb&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And here's what it pulled down for me!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/_posts/2023-06-13-inserting-a-string-on-the-first-line-of-every-file-with-vim.md
src/_posts/2023-06-07-maintain-scroll-position-in-turbo-without-data-turbo-permanent.md
src/_posts/2023-05-30-button-to-vs-link-to-and-the-pitfalls-of-data-turbo-method.md
src/_posts/2023-05-22-rails-frontend-bundling-which-one-should-i-choose.md
src/_posts/2023-05-22-revisiting-box-sizing-best-practices.md
src/_posts/2023-04-08-how-to-keep-a-persistent-class-on-a-litelement.md
src/_posts/2022-11-22-jest-vitest-and-webcomponents.md
src/_posts/2022-10-20-actiontext-all-the-ways-to-render-an-actiontext-attachment.md
src/_posts/2022-10-10-actiontext-safe-listing-attributes-and-tags.md
src/_posts/2022-10-04-actiontext-modify-the-rendering-of-activestorage-attachments.md
src/_posts/2022-07-20-why-we-still-bundle-with-http-2-in-2022.md
src/_posts/2022-04-08-testing-scopes-with-rails.md
src/_posts/2022-04-07-adding-additional-actions-to-trix.md
src/_posts/2022-03-13-converting-a-callback-to-a-promise.md
src/_posts/2022-03-10-escaping-the-traditional-rails-form.md
src/_posts/2022-02-21-adding-text-alignment-to-trix.md
src/_posts/2022-01-29-modifying-the-default-toolbar-in-trix.md
src/_posts/2022-01-29-exploring-trix.md
src/_posts/2021-11-30-cross-browser-vertical-slider-using-input-type-range.md
src/_posts/2021-11-01-rebuilding-activestorage-first-impressions.md
src/_posts/2021-10-27-why-jest-is-not-for-me.md
src/_posts/2021-10-06-frontend-bundler-braindump.md
src/_posts/2021-07-08-writing-code-block-highlighting-to-a-css-file-with-rouge.md
src/_posts/2021-07-06-creating-reusable-flashes-in-rails-using-shoelace.md
src/_posts/2021-07-03-pulling-down-somebody-s-fork-with-git.md
src/_posts/2021-07-02-fixing-fatal-error-ineffective-mark-compacts-near-heap-limit-allocation-failed-javascript-heap-out-of-memory-in-webpacker.md
src/_posts/2021-07-02-migrating-hls-videos-to-mp4-format-in-rails.md
src/_posts/2021-06-25-querying-activestorage-attachments.md
src/_posts/2021-05-25-case-switch-statement-in-ruby.md
src/_posts/2021-05-15-arel-notes.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Best of luck and hopefully this gives you some motivation to dust off your self-hosted blog! I personally have been writing on dev.to because my old blog site is a 4 year old Gatsby site I have exactly 0 hope of ever getting running again. So here's to new beginnings! 🥂&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>bridgetownrb</category>
      <category>ruby</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Inserting a string on the first line of every file with Vim</title>
      <dc:creator>Konnor Rogers</dc:creator>
      <pubDate>Tue, 13 Jun 2023 19:15:30 +0000</pubDate>
      <link>https://dev.to/konnorrogers/inserting-a-string-at-the-beginning-of-every-file-with-vim-4og3</link>
      <guid>https://dev.to/konnorrogers/inserting-a-string-at-the-beginning-of-every-file-with-vim-4og3</guid>
      <description>&lt;p&gt;Alright here it goes, I needed to add a header to all files. There are roughly ~72 files and I didn't want to do it by hand. The header in question was for test files in &lt;a href="https://shoelace.style"&gt;Shoelace&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;I stumbled across this StackOverflow link:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stackoverflow.com/questions/30541582/how-do-i-insert-the-same-line-into-multiple-files-using-vim"&gt;https://stackoverflow.com/questions/30541582/how-do-i-insert-the-same-line-into-multiple-files-using-vim&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Which was 99% of what I wanted, but didnt show how to use "put" without using a register.&lt;/p&gt;

&lt;p&gt;So here's the magic few commands that saved me a bunch of time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:args ./**/*.test.ts
:argdo 1put! = 'import \"../../../dist/shoelace.js\"' | write | update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first line &lt;code&gt;:args ./**/*.test.ts&lt;/code&gt; tells us what files we want to look at. The second line: &lt;code&gt;:argdo 1put! = 'import \"../../../dist/shoelace.js\"' | write | update&lt;/code&gt; says: "On the first line, put this statement above whatever is already on the first line, save the file, then update it in Vim.&lt;/p&gt;

&lt;p&gt;That's all I got, mostly saving this for future me who may need this!&lt;/p&gt;

</description>
      <category>vim</category>
      <category>programming</category>
    </item>
    <item>
      <title>Maintain scroll position in Turbo without data-turbo-permanent</title>
      <dc:creator>Konnor Rogers</dc:creator>
      <pubDate>Wed, 07 Jun 2023 20:44:01 +0000</pubDate>
      <link>https://dev.to/konnorrogers/maintain-scroll-position-in-turbo-without-data-turbo-permanent-2b1i</link>
      <guid>https://dev.to/konnorrogers/maintain-scroll-position-in-turbo-without-data-turbo-permanent-2b1i</guid>
      <description>&lt;p&gt;Alright, this will be short and sweet for future me.&lt;/p&gt;

&lt;p&gt;Maintaining scroll position is notoriously painful.&lt;/p&gt;

&lt;p&gt;Some articles like this have you add &lt;code&gt;data-turbo-permanent&lt;/code&gt;: &lt;a href="https://dev.to/mikerogers0/persist-scroll-positions-with-hotwire-turbo-1ihk"&gt;https://dev.to/mikerogers0/persist-scroll-positions-with-hotwire-turbo-1ihk&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Why not &lt;code&gt;data-turbo-permanent&lt;/code&gt;? Well, in our case we had a sidebar with a highlighted link for the current page, which means link clicks allowed for updating the highlighted current link. There were some workarounds we could have done, but decided not to.&lt;/p&gt;

&lt;p&gt;There is also this GitHub issue which has a ton of workarounds:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/hotwired/turbo/issues/37"&gt;https://github.com/hotwired/turbo/issues/37&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are some snippets in there that are pretty close to this. Here's what I used recently that worked well. Here's what I came up with that worked for me.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Turbo&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@hotwired/turbo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollPositions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollPositions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;preserveScroll&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[data-preserve-scroll]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;scrollPositions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollTop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;restoreScroll&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[data-preserve-scroll]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollTop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;scrollPositions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt; 

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newBody&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;
  &lt;span class="c1"&gt;// event.detail.newBody is the body element to be swapped in.&lt;/span&gt;
  &lt;span class="c1"&gt;// https://turbo.hotwired.dev/reference/events&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newBody&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[data-preserve-scroll]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollTop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;scrollPositions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;turbo:before-cache&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;preserveScroll&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;turbo:before-render&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;restoreScroll&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;turbo:render&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;restoreScroll&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are 2 key things to note. Every element must have a unique ID, and every element must have a &lt;code&gt;data-preserve-scroll&lt;/code&gt; on it. Like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;nav&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"sidebar"&lt;/span&gt; &lt;span class="na"&gt;data-preserve-scroll&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- stuff --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Happy hunting!&lt;/p&gt;

&lt;p&gt;EDIT: The one downside to this approach is I've noticed a brief flicker in Safari / Chrome. No flicker in FF. Perhaps a Turbo Transition, or using data-turbo-permanent could remove the flicker.&lt;/p&gt;

&lt;p&gt;EDIT 2: Fixed the flicker. Article updated.&lt;/p&gt;

</description>
      <category>turbo</category>
      <category>rails</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>button_to vs link_to and the pitfalls of data-turbo-method</title>
      <dc:creator>Konnor Rogers</dc:creator>
      <pubDate>Tue, 30 May 2023 18:49:01 +0000</pubDate>
      <link>https://dev.to/konnorrogers/buttonto-vs-linkto-and-the-pitfalls-of-data-turbo-method-3pe4</link>
      <guid>https://dev.to/konnorrogers/buttonto-vs-linkto-and-the-pitfalls-of-data-turbo-method-3pe4</guid>
      <description>&lt;p&gt;If you're familiar with Turbo, or even &lt;a href="https://htmx.org/"&gt;HTMX&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll see this pattern come up frequently.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/logout"&lt;/span&gt; &lt;span class="na"&gt;data-turbo-method=&lt;/span&gt;&lt;span class="s"&gt;"delete"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Logout&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;data-hx-delete=&lt;/span&gt;&lt;span class="s"&gt;"/logout"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Logout&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before we get into the difference, checkout &lt;a class="mentioned-user" href="https://dev.to/excid3"&gt;@excid3&lt;/a&gt; 's video on button_to vs link_to: &lt;a href="https://gorails.com/episodes/link_to-vs-button_to-in-rails?autoplay=1"&gt;https://gorails.com/episodes/link_to-vs-button_to-in-rails?autoplay=1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's dissect these one by one and checkout the issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turbo anchors
&lt;/h2&gt;

&lt;p&gt;We'll start with the Turbo version.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/logout"&lt;/span&gt; &lt;span class="na"&gt;data-turbo-method=&lt;/span&gt;&lt;span class="s"&gt;"delete"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Logout&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  JS hasn't loaded
&lt;/h3&gt;

&lt;p&gt;The problem with this is even though you're writing HTML, there is no such thing as a "delete link" in regular ole HTML. This link will fire a &lt;code&gt;GET&lt;/code&gt; request if a user manages to click the link before your JavaScript loads.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accessibility
&lt;/h3&gt;

&lt;p&gt;Buttons and links tend to get separated by screenreaders. Buttons tend to get lumped under form controls, whereas links are put together with navigation.&lt;/p&gt;

&lt;p&gt;In reality, to get the screenreader to read the button similarly, you would need to do something like this to get the browser to read it a little easier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/logout"&lt;/span&gt; &lt;span class="na"&gt;data-turbo-method=&lt;/span&gt;&lt;span class="s"&gt;"delete"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Logout&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Done right?!&lt;/p&gt;

&lt;p&gt;Not really. First rule of ARIA is don't use ARIA. Anchors have different semantics than buttons. In fact, its not even technically possible to disable anchors with regular HTML!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- This is still clickable and doesn't get disabled --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/blah"&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Logout&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So if for some reason you wanted to disable that anchor, it wouldn't be possible without JavaScript to intercept clicks! Even further, similar shortcuts between button vs anchor behave differently!&lt;/p&gt;

&lt;p&gt;If I &lt;code&gt;ctrl+click&lt;/code&gt; a button, it will still send my form and log me out, because it doesn't need JavaScript. If I &lt;code&gt;ctrl+click&lt;/code&gt; the above link, the browser wont be able to send the DELETE request, and instead issue a &lt;code&gt;GET&lt;/code&gt; request to "/logout" and it will feel broken to a user.&lt;/p&gt;

&lt;h2&gt;
  
  
  The HTMX example
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;data-hx-delete=&lt;/span&gt;&lt;span class="s"&gt;"/logout"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Logout&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem with this is that an &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; without an &lt;code&gt;href&lt;/code&gt; will not be clickable and not within the accessibility tree of focusable elements.&lt;/p&gt;

&lt;p&gt;Okay so lets fix it!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"javascript: void 0;"&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;data-hx-delete=&lt;/span&gt;&lt;span class="s"&gt;"/logout"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Logout&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;tabindex=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;data-hx-delete=&lt;/span&gt;&lt;span class="s"&gt;"/logout"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Logout&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please don't do this. It's merely an example. Again, this hits similar pitfalls as Turbo links. If your JS fails to load, if a user &lt;code&gt;ctrl+clicks&lt;/code&gt;, if JS hasn't finished loading, all of those situations will get this to appear broken. This doesn't even address anything to do with accessibility because that is a whole other can of worms. Just because you set a &lt;code&gt;role="button"&lt;/code&gt; on there, it still doesn't behave the same because its not attached to a &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; so many assistive technologies will not treat it as a &lt;code&gt;&amp;lt;button type="submit"&amp;gt;&lt;/code&gt; which is the default behavior of a &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; within a form.&lt;/p&gt;

&lt;h2&gt;
  
  
  Okay? But forms only support GET / POST.
&lt;/h2&gt;

&lt;p&gt;Yes. &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; only supports GET / POST. This is true. It will depend on your server. Look up "method spoofing" or "method masking".&lt;/p&gt;

&lt;p&gt;I wrote a primer on this in mrujs: &lt;a href="https://mrujs.com/references/understanding-method-masking"&gt;https://mrujs.com/references/understanding-method-masking&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here's Laravel's docs on method spoofing. &lt;a href="https://laravel.com/docs/10.x/routing#form-method-spoofing"&gt;https://laravel.com/docs/10.x/routing#form-method-spoofing&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And heres the rough HTML you would use in Laravel or Rails:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"/logout"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"_method"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"delete"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&amp;gt;&lt;/span&gt;Logout&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or if you want to get fancy you can have the form and the button in two different places!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"logout-form"&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"/logout"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"_method"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"delete"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Other stuff here --&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;form=&lt;/span&gt;&lt;span class="s"&gt;"logout-form"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Logout&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're using Rails form helpers, the hidden input is handled for you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form_with&lt;/span&gt; &lt;span class="ss"&gt;model: &lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt; &lt;span class="s2"&gt;"Logout"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look mom! No JavaScript!&lt;/p&gt;

&lt;h2&gt;
  
  
  TLDR
&lt;/h2&gt;

&lt;p&gt;Please, just use buttons inside of forms to do non-GET requests. Yes, it may require more work to style it to look like a link, but I promise your users will thank you.&lt;/p&gt;

</description>
      <category>turbo</category>
      <category>hotwire</category>
      <category>rails</category>
      <category>htmx</category>
    </item>
    <item>
      <title>Rails Frontend Bundling - Which one should I choose?</title>
      <dc:creator>Konnor Rogers</dc:creator>
      <pubDate>Mon, 22 May 2023 16:46:07 +0000</pubDate>
      <link>https://dev.to/konnorrogers/rails-frontend-bundling-which-one-should-i-choose-9gp</link>
      <guid>https://dev.to/konnorrogers/rails-frontend-bundling-which-one-should-i-choose-9gp</guid>
      <description>&lt;h2&gt;
  
  
  Propshaft? Or Sprockets?
&lt;/h2&gt;

&lt;p&gt;If you use the following options: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ImportMaps&lt;/li&gt;
&lt;li&gt;JSBundling-Rails&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;you'll be using either Sprockets or Propshaft. The default that Rails ships with is Sprockets. &lt;/p&gt;

&lt;p&gt;If you're in a new application, I would try to start with Propshaft if possible. If you're attempting to migrate an existing application, I would probably go with Sprockets for compatibility with gems that may ship their own assets.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Propshaft?
&lt;/h3&gt;

&lt;p&gt;Propshaft is much more streamlined, isn't as heavy handed as Sprockets, and has much more predictable behavior. It doesn't ship with as many features as Sprockets, but in this case, that's a feature.&lt;/p&gt;

&lt;p&gt;We won't get into caching bugs with Sprockets, but I have noticed when things are FUBAR in local dev with Sprockets, &lt;code&gt;rails assets:clobber&lt;/code&gt; is your friend.&lt;/p&gt;

&lt;h2&gt;
  
  
  ImportMaps
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/WICG/import-maps"&gt;https://github.com/WICG/import-maps&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What are importmaps?
&lt;/h3&gt;

&lt;p&gt;At their core, importmaps are essentially aliases.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@hotwired/turbo-rails&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;gets expanded to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://{{cdn}}/@hotwired/turbo-rails&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We won't get into the nitty gritty, but this is the basics of ImportMaps. ImportMaps also only apply to JavaScript files. You cannot import &lt;code&gt;svg&lt;/code&gt;, &lt;code&gt;png&lt;/code&gt;, &lt;code&gt;css&lt;/code&gt;, etc files like people have come to expect from bundlers like Webpack. You can only import &lt;code&gt;.js&lt;/code&gt; files.&lt;/p&gt;

&lt;p&gt;To import css files, generally the easiest way is to insert a &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; tag that points to a prebuilt &lt;code&gt;.css&lt;/code&gt; file from a CDN or locally.&lt;/p&gt;

&lt;p&gt;Import Attributes (Import assertions) are coming, but aren't here yet, and it'll be interesting to see how they map to ImportMaps.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tc39/proposal-import-attributes"&gt;https://github.com/tc39/proposal-import-attributes&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why ImportMaps?
&lt;/h3&gt;

&lt;p&gt;ImportMaps are the least invasive of all the tools. It also has the least number of features. Things like transpilation for older browsers, dependency graph management, and dead code elimination (tree-shaking) do not exist in importmaps.&lt;/p&gt;

&lt;p&gt;If you plan to stick to the default packages Rails provides and don't plan to add much more JavaScript, ImportMaps will be a great tool for you. If you plan to add packages from NPM, or add a lot more JavaScript, or leverage any more advanced features ImportMaps may not be right for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Notes on ImportMaps
&lt;/h3&gt;

&lt;p&gt;ImportMaps are getting near universal adoption in newer browsers. And the polyfill for importmaps is really good. However, the timing of your JavaScript is different between browsers.&lt;/p&gt;

&lt;p&gt;The order of operations, or when the JavaScript executes, is different between all browsers. Meaning you will need to be much more careful between browsers because of timing issues. Without being too specific, the reason for this is because the timing and execution of JS Modules has never been fully defined. Each browser is free to implement the parsing, evaluation, and execution stage of JS Modules however they see fit. This is not a problem for bundlers because they are able to make more guarantees when they parse + build your JavaScript for you.&lt;/p&gt;

&lt;p&gt;The other problem with importmaps is some NPM packages may not be properly compiled for use in browsers from a CDN without a bundling step. These dependencies may have non-standard syntax which the browser may not be able to handle. The other problem is with nested dependencies. If the CDN doesn't inline dependencies, you may be stuck in a scenario where you have incompatible versions of the same dependency. If the CDN does inline dependencies, you're now shipping duplicate code.&lt;/p&gt;

&lt;h2&gt;
  
  
  JSBundling-Rails
&lt;/h2&gt;

&lt;p&gt;Alright, you've decided that ImportMaps don't provide enough features, or maybe you have a specific package that may be problematic. JSBundling-Rails actually has generators for Rollup, Webpack, and ESBuild&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/rails/jsbundling-rails/tree/main/lib/install"&gt;https://github.com/rails/jsbundling-rails/tree/main/lib/install&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But we'll focus on the ESBuild portion specifically. It's important to note that JSBundling-Rails is a few rakes tasks, it does not handle your actual asset URLs and paths. Asset URLs and paths will still be handled by Sprockets / Propshaft. JSBundling-Rails basically compiles your files into &lt;code&gt;app/assets/builds&lt;/code&gt; and then the Rails asset pipelines picks up the unhashed files and will hash them for you. So even if you're using ESBuild via JSBundling-Rails, you're still using either Sprockets or Propshaft.&lt;/p&gt;

&lt;h3&gt;
  
  
  ESBuild
&lt;/h3&gt;

&lt;p&gt;ESBuild is a great middle ground between Webpacker / Shakapacker and ImportMaps. ESBuild provides transpilation, tree-shaking, scope-hoisting, non-js imports, bare module imports, and much more.&lt;/p&gt;

&lt;p&gt;ESBuild is also written in Go and is crazy fast compared to other bundlers. &lt;/p&gt;

&lt;p&gt;** For the majority of applications ESBuild is probably the best option **&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not ESBuild?
&lt;/h2&gt;

&lt;p&gt;There are very few reasons not to use ESBuild. But there are a couple.&lt;/p&gt;

&lt;p&gt;There's a whole list here: &lt;a href="https://esbuild.github.io/content-types/#javascript-caveats"&gt;https://esbuild.github.io/content-types/#javascript-caveats&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But I'll simplify it for you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IE11. If you need to support ES5 browsers, but are writing ES6+ syntax (very rare these days), then ESBuild is not the tool for you.&lt;/li&gt;
&lt;li&gt;Problematic dependencies. Some dependencies like &lt;a href="https://ckeditor.com/"&gt;CKEditor&lt;/a&gt; are designed specifically to work with Webpacker and won't work with other tools.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Webpacker / Shakapacker
&lt;/h2&gt;

&lt;p&gt;Webpacker (now Shakapacker) is the oldest of the current frontend bundling tools. It's very robust and solid...and also confusing. This is the nuclear option if none of the other frontend bundling options will work for you. It will handle just about anything you throw at it (as long as you set it up properly). I don't have much more to add here, we've all dealt with Webpacker for the last few years.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vite
&lt;/h2&gt;

&lt;p&gt;Vite, in particular, &lt;a href="https://vite-ruby.netlify.app/"&gt;ViteRuby&lt;/a&gt; is a solid option. It sits between ESBuild and Webpacker, and if you're looking at Webpacker, Vite may actually be a better option for you. It is a very solid option, and I've enjoyed using Vite personally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Caveats
&lt;/h3&gt;

&lt;p&gt;Vite uses ESBuild in development, and Rollup for production. Occasionally this can cause issues because now production and development can be different and it is worth calling out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honorable Mention
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Parcel 2
&lt;/h3&gt;

&lt;p&gt;Parcel 2 is a very solid step up from Parcel 1. There are currently no Rails integrations for it, but it is very close to Vite in terms of developer experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;For &lt;strong&gt;new&lt;/strong&gt; applications ESBuild + Propshaft is probably the best option.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;existing&lt;/strong&gt; applications ESBuild + Sprockets is probably the best option.&lt;/p&gt;

&lt;p&gt;Importmaps are good for small projects / prototypes.&lt;/p&gt;

&lt;p&gt;Vite is good for more powerful apps that may be leveraging React, Vue, Svelte, etc. and would like a more robust experience.&lt;/p&gt;

&lt;p&gt;Webpacker / Shakapacker is there if you really need to support old browsers, legacy syntax, or have a problematic dependency.&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>rails</category>
      <category>webdev</category>
      <category>ruby</category>
    </item>
    <item>
      <title>Revisiting box-sizing best practices</title>
      <dc:creator>Konnor Rogers</dc:creator>
      <pubDate>Mon, 22 May 2023 04:50:13 +0000</pubDate>
      <link>https://dev.to/konnorrogers/revisiting-box-sizing-best-practices-3del</link>
      <guid>https://dev.to/konnorrogers/revisiting-box-sizing-best-practices-3del</guid>
      <description>&lt;p&gt;We've all googled "best way to set &lt;code&gt;box-sizing: border-box;&lt;/code&gt;" and come across this fun article from CSS Tricks about setting box sizing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://css-tricks.com/box-sizing/"&gt;https://css-tricks.com/box-sizing/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you haven't read it, let me spare you some time.&lt;/p&gt;

&lt;p&gt;Here's the "recommended" approach due to increased flexibility.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://css-tricks.com/box-sizing/#aa-universal-box-sizing-with-inheritance"&gt;https://css-tricks.com/box-sizing/#aa-universal-box-sizing-with-inheritance&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;html&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;box-sizing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;border-box&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;*,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nd"&gt;:before&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nd"&gt;:after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;box-sizing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;inherit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While this works if all of your elements are in the light DOM, tonight I discovered a fun bug plaguing my site.&lt;/p&gt;

&lt;p&gt;If you have an element that is "slotted" into a custom element's shadow DOM, it will "inherit" the &lt;code&gt;box-sizing&lt;/code&gt; of the slot element.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;MyElement&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;connectedCallback&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attachShadow&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;open&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

      &lt;span class="c1"&gt;// These slots are automatically &lt;/span&gt;
      &lt;span class="c1"&gt;// "box-sizing: content-box;" &lt;/span&gt;
      &lt;span class="c1"&gt;// so this default slot causes all its children to have&lt;/span&gt;
      &lt;span class="c1"&gt;// the same box-sizing properties...&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shadowRoot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;slot&amp;gt;&amp;lt;/slot&amp;gt;`&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customElements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-custom-element&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MyElement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;html&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;box-sizing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;border-box&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="o"&gt;*,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nd"&gt;:after&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nd"&gt;:before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;box-sizing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;inherit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;my-custom-element&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;My box sizing is "content-box"&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/my-custom-element&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;My box sizing is "border-box"&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you'd prefer, I also made a CodePen reproducing this issue.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://codepen.io/paramagicdev/pen/abRPJjB?editors=1111"&gt;https://codepen.io/paramagicdev/pen/abRPJjB?editors=1111&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The "fix" is to use the "universal" box-sizing selector.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="o"&gt;*,*&lt;/span&gt;&lt;span class="nd"&gt;:after&lt;/span&gt;&lt;span class="o"&gt;,*&lt;/span&gt;&lt;span class="nd"&gt;:before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;box-sizing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;border-box&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, I don't know if the inherited box-sizing is a bug, but it sure does feel like a bug. But, at least there is a workaround. I tested in Firefox, Chrome, Safari, and Edge and got the same result in all 4. Modifying the &lt;code&gt;box-sizing&lt;/code&gt; of the parent slot element does indeed fix it like you would expect.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>css</category>
      <category>webcomponents</category>
    </item>
    <item>
      <title>How to keep a persistent class on a LitElement</title>
      <dc:creator>Konnor Rogers</dc:creator>
      <pubDate>Sat, 08 Apr 2023 02:56:40 +0000</pubDate>
      <link>https://dev.to/konnorrogers/how-to-keep-a-persistent-class-on-a-litelement-35io</link>
      <guid>https://dev.to/konnorrogers/how-to-keep-a-persistent-class-on-a-litelement-35io</guid>
      <description>&lt;p&gt;When working with lit, sometimes you want the host element to have a persistent class name. A good example is if I were using Shoelace I'd want my elements to look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;sl-button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"sl-button"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/sl-button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That way if a user registers the button under another namespace, they can still target all instances with &lt;code&gt;.sl-button {}&lt;/code&gt; in their CSS, or by using querySelectors. There are a number of use-cases, but lets forget about the "why", and focus on the how.&lt;/p&gt;

&lt;p&gt;Here is how I found the most effective way to keep a persistent class on a LitElement.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LitElement&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;MyElement&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;LitElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;properties&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;reflect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;connectedCallback&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connectedCallback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-element&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;willUpdate&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;changedProperties&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;changedProperties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;class&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-element&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or for you folks out there using decorators:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LitElement&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;property&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lit/decorators.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;MyElement&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;LitElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;property&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;reflect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt;

  &lt;span class="nx"&gt;connectedCallback&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connectedCallback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-element&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;willUpdate&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;changedProperties&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;changedProperties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;class&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-element&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm sure there's another way to do this, but this has been the way that's worked for me!&lt;/p&gt;

</description>
      <category>webcomponents</category>
      <category>lit</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Jest, Vitest, and WebComponents</title>
      <dc:creator>Konnor Rogers</dc:creator>
      <pubDate>Tue, 22 Nov 2022 17:46:45 +0000</pubDate>
      <link>https://dev.to/konnorrogers/jest-vitest-and-webcomponents-19lk</link>
      <guid>https://dev.to/konnorrogers/jest-vitest-and-webcomponents-19lk</guid>
      <description>&lt;h2&gt;
  
  
  Purpose
&lt;/h2&gt;

&lt;p&gt;Jest and the newer Vitest are inextricably linked with frontend testing tools. While I personally do not care for these tools, its important to understand a lot of projects are tied to them. As a result, even though their web component support / DOM mocking isn't the best, we should at least look at what it does support.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shadow Root rendering
&lt;/h2&gt;

&lt;p&gt;Jest underwent a major revamp and received support for web components around version &lt;a href="https://github.com/facebook/jest/blob/main/CHANGELOG.md#2650"&gt;26.5.0&lt;/a&gt; when it introduced JSDOM version &lt;a href="https://github.com/jsdom/jsdom/blob/master/Changelog.md#1620"&gt;16.2.0&lt;/a&gt; which added the ability to render shadow roots. Prior to this version, rendering of shadowRoots would not work as expected, so its important if your project is using shadowRoots, you make sure to check what version of JSDOM / Jest you're using.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mocking more of the DOM
&lt;/h2&gt;

&lt;p&gt;This major revamp also included a number of mocks for built-in &lt;br&gt;
browser functions such as MutationObserver, document.createRange, and many others.&lt;/p&gt;

&lt;p&gt;However, there are still some notable missing functionalities such as &lt;code&gt;matchMedia&lt;/code&gt;, &lt;code&gt;IntersectionObserver&lt;/code&gt;, &lt;code&gt;ResizeObserver&lt;/code&gt;, and more that I'm sure I haven't bumped into. So if your WebComponents use these, make sure to mock those too!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom"&gt;https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Jest transforms
&lt;/h2&gt;

&lt;p&gt;I don't believe Vitest has this issue due to using ESM transforms via Vite, but when using Jest its important to remember that it expects CommonJS files, but will not transform node_modules for you. Many WebComponent libraries do not ship CJS files. So if you have an NPM package that ships ESM, you'll want to tell Jest to transform it via the &lt;code&gt;"transformIgnorePatterns"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example, if I were using Shoelace, I'd do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;"transformIgnorePatterns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"node_modules/?!(@shoelace-style)"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;for more on transformIgnorePatterns, check this out:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://jestjs.io/docs/tutorial-react-native#transformignorepatterns-customization"&gt;https://jestjs.io/docs/tutorial-react-native#transformignorepatterns-customization&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Focus order and keyboard events
&lt;/h2&gt;

&lt;p&gt;I've noticed focus order and keyboard events can get messed up in JSDOM. Here's a spot where I don't have a fix. I've noticed that JSDOM does not always behave as expected with these and the only thing I can chalk it up to is that its dispatching events rather than sending the real actions to a browser.&lt;/p&gt;

&lt;p&gt;JSDOM and Jest / Vitest come with so many caveats for WebComponents and browser testing in general, I can't recommend them in good faith. Nobody browses the web in JSDOM. If you hook up a custom runner to Jest that uses a real browser, now you're cookin'! I guess all this to say most of my beef lies with JSDOM and not necessarily Jest itself...&lt;/p&gt;

&lt;p&gt;There are other projects like &lt;a href="https://playwright.dev/"&gt;Playwright&lt;/a&gt; and &lt;a href="https://modern-web.dev/docs/test-runner/overview/"&gt;Web Test Runner&lt;/a&gt; which will give you a real browser to work with and more closely mirror how your users will use your components.&lt;/p&gt;

</description>
      <category>jest</category>
      <category>vitest</category>
      <category>shadowdom</category>
      <category>webcomponents</category>
    </item>
    <item>
      <title>ActionText: All the ways to render an ActionText Attachment</title>
      <dc:creator>Konnor Rogers</dc:creator>
      <pubDate>Thu, 20 Oct 2022 23:11:46 +0000</pubDate>
      <link>https://dev.to/konnorrogers/all-the-ways-to-render-an-actiontext-attachment-1jo4</link>
      <guid>https://dev.to/konnorrogers/all-the-ways-to-render-an-actiontext-attachment-1jo4</guid>
      <description>&lt;p&gt;There's so many ways to render an ActionText attachment, we can change the &lt;code&gt;app/views/active_storage/blobs/_blob.html.erb&lt;/code&gt; as we saw in a previous post. This will apply to all ActiveStorage blobs. However, you may want more fine grained control for things like CustomAttachments. You'll also find some of the ways to change a models ActionText rendering method:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;to_trix_content_attachment_partial_path&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;to_attachable_partial_path&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;to_partial_path&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/rails/rails/blob/b96ddea5f0b4ff8ed6e9dfe4df62f7571b147b11/actiontext/lib/action_text/attachable.rb#L70-L76"&gt;https://github.com/rails/rails/blob/b96ddea5f0b4ff8ed6e9dfe4df62f7571b147b11/actiontext/lib/action_text/attachable.rb#L70-L76&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These methods are particularly useful when creating Custom Attachments. We won't cover those in this post, but that's where the above methods will come into play.&lt;/p&gt;

&lt;p&gt;Let's talk about what each does:&lt;/p&gt;

&lt;h2&gt;
  
  
  to_trix_content_attachment_partial_path
&lt;/h2&gt;

&lt;p&gt;Under the hood Rails calls &lt;code&gt;value.to_trix_html&lt;/code&gt; when you render within your Trix editor using the &lt;code&gt;rich_text_area&lt;/code&gt; form helper.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form_with&lt;/span&gt; &lt;span class="ss"&gt;model: &lt;/span&gt;&lt;span class="vi"&gt;@post&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- calls @post.body.to_trix_html --&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rich_text_area&lt;/span&gt; &lt;span class="ss"&gt;:body&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt; 
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/rails/rails/blob/0c97d1db023de4df6d2df8829e5ee311ff0d0e28/actiontext/app/helpers/action_text/tag_helper.rb#L36"&gt;https://github.com/rails/rails/blob/0c97d1db023de4df6d2df8829e5ee311ff0d0e28/actiontext/app/helpers/action_text/tag_helper.rb#L36&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;to_trix_content_attachment_partial_path&lt;/code&gt; is what users will see in their editor.&lt;/p&gt;

&lt;h2&gt;
  
  
  to_attachable_partial_path
&lt;/h2&gt;

&lt;p&gt;When the user hits save, then the editor will show the final output by calling:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;to_attachable_partial_path&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This allows you to create 2 experiences, one for the editor, and one for the final rendering.&lt;/p&gt;

&lt;h2&gt;
  
  
  to_partial_path
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;to_partial_path&lt;/code&gt; is a standard rendering path for models. This is used as a shortcut for &lt;code&gt;to_attachable_partial_path&lt;/code&gt; and &lt;code&gt;to_trix_content_attachment_partial_path&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;For more on &lt;code&gt;to_partial_path&lt;/code&gt; it's worth checking out this Thoughtbot article on rendering.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://thoughtbot.com/blog/rendering-collections-in-rails"&gt;https://thoughtbot.com/blog/rendering-collections-in-rails&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's what it may look like all together in an actual attachable model:&lt;/p&gt;

&lt;h3&gt;
  
  
  With an ActiveRecord Model
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;ActionText&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Attachable&lt;/span&gt;

  &lt;span class="c1"&gt;# app/views/users/_mention.html.erb &lt;/span&gt;
  &lt;span class="c1"&gt;# rendered in final rendering&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;to_attachment_partial_path&lt;/span&gt;
    &lt;span class="s2"&gt;"users/mention"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# app/views/users/_thumbnail.html.erb&lt;/span&gt;
  &lt;span class="c1"&gt;# rendered in Trix editor&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;to_trix_content_attachment_partial_path&lt;/span&gt;
    &lt;span class="s2"&gt;"users/thumbnail"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# app/views/users/_user.html.erb&lt;/span&gt;
  &lt;span class="c1"&gt;# rendered when calling &amp;lt;%= render User.first %&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;to_partial_path&lt;/span&gt;
    &lt;span class="s2"&gt;"users/user"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  With a non-ActiveRecord model
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Youtube&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;ActiveModel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Model&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;ActiveModel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Attributes&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;GlobalID&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Identification&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;ActionText&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Attachable&lt;/span&gt;

  &lt;span class="c1"&gt;# to be eligible to create an "sgid" we need to implement an &lt;/span&gt;
  &lt;span class="c1"&gt;#   "id" attribute.&lt;/span&gt;
  &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="ss"&gt;:id&lt;/span&gt;

  &lt;span class="c1"&gt;# app/views/youtubes/_embed.html.erb &lt;/span&gt;
  &lt;span class="c1"&gt;# rendered in final rendering&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;to_attachment_partial_path&lt;/span&gt;
    &lt;span class="s2"&gt;"youtubes/embed"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# app/views/youtubes/_thumbnail.html.erb&lt;/span&gt;
  &lt;span class="c1"&gt;# rendered in Trix editor&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;to_trix_content_attachment_partial_path&lt;/span&gt;
    &lt;span class="s2"&gt;"youtubes/thumbnail"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# app/views/youtubes/_youtube.html.erb&lt;/span&gt;
  &lt;span class="c1"&gt;# rendered when calling &amp;lt;%= render Youtube.first %&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;to_partial_path&lt;/span&gt;
    &lt;span class="s2"&gt;"youtubes/youtube"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;to_trix_content_attachment_partial_path&lt;/code&gt; is for rendering in the editor&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;to_attachable_partial_path&lt;/code&gt; is for rendering in the final output&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;to_partial_path&lt;/code&gt; is for standard object rendering&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For more on Attachments, particularly custom attachments checkout the following resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=2iGBuLQ3S0c"&gt;https://www.youtube.com/watch?v=2iGBuLQ3S0c&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://afomera.dev/posts/2022-10-11-combined-mentions-part-one"&gt;https://afomera.dev/posts/2022-10-11-combined-mentions-part-one&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://afomera.dev/posts/2022-10-12-combined-mentions-part-two"&gt;https://afomera.dev/posts/2022-10-12-combined-mentions-part-two&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gorails.com/episodes/at-mentions-with-actiontext"&gt;https://gorails.com/episodes/at-mentions-with-actiontext&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/yarotheslav/how-to-embed-youtube-videos-with-actiontext-tldr-5bbh"&gt;https://dev.to/yarotheslav/how-to-embed-youtube-videos-with-actiontext-tldr-5bbh&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>actiontext</category>
      <category>rails</category>
      <category>ruby</category>
      <category>webdev</category>
    </item>
    <item>
      <title>ActionText: Safe listing attributes and tags</title>
      <dc:creator>Konnor Rogers</dc:creator>
      <pubDate>Mon, 10 Oct 2022 01:12:52 +0000</pubDate>
      <link>https://dev.to/konnorrogers/actiontext-safe-listing-attributes-and-tags-1a4j</link>
      <guid>https://dev.to/konnorrogers/actiontext-safe-listing-attributes-and-tags-1a4j</guid>
      <description>&lt;p&gt;To safelist tags and attributes in ActionText we need to inspect the source since I was unable to find anywhere in the documentation how to do so.&lt;/p&gt;

&lt;p&gt;Rails has a separate gem for sanitizing which can be found here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/rails/rails-html-sanitizer"&gt;https://github.com/rails/rails-html-sanitizer&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The gem is utilized within ActionText by the content helper here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/rails/rails/blob/4328d0e16028a46bba79ab775e509a743ceaf18c/actiontext/app/helpers/action_text/content_helper.rb#L7-L10"&gt;https://github.com/rails/rails/blob/4328d0e16028a46bba79ab775e509a743ceaf18c/actiontext/app/helpers/action_text/content_helper.rb#L7-L10&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What we can do with these &lt;code&gt;mattr_accessor&lt;/code&gt;s is override them by creating an &lt;code&gt;initializer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We can create a file called &lt;code&gt;config/initializers/action_text.rb&lt;/code&gt; and fill it with some custom contents for allowable things. Let's say for example we wanted to add table editing. We'd need to add &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;tr&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;td&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;th&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;thead&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;tbody&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In addition, we may also want to add some additional attributes which we could also do here say perhaps &lt;code&gt;target&lt;/code&gt; for links.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/initializers/action_text.rb&lt;/span&gt;

&lt;span class="c1"&gt;# Add table tags&lt;/span&gt;
&lt;span class="no"&gt;ActionText&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ContentHelper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;allowed_tags&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"table"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"tr"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"td"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"th"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"thead"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"tbody"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Add link attributes&lt;/span&gt;
&lt;span class="no"&gt;ActionText&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ContentHelper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;allowed_attributes&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"rel"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also see an example from &lt;a class="mentioned-user" href="https://dev.to/excid3"&gt;@excid3&lt;/a&gt; 's latest ActionText episode:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/gorails-screencasts/modify-actiontext-html-output/blob/master/config/initializers/action_text.rb"&gt;https://github.com/gorails-screencasts/modify-actiontext-html-output/blob/master/config/initializers/action_text.rb&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gorails.com/episodes/modify-and-customize-actiontext-html-output?autoplay=1"&gt;https://gorails.com/episodes/modify-and-customize-actiontext-html-output?autoplay=1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're feeling real wild, you could even replace the sanitizer and scrubber with your own custom sanitizer / scrubber!&lt;/p&gt;

</description>
      <category>actiontext</category>
      <category>rails</category>
      <category>ruby</category>
      <category>webdev</category>
    </item>
    <item>
      <title>ActionText: Modify the rendering of ActiveStorage attachments</title>
      <dc:creator>Konnor Rogers</dc:creator>
      <pubDate>Tue, 04 Oct 2022 21:39:36 +0000</pubDate>
      <link>https://dev.to/konnorrogers/actiontext-modify-the-default-rendering-of-attachments-26g6</link>
      <guid>https://dev.to/konnorrogers/actiontext-modify-the-default-rendering-of-attachments-26g6</guid>
      <description>&lt;p&gt;If you have not already, make sure to run both the ActiveStorage and ActionText installers respectively.&lt;/p&gt;

&lt;p&gt;The ActiveStorage generator should create a file that looks like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;app/views/active_storage/blobs/_blob.html.erb&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;figure&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"attachment attachment--&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;representable?&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;"preview"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"file"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="s"&gt; attachment--&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extension&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;representable?&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;image_tag&lt;/span&gt; &lt;span class="n"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;representation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;resize_to_limit: &lt;/span&gt;&lt;span class="n"&gt;local_assigns&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:in_gallery&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;768&lt;/span&gt; &lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;figcaption&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"attachment__caption"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;caption&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;try&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:caption&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;caption&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"attachment__name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filename&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"attachment__size"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;number_to_human_size&lt;/span&gt; &lt;span class="n"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;byte_size&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/figcaption&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/figure&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can modify this file however you see fit. For example, you may want to provide custom overrides onto these dimensions for things like retina rendering or other optimizations.&lt;/p&gt;

&lt;p&gt;The reason I took the time to document this is because I noticed when I turned on file annotations for rendering partials, this is what showed up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- BEGIN /Users/konnorrogers/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/actiontext-7.0.4/app/views/action_text/contents/_content.html.erb --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Other use cases include using something like tailwind and adjusting the produced markup!&lt;/p&gt;

&lt;p&gt;I'll be documenting more undocumented things from ActionText as I go along and hopefully will upstream them into the official Rails guides!&lt;/p&gt;

</description>
      <category>actiontext</category>
      <category>rails</category>
      <category>ruby</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Why we still bundle with HTTP/2 in 2022</title>
      <dc:creator>Konnor Rogers</dc:creator>
      <pubDate>Wed, 20 Jul 2022 00:05:28 +0000</pubDate>
      <link>https://dev.to/konnorrogers/why-we-still-bundle-with-http2-in-2022-3noo</link>
      <guid>https://dev.to/konnorrogers/why-we-still-bundle-with-http2-in-2022-3noo</guid>
      <description>&lt;p&gt;HTTP/2 is out! CDNs are hot!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fjxvki99y51s4kbhpgsus.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fjxvki99y51s4kbhpgsus.jpg" alt="Jim Cramer pointing that something is doing really well"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Perhaps you've heard of HTTP/2 and its &lt;a href="https://web.dev/performance-http2/#request-and-response-multiplexing" rel="noopener noreferrer"&gt;Asset Multiplexing&lt;/a&gt; and the benefits it can bring to your project and allowing more assets to be loaded at once reducing the need for massive asset bundles and thus reducing cache churn.&lt;/p&gt;

&lt;p&gt;The above is all true. HTTP/2 has done wonders for asset loading. However, HTTP/2 isn't a catch all solution that lets you ditch the current set of frontend bundlers and use CDN only solutions.&lt;/p&gt;

&lt;p&gt;However, the problem is you lose out on a number of efficiencies that can be afforded by using a frontend bundler. To do this exercise, we'll be looking at &lt;a href="https://shoelace.style/" rel="noopener noreferrer"&gt;Shoelace&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Shoelace is an open-source project that leverages web-components and CSS variables to create a coherent design system for your web application. This case study isn't about Shoelace is or is not doing, but merely used as an example of where CDNs fall down. Also, you should use Shoelace, it is a pretty cool project!&lt;/p&gt;

&lt;h2 id="getting-started"&gt;
  
    Lets get cracking  
  
&lt;/h2&gt;

&lt;p&gt;Shoelace's easiest installation path is by importing from a JS CDN like jsdelivr.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0-beta.78/dist/themes/light.css"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0-beta.78/dist/shoelace.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's look at the network graph this creates.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Ffznflwygwcqb50gs9vst.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Ffznflwygwcqb50gs9vst.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Without diving too deep, what happens is Shoelace ships a base "module" that then uses a number of shared chunks generated by ESBuild. So when &lt;code&gt;&amp;lt;script src="${cdn}@{versionNumber}/dist/shoelace.js"&lt;/code&gt; is requested, then it requests the chunks that also live on the same path, and those chunks may import other chunks and then those chunks are imported. You can see how if you have a deeply nested set of "chunks" this can quickly bloat response times and create the "waterfall" effect we see here. The "waterfall" is called that because each request is dependent on the request prior to it. Imagine you had multiple &lt;code&gt;redirects&lt;/code&gt; happening on the same route. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;redirect_to "/foo" -&amp;gt; redirect_to "/bar" -&amp;gt; redirect_to "baz"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It's the same effect. You don't know you need the next asset until you request the asset. Or in this case, the javascript file.&lt;/p&gt;

&lt;h2 id="the-waterfall"&gt;
  
    Mitigating The Waterfall
  
&lt;/h2&gt;

&lt;p&gt;Enter &lt;code&gt;&amp;lt;link rel="modulepreload"&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/modulepreload" rel="noopener noreferrer"&gt;https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/modulepreload&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A module preload link allows you to request an asset without waiting for a browser to hit the chain of redirects for an asset. While good, its very hard to get right.&lt;/p&gt;

&lt;p&gt;Let's look at &lt;a href="https://jspm.org/" rel="noopener noreferrer"&gt;https://jspm.org/&lt;/a&gt; which is the default CDN used with the new Rails importmaps.&lt;/p&gt;

&lt;p&gt;I generated a "modulepreload" graph for Shoelace-beta.78 and loaded it up in my index.html.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://generator.jspm.io/#U2NjYGBkDM7IT81JTE5VyMwtyC8qyU0sYHAohorpFpdU5qTqw7gORnoGega6SakliXrmFgB/h42GPwA" rel="noopener noreferrer"&gt;https://generator.jspm.io/#U2NjYGBkDM7IT81JTE5VyMwtyC8qyU0sYHAohorpFpdU5qTqw7gORnoGega6SakliXrmFgB/h42GPwA&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fk6nvzf9wo66d1tdy06l9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fk6nvzf9wo66d1tdy06l9.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see this 'waterfall' is a little less and the initial "chunks" are all requested in parallel reducing the "waterfall" effect! Modulepreloads are good and work to great effect here with Shoelace! However, they add a lot of overhead of whenever you upgrade your shoelace version you have to generate a new preload graph.&lt;/p&gt;

&lt;p&gt;Let's also think beyond the scope of Shoelace, as we add more and more dependencies this module preload section can get quite large!&lt;/p&gt;

&lt;p&gt;When you use a bundler, static code analysis is performed and your assets can all get preloaded into one single bundle and this code analysis is far deeper and much more complex than the simple file imports allowed by module preloading.&lt;/p&gt;

&lt;h2 id="treeshaking"&gt;
  
    Unused import treeshaking
  
&lt;/h2&gt;

&lt;p&gt;With traditional JavaScript modules imported from a CDN there is no treeshaking. What you request is what you get. With a bundler like Parcel / Rollup / Webpack / Vite, they can statically analyze whatever code you import and get rid of any code paths that don't get used. CDN's dont have this privilege since they have no introspection into your final code.&lt;/p&gt;

&lt;p&gt;Heres a very basic example of how treeshaking would work from a dependency.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;doTheRoar&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;roar&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;doTheMeow&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;meow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, in our application code we would do something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;doIt&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./my-package&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;doIt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;doTheRoar&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above case, a bundler would get rid of the "doTheMeow" function and only ship "doTheRoar" in our final production bundle thus reducing the amount of JavaScript we ship. If we were using a CDN, &lt;code&gt;doTheRoar&lt;/code&gt; and &lt;code&gt;doTheMeow&lt;/code&gt; will always ship even if they don't get used.&lt;/p&gt;

&lt;h2 id="scope-hoisting"&gt;
  
    Scope Hoisting
  
&lt;/h2&gt;

&lt;p&gt;While we're on the topic of efficiency, we can talk about "scope hoisting". Scope hoisting is effectively as follows from the Parcel docs:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;...concatenates modules into a single scope when possible, rather than wrapping each module in a separate function. This is called “scope hoisting”. This helps make minification more effective, and also improves runtime performance by making references between modules static rather than dynamic object lookups.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://parceljs.org/features/scope-hoisting/" rel="noopener noreferrer"&gt;https://parceljs.org/features/scope-hoisting/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Scope hoisting helps minimize the amount of JavaScript we ship as well as improving our performance. That sounds pretty good to me!&lt;/p&gt;

&lt;h2 id="chunking"&gt;
  
    Chunking, Code Splitting, and Code Duplication
  
&lt;/h2&gt;

&lt;p&gt;Frontend bundlers allow us to "chunk" or "code split" our packages and ship smaller bundles and take advantage of HTTP/2!  We can code split in a variety of ways that I won't talk about, but bottom line is you can do it. Code splitting allows us to make optimized chunks.&lt;/p&gt;

&lt;p&gt;From the Parcel docs:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;...allows you to split your application code into separate bundles which can be loaded on demand, resulting in smaller initial bundle sizes and faster load times&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://parceljs.org/features/code-splitting/" rel="noopener noreferrer"&gt;https://parceljs.org/features/code-splitting/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This means we can get per-page chunks and do fancy things like "lazy-load" non-critical dependencies. Yes, you can do this with regular module scripts, but it is a lot more manual work.&lt;/p&gt;

&lt;p&gt;Chunking / splitting also lends itself to talk about dependency duplication. With CDNs, you can easily bloat the amount of JavaScript you import when you import similar, but slightly different versioned dependencies.&lt;/p&gt;

&lt;p&gt;For example, Shoelace imports &lt;a href="https://lit.dev/" rel="noopener noreferrer"&gt;Lit&lt;/a&gt; and lets say you wanted to use Lit in your project. Because of how Shoelace imports Lit inside of CDN's to make it self-contained, you do not get access to Lit, so it means you have to import the library again on your own meaning its not a "shared" dependency.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://unpkg.com/browse/@shoelace-style/shoelace@2.0.0-beta.78/dist/chunks/chunk.WWAD5WF4.js" rel="noopener noreferrer"&gt;https://unpkg.com/browse/@shoelace-style/shoelace@2.0.0-beta.78/dist/chunks/chunk.WWAD5WF4.js&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With frontend bundlers, we could easily hook into lit and build Shoelace's source ourself allowing us to ship a smaller final bundle by not duplicating dependencies.&lt;/p&gt;

&lt;h2 id="cache-sharing"&gt;
  
    Cache sharing across sites and domains
  
&lt;/h2&gt;

&lt;p&gt;Our final point is in regards to cross-origin resource sharing. The common trope used to be &lt;/p&gt;

&lt;p&gt;"if you import jquery on site A from a CDN and import jquery on site B from the same CDN, you don't incur the cost and the browser uses the cached version"&lt;/p&gt;

&lt;p&gt;This isn't true anymore. &lt;br&gt;
&lt;a href="https://twitter.com/seldo/status/1486122838801063938" rel="noopener noreferrer"&gt;https://twitter.com/seldo/status/1486122838801063938&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What does this change mean for you? If your sites live on modern hosting that provides a CDN and supports HTTP/2, you should drop the third-parties and ship all resources yourself. Relying on a third party resources offers little value in 2020.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;While the final point may be contentious, especially those hosting their own resources on non-HTTP/2 routers (Looking at you Heroku), it is important to note that shipping first party dependencies offers a lot more control and can lead to better performance since an additional HTTP handshake doesn't need to be made to a 3rd party.&lt;/p&gt;

&lt;h2 id="conclusion"&gt;
  
     That's all folks!
  
&lt;/h2&gt;

&lt;p&gt;While HTTP/2 offers a ton of performance benefits especially in regards to asset loading. It does not mean that we can't still find benefits in bundling our applications. At the end of the day, as long as you've evaluated the options and does what works best for your project, that's what really matters.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>bundlers</category>
      <category>frontend</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
