<?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: Ignace Maes</title>
    <description>The latest articles on DEV Community by Ignace Maes (@ignace).</description>
    <link>https://dev.to/ignace</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%2F854502%2F7cd8fb3b-2eef-438b-a381-d9adebf46d88.jpeg</url>
      <title>DEV Community: Ignace Maes</title>
      <link>https://dev.to/ignace</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ignace"/>
    <language>en</language>
    <item>
      <title>Adding custom fonts to your Ember app with Fontsource</title>
      <dc:creator>Ignace Maes</dc:creator>
      <pubDate>Thu, 02 Apr 2026 11:49:39 +0000</pubDate>
      <link>https://dev.to/ignace/adding-custom-fonts-to-your-ember-app-with-fontsource-43l0</link>
      <guid>https://dev.to/ignace/adding-custom-fonts-to-your-ember-app-with-fontsource-43l0</guid>
      <description>&lt;p&gt;If you're running a modern Ember app with Embroider and Vite, adding custom fonts is easy. &lt;a href="https://fontsource.org/" rel="noopener noreferrer"&gt;Fontsource&lt;/a&gt; is a good way to do it: fonts are packaged as npm dependencies, so you get versioned, self-hosted fonts with zero config on the bundler side.&lt;/p&gt;

&lt;p&gt;Here's how it works in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install the font packages
&lt;/h2&gt;

&lt;p&gt;Pick the fonts you need from &lt;a href="https://fontsource.org/" rel="noopener noreferrer"&gt;fontsource.org&lt;/a&gt; and install them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm add @fontsource/geist @fontsource/geist-mono
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Import the CSS
&lt;/h2&gt;

&lt;p&gt;In your &lt;code&gt;app/app.css&lt;/code&gt; (or wherever your main stylesheet lives), import the weights you actually use:&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="k"&gt;@import&lt;/span&gt; &lt;span class="s1"&gt;"@fontsource/geist/400.css"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s1"&gt;"@fontsource/geist/500.css"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s1"&gt;"@fontsource/geist/600.css"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s1"&gt;"@fontsource/geist/700.css"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s1"&gt;"@fontsource/geist-mono/400.css"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s1"&gt;"@fontsource/geist-mono/500.css"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each of these CSS files contains the &lt;code&gt;@font-face&lt;/code&gt; declarations for that specific weight. The font files (woff2) ship as part of the npm package and Vite resolves them automatically.&lt;/p&gt;

&lt;p&gt;Only import the weights you use. Every weight you add is another font file your users have to download.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use the fonts in CSS
&lt;/h2&gt;

&lt;p&gt;Reference the font family names in your styles:&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;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"Geist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;code&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;pre&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"Geist Mono"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;monospace&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;If you're using Tailwind v4, you can set them as theme variables instead:&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="k"&gt;@theme&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--font-sans&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"Geist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--font-mono&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"Geist Mono"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;monospace&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;Tailwind will then use these as the default &lt;code&gt;font-sans&lt;/code&gt; and &lt;code&gt;font-mono&lt;/code&gt; utilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preloading fonts to avoid FOUT
&lt;/h2&gt;

&lt;p&gt;The setup above works, but you'll probably see a flash of unstyled text (FOUT) on first load. The browser discovers the font files only after parsing the CSS, which is late.&lt;/p&gt;

&lt;p&gt;You can fix this by &lt;a href="https://fontsource.org/docs/getting-started/preload" rel="noopener noreferrer"&gt;preloading the woff2 files&lt;/a&gt;. In an Embroider app with Vite, you can use the &lt;code&gt;?url&lt;/code&gt; import suffix to get the resolved URL for a font file, then inject &lt;code&gt;&amp;lt;link rel="preload"&amp;gt;&lt;/code&gt; tags early.&lt;/p&gt;

&lt;p&gt;Create a file like &lt;code&gt;app/lib/preload-fonts.ts&lt;/code&gt;:&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="nx"&gt;geist400&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;@fontsource/geist/files/geist-latin-400-normal.woff2?url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;geist500&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;@fontsource/geist/files/geist-latin-500-normal.woff2?url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;geist600&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;@fontsource/geist/files/geist-latin-600-normal.woff2?url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;geist400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;geist500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;geist600&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;link&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;preload&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;font&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;font/woff2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;crossOrigin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;anonymous&lt;/span&gt;&lt;span class="dl"&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;head&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;link&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;Then import it from your &lt;code&gt;app/app.ts&lt;/code&gt; entrypoint:&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./lib/preload-fonts&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;The &lt;code&gt;?url&lt;/code&gt; suffix is a Vite feature. It makes the import return the final, hashed URL of the asset. In production, this will be something like &lt;code&gt;/assets/geist-latin-400-normal-abc123.woff2&lt;/code&gt;. This means your preload links always point to the correct file, even after builds with content hashing.&lt;/p&gt;

&lt;p&gt;You only need to preload fonts that are used above the fold. If you have a monospace font that only shows up in specific views, skip preloading it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Variable fonts
&lt;/h2&gt;

&lt;p&gt;If the font supports it, Fontsource also ships variable font versions. Instead of importing individual weights, you import a single file that covers all weights:&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="k"&gt;@import&lt;/span&gt; &lt;span class="s1"&gt;"@fontsource-variable/geist"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s1"&gt;"@fontsource-variable/geist-mono"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The package name changes from &lt;code&gt;@fontsource/&lt;/code&gt; to &lt;code&gt;@fontsource-variable/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm add @fontsource-variable/geist @fontsource-variable/geist-mono
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One file per font, all weights included. The browser only downloads a single woff2 file and interpolates between weights. This is usually smaller than loading 4+ static weight files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Fontsource over Google Fonts?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Fonts are self-hosted. No third-party requests.&lt;/li&gt;
&lt;li&gt;You control exactly which weights and subsets are included.&lt;/li&gt;
&lt;li&gt;Fonts are versioned alongside your app. No CDN surprises.&lt;/li&gt;
&lt;li&gt;Works with any bundler that can resolve &lt;code&gt;node_modules&lt;/code&gt; CSS imports (Vite, Webpack, etc).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  That's it
&lt;/h2&gt;

&lt;p&gt;Three steps: install the package, import the CSS weights, and reference the font family. Preloading is optional but helps with perceived performance. The whole thing works because Embroider + Vite resolves npm CSS imports and static assets the same way any Vite app would.&lt;/p&gt;

</description>
      <category>ember</category>
      <category>css</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I ported shadcn/ui to modern Ember.js</title>
      <dc:creator>Ignace Maes</dc:creator>
      <pubDate>Sun, 25 Jan 2026 11:18:40 +0000</pubDate>
      <link>https://dev.to/ignace/i-ported-shadcnui-to-modern-emberjs-440d</link>
      <guid>https://dev.to/ignace/i-ported-shadcnui-to-modern-emberjs-440d</guid>
      <description>&lt;p&gt;About a week ago, I made &lt;a href="https://shadcn-ember.com/" rel="noopener noreferrer"&gt;shadcn-ember&lt;/a&gt; public. It is an unofficial, community-led port of &lt;a href="https://ui.shadcn.com/" rel="noopener noreferrer"&gt;shadcn/ui&lt;/a&gt; for &lt;a href="https://emberjs.com/" rel="noopener noreferrer"&gt;Ember.js&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are not familiar with shadcn/ui, the short version is: you copy component code into your project instead of installing it as a dependency. You own the source. You can change anything. No more fighting with library APIs or overriding styles with &lt;code&gt;!important&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I wanted that for Ember. So I (and Claude) built it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is this, exactly?
&lt;/h2&gt;

&lt;p&gt;It is a collection of 47 components: buttons, dialogs, dropdowns, tooltips, toasts, sliders, tabs, sidebars, and more. All built for modern Ember (Glimmer components, &lt;code&gt;.gts&lt;/code&gt; files, tracked properties).&lt;/p&gt;

&lt;p&gt;The components are styled with Tailwind CSS v4 and use CSS variables for theming. You can customize colors, border radius, and dark mode out of the box.&lt;/p&gt;

&lt;p&gt;There is also a CLI that handles the "copy and paste" part for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Set up your project
&lt;/h3&gt;

&lt;p&gt;Run the init command. It installs dependencies, sets up the Tailwind config, and adds the &lt;code&gt;cn&lt;/code&gt; utility for class merging.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx shadcn-ember@latest init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will get a few questions: which style, which base color, whether to use CSS variables. Pick what you like.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Add a component
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx shadcn-ember@latest add button
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This puts a &lt;code&gt;button.gts&lt;/code&gt; file in &lt;code&gt;app/components/ui/&lt;/code&gt;. It looks like this:&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;hash&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="s1"&gt;@ember/helper&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Component&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;@glimmer/component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;cn&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="s1"&gt;@/lib/utils&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ... types and variants&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Component&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ButtonSignature&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;classes&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="nf"&gt;buttonVariants&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;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;variant&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default&lt;/span&gt;&lt;span class="dl"&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;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default&lt;/span&gt;&lt;span class="dl"&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;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt;
    &lt;span class="err"&gt;);
  }

  &amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;template&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="o"&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;classes&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;attributes&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/template&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;buttonVariants&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is just a Glimmer component. Nothing special. You can read it, understand it, and change it.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Use it
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;Button&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="s1"&gt;@/components/ui/button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;template&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"outline"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Click me&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;template&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other CLI commands
&lt;/h2&gt;

&lt;p&gt;There are a few more commands if you need them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;npx shadcn-ember@latest add dialog toast tabs&lt;/code&gt; adds multiple components at once&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npx shadcn-ember@latest view button&lt;/code&gt; shows you the component code before you add it&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npx shadcn-ember@latest build&lt;/code&gt; generates registry JSON if you want to host your own component registry&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Installation options
&lt;/h2&gt;

&lt;p&gt;The docs cover setup for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vite&lt;/strong&gt; (recommended for new projects)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Astro&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manual&lt;/strong&gt; (if you want to set things up yourself)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All install guides are at &lt;a href="https://shadcn-ember.com/docs/installation" rel="noopener noreferrer"&gt;shadcn-ember.com/docs/installation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I made this
&lt;/h2&gt;

&lt;p&gt;Ember has always valued stability and conventions. But when it comes to UI, we have been a bit behind. I wanted to bring the same "open code" approach that has worked so well in other ecosystems.&lt;/p&gt;

&lt;p&gt;The original shadcn/ui project has a clear philosophy: you should be able to read, understand, and modify your UI code. That resonated with me, and I think it fits well with how Ember developers like to work.&lt;/p&gt;

&lt;p&gt;This is an ongoing project. There are more components to add, edge cases to handle, and docs to write. Contributions are welcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Docs and demo:&lt;/strong&gt; &lt;a href="https://shadcn-ember.com/" rel="noopener noreferrer"&gt;shadcn-ember.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/IgnaceMaes/shadcn-ember" rel="noopener noreferrer"&gt;github.com/IgnaceMaes/shadcn-ember&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let me know what you think. I am curious to hear how it works in your projects.&lt;/p&gt;

</description>
      <category>ember</category>
      <category>shadcn</category>
      <category>tailwindcss</category>
      <category>ui</category>
    </item>
    <item>
      <title>How to use the new Ember theme for QUnit</title>
      <dc:creator>Ignace Maes</dc:creator>
      <pubDate>Tue, 11 Jun 2024 17:17:42 +0000</pubDate>
      <link>https://dev.to/ignace/how-to-use-the-new-ember-theme-for-qunit-3gac</link>
      <guid>https://dev.to/ignace/how-to-use-the-new-ember-theme-for-qunit-3gac</guid>
      <description>&lt;p&gt;The integrated web UI test runner in Ember is a convenient way to run your tests. If you've been using the default QUnit theme, it might not surprise you that it was designed over ten years ago.&lt;/p&gt;

&lt;p&gt;As of the latest release of &lt;a href="https://github.com/emberjs/ember-qunit/releases/tag/v8.1.0"&gt;&lt;code&gt;ember-qunit&lt;/code&gt;&lt;/a&gt;, support for theming has been added. The &lt;a href="https://www.npmjs.com/package/qunit-theme-ember"&gt;&lt;code&gt;qunit-theme-ember&lt;/code&gt;&lt;/a&gt;, a modern theme based on the Ember styleguide, has been included as a built-in option. Here's how to enable it in your app:&lt;/p&gt;

&lt;p&gt;First, create an Ember app if you haven't already.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx ember-cli new example-app &lt;span class="nt"&gt;--embroider&lt;/span&gt; &lt;span class="nt"&gt;--pnpm&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you already have an app, make sure &lt;code&gt;ember-qunit&lt;/code&gt; version &lt;code&gt;8.1.0&lt;/code&gt; or above is used as dependency.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; ember-qunit@^8.1.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, install &lt;a href="https://www.npmjs.com/package/@embroider/macros"&gt;&lt;code&gt;@embroider/macros&lt;/code&gt;&lt;/a&gt; to be able to pass configuration options to &lt;code&gt;ember-qunit&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; @embroider/macros
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now configuration options can be set in the &lt;code&gt;ember-cli-build.js&lt;/code&gt; file. Add the following to the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;module.exports = function (defaults) {
&lt;/span&gt;  const app = new EmberApp(defaults, {
    // Add options here
&lt;span class="gi"&gt;+    '@embroider/macros': {
+      setConfig: {
+        'ember-qunit': {
+          theme: 'ember',
+        },
+      },
+    },
&lt;/span&gt;  });
&lt;span class="err"&gt;
&lt;/span&gt;  // ...
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it 🎉&lt;/p&gt;

&lt;p&gt;Simply restart your dev server and go to &lt;code&gt;http://localhost:4200/tests&lt;/code&gt; to see it in action.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>testing</category>
      <category>qunit</category>
      <category>ember</category>
    </item>
    <item>
      <title>The easy way to access the last JavaScript array element</title>
      <dc:creator>Ignace Maes</dc:creator>
      <pubDate>Fri, 29 Mar 2024 18:32:15 +0000</pubDate>
      <link>https://dev.to/ignace/the-easy-way-to-access-the-last-javascript-array-element-3oa4</link>
      <guid>https://dev.to/ignace/the-easy-way-to-access-the-last-javascript-array-element-3oa4</guid>
      <description>&lt;p&gt;In JavaScript, accessing the last element of an array is not as straightforward as in other languages. In Python, for example, you can use negative indexing to access the last element of an array. In JavaScript, however, using negative indexing with brackets &lt;code&gt;[]&lt;/code&gt; doesn't work. Instead, you have to use brackets with the length of the array minus one.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;frameworks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Nuxt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Remix&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SvelteKit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Ember&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// ❌ Doesn't work&lt;/span&gt;
&lt;span class="nx"&gt;frameworks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="c1"&gt;// undefined&lt;/span&gt;


&lt;span class="c1"&gt;// ✅ Works&lt;/span&gt;
&lt;span class="nx"&gt;frameworks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;frameworks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="c1"&gt;// 'Ember'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make array indexing more flexible, JavaScript introduced the &lt;code&gt;at&lt;/code&gt; method. The &lt;code&gt;at&lt;/code&gt; method allows you to get the element at a given index in an array, &lt;em&gt;with&lt;/em&gt; support for negative indexing.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;frameworks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Nuxt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Remix&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SvelteKit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Ember&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// 🔥 Supports negative indexing&lt;/span&gt;
&lt;span class="nx"&gt;frameworks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// 'Ember'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, the &lt;code&gt;at&lt;/code&gt; method is only an accessor method. This means that you can't use it to mutate the array. If you want to mutate the array, you have to use the bracket notation.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;frameworks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Nuxt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Remix&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SvelteKit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Ember&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// ❌ Doesn't work&lt;/span&gt;
&lt;span class="nx"&gt;frameworks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;React&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Uncaught ReferenceError: Invalid left-hand side in assignment&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Works&lt;/span&gt;
&lt;span class="nx"&gt;frameworks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;frameworks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;React&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ['Nuxt', 'Remix', 'SvelteKit', 'React']&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A similar method for mutations that does support negative indexing has been introduced as well. The &lt;code&gt;with&lt;/code&gt; method allows you to change the value of an element at a given index in an array. However, it returns a new array with the change, as it doesn't mutate the original array.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;frameworks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Nuxt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Remix&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SvelteKit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Ember&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Returns a copy with the change&lt;/span&gt;
&lt;span class="nx"&gt;frameworks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;React&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// ['Nuxt', 'Remix', 'SvelteKit', 'React']&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Browser support
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;at&lt;/code&gt; function has baseline &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at#browser_compatibility"&gt;browser support&lt;/a&gt; on since 2022. Node has support in all current LTS versions.&lt;/p&gt;

&lt;p&gt;If you're targetting older browsers, &lt;a href="https://github.com/zloirock/core-js#relative-indexing-method"&gt;a pollyfill&lt;/a&gt; is available in core-js.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Chrome&lt;/th&gt;
&lt;th&gt;Edge&lt;/th&gt;
&lt;th&gt;Firefox&lt;/th&gt;
&lt;th&gt;Safari&lt;/th&gt;
&lt;th&gt;Node.js&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;✅ 92&lt;/td&gt;
&lt;td&gt;✅ 92&lt;/td&gt;
&lt;td&gt;✅ 90&lt;/td&gt;
&lt;td&gt;✅ 15.4&lt;/td&gt;
&lt;td&gt;✅ 16.6.0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;with&lt;/code&gt; function is newer, having only baseline &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/with#browser_compatibility"&gt;browser support&lt;/a&gt; since July 2023. Node includes support since version 20.&lt;/p&gt;

&lt;p&gt;Luckily, there is also &lt;a href="https://github.com/zloirock/core-js#change-array-by-copy"&gt;a polyfill&lt;/a&gt; available in core-js.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Chrome&lt;/th&gt;
&lt;th&gt;Edge&lt;/th&gt;
&lt;th&gt;Firefox&lt;/th&gt;
&lt;th&gt;Safari&lt;/th&gt;
&lt;th&gt;Node.js&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;✅ 110&lt;/td&gt;
&lt;td&gt;✅ 110&lt;/td&gt;
&lt;td&gt;✅ 115&lt;/td&gt;
&lt;td&gt;✅ 16&lt;/td&gt;
&lt;td&gt;✅ 20.0.0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;p&gt;This article introduced the &lt;code&gt;at&lt;/code&gt; method to access elements in an array. It supports negative indexing, which comes in handy when selecting the last elements. The &lt;code&gt;with&lt;/code&gt; method is a new syntax that allows you to change the value of an element at a given index. However, it returns a new array with the change, as it doesn't mutate the original array. Both methods are supported in modern browsers, but you might need a polyfill for older browsers.&lt;/p&gt;

&lt;p&gt;Interested in content like this? Follow along on &lt;a href="https://twitter.com/Ignace_Maes"&gt;~Twitter~ X&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Automate NPM releases on GitHub using changesets 🦋</title>
      <dc:creator>Ignace Maes</dc:creator>
      <pubDate>Wed, 18 Oct 2023 16:11:17 +0000</pubDate>
      <link>https://dev.to/ignace/automate-npm-releases-on-github-using-changesets-25b8</link>
      <guid>https://dev.to/ignace/automate-npm-releases-on-github-using-changesets-25b8</guid>
      <description>&lt;p&gt;You've built an awesome JavaScript library and can't wait to share it with the world. But how do you package and release your library, manage versioning, and maintain a tidy, structured changelog?&lt;/p&gt;

&lt;p&gt;Doing these tasks by hand can quickly become a hassle. If you're open to receiving contributions from others, it can be an even bigger headache. In this post we'll cover how to use &lt;a href="https://github.com/changesets/changesets" rel="noopener noreferrer"&gt;changesets&lt;/a&gt; to automate all of these steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up changesets
&lt;/h2&gt;

&lt;p&gt;Changesets is a tool to manage versioning and changelogs in an automated way. It works by keeping a markdown file per change, conveniently called a "changeset". This file is used to track the type of change together with a description.&lt;/p&gt;

&lt;p&gt;To install changesets, run the following command in your project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; @changesets/cli &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npx changeset init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running this command will generate a &lt;code&gt;.changeset&lt;/code&gt; folder. This folder contains a &lt;code&gt;README.md&lt;/code&gt; with some general info and a &lt;code&gt;config.json&lt;/code&gt; file to customize the tool. All "changesets" will also be stored in this folder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Versioning
&lt;/h2&gt;

&lt;p&gt;Now that changesets is set up you can start making improvements to your project. Once you're ready to open a PR and merge your changes, it is time to add a "changeset".&lt;/p&gt;

&lt;p&gt;Changesets follow &lt;a href="https://semver.org/" rel="noopener noreferrer"&gt;semantic versioning&lt;/a&gt; conventions. In a nutshell, this indicates that the version number follows the fixed format of &lt;code&gt;major.minor.patch&lt;/code&gt;. A &lt;em&gt;patch&lt;/em&gt; is a backwards-compatible bugfix, a &lt;em&gt;minor&lt;/em&gt; is a feature that is backwards compatible, and a &lt;em&gt;major&lt;/em&gt; is for any change that isn't backwards compatible.&lt;/p&gt;

&lt;p&gt;To create a "changeset", simply run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx changeset
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will first ask to choose the type of version change (patch, minor, or major) followed by a description of the change that's made. This description is what will end up in the changelog when a release is done. After running this command a new markdown file will be created in the &lt;code&gt;.changeset&lt;/code&gt; folder. Congratulations, you've made your first "changeset"!&lt;/p&gt;

&lt;p&gt;To remind contributors (and yourself!) to add this changeset to PRs, &lt;a href="https://github.com/apps/changeset-bot" rel="noopener noreferrer"&gt;install the Changeset bot&lt;/a&gt; from the GitHub Marketplace.&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%2Fj5znf3sszi8l9mpuauxw.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%2Fj5znf3sszi8l9mpuauxw.png" alt="Changeset bot comment"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Release action on GitHub
&lt;/h2&gt;

&lt;p&gt;You've now merged a couple of great new features and it's time for a release of your package! While changesets offers commands to do this process manually, let's go all out and automate this using GitHub actions. This will save time, and perhaps more importantly, reduce potential manual errors.&lt;/p&gt;

&lt;p&gt;Create a file &lt;code&gt;.github/workflows/release.yml&lt;/code&gt; with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Release&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

&lt;span class="na"&gt;concurrency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.workflow }}-${{ github.ref }}&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;release&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Release&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout Repo&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Node.js 20.x&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;20.x&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm install&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create Release Pull Request or Publish to npm&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;changesets&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;changesets/action@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;publish&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx changeset publish&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;NPM_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.NPM_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The action needs access to your NPM token in order to be able to do releases. Add this secret in your repository's settings located at &lt;code&gt;Settings &amp;gt; Secrets and variables &amp;gt; Actions&lt;/code&gt; using the name &lt;code&gt;NPM_TOKEN&lt;/code&gt;. If you do not yet have an NPM token, create one on &lt;a href="https://www.npmjs.com/" rel="noopener noreferrer"&gt;npmjs.com&lt;/a&gt;. Make sure the token supports automation as it will be used to run in CI.&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%2Fyfkd0lm77kw3ac21bz4u.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%2Fyfkd0lm77kw3ac21bz4u.png" alt="NPM token secret"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The newly added GitHub action will run every time a commit is made on the main branch. If the commit has a "changeset" it will open the release PR. (or update, if there is already one open) This PR gives an overview of all changes which are currently on the main branch but not yet in the latest release. The PR will include three changes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The removal of all "changeset" markdown files&lt;/li&gt;
&lt;li&gt;The package version is bumped accordingly&lt;/li&gt;
&lt;li&gt;A changelog entry is added for every "changeset"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The next package version is determined by looking at all "changeset" entries. When two "changesets" are included which are of the type &lt;em&gt;minor&lt;/em&gt;, the &lt;em&gt;minor&lt;/em&gt; release will only be bumped once. Likewise, when a mix of version types are included, the most significant version is bumped. For example: if both a &lt;em&gt;bugfix&lt;/em&gt; and &lt;em&gt;minor&lt;/em&gt; are included, only the &lt;em&gt;minor&lt;/em&gt; version will be bumped. The days of manually calculating the correct package version are gone!&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%2F39qgxfckyx8gbsltm3po.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%2F39qgxfckyx8gbsltm3po.png" alt="Release PR"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, when you're ready to go, the release PR can be merged and the GitHub Action will automatically do a publish of the package to NPM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: upgrade your changelog game with GitHub links
&lt;/h2&gt;

&lt;p&gt;By default the changelog just combines all changeset descriptions. With custom formatters, it can be extended to do more.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;@changesets/changelog-github&lt;/code&gt; package formats the changelog to include links to the merged PRs as well as a reference to the GitHub user who submitted this change. This is great to give proper credit to contributors as well as easily finding back changes that have been made. It can be installed with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; @changesets/changelog-github
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, register it as the changelog formatter in your &lt;code&gt;.changeset/config.json&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; {
   "$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json",
&lt;span class="gd"&gt;-  "changelog": "@changesets/cli/changelog",
&lt;/span&gt;&lt;span class="gi"&gt;+  "changelog": ["@changesets/changelog-github", {
+    "repo": "your-org/your-repo"
+  }],
&lt;/span&gt;   "commit": false,
   "fixed": [],
   "linked": [],
   "access": "restricted",
   "baseBranch": "main",
   "updateInternalDependencies": "patch",
   "ignore": []
 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Upon merging the release PR, the changelog will now include links to PRs as well as the authors.&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%2F8v6q1s5z5gghi2qfjofu.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%2F8v6q1s5z5gghi2qfjofu.png" alt="Changelog"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Changesets is a great tool to automate versioning and changelogs for JavaScript packages. By streamlining the release process, it not only saves time but also reduces the potential for errors.&lt;/p&gt;




&lt;p&gt;Do you automate your releases already? And which tool do you prefer?&lt;/p&gt;

&lt;p&gt;Questions? Leave a comment or reach out on &lt;del&gt;Twitter&lt;/del&gt;X &lt;a href="https://twitter.com/Ignace_Maes" rel="noopener noreferrer"&gt;@Ignace_Maes&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>github</category>
      <category>npm</category>
      <category>typescript</category>
    </item>
    <item>
      <title>The road from Ember classic to Glimmer components</title>
      <dc:creator>Ignace Maes</dc:creator>
      <pubDate>Thu, 02 Feb 2023 14:20:47 +0000</pubDate>
      <link>https://dev.to/lighthouse-intelligence/the-road-from-ember-classic-to-glimmer-components-4hlc</link>
      <guid>https://dev.to/lighthouse-intelligence/the-road-from-ember-classic-to-glimmer-components-4hlc</guid>
      <description>&lt;p&gt;In late 2019, the &lt;a href="https://blog.emberjs.com/octane-is-here/" rel="noopener noreferrer"&gt;Ember.js Octane edition&lt;/a&gt; was released which included a new way of writing components: Glimmer components. Components now extend the component class from the Glimmer package instead of Ember. Besides this minor difference in importing there’s a large difference in functionality. This article will go over the differences, reasons why you would want to upgrade, and an upgrade strategy to tackle this in large codebases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Classic vs. Glimmer
&lt;/h2&gt;

&lt;p&gt;Glimmer components can be seen as a slimmed down version of classic components. Most lifecycle hooks were removed. Arguments are scoped and built upon auto tracking reactivity from Glimmer. There’s no more HTML wrapping element. They use native class syntax. And a lot of classic leftovers were cleaned up.&lt;/p&gt;

&lt;p&gt;The following example implements a component which copies the text passed as argument to the clipboard when clicking a button. When using classic Ember components, it could be implemented as follows:&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="c1"&gt;// copy-to-clipboard.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Component&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;@ember/component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;set&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="s1"&gt;@ember/object&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="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;isCopied&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;copyToClipboard&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clipboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeText&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;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;set&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;isCopied&lt;/span&gt;&lt;span class="dl"&gt;'&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight handlebars"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- copy-to-clipboard.hbs --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;action&lt;/span&gt; &lt;span class="s1"&gt;'copyToClipboard'&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;isCopied&lt;/span&gt; &lt;span class="s1"&gt;'Copied!'&lt;/span&gt; &lt;span class="s1"&gt;'Click to copy text'&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
&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;The same component using Glimmer would look 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="c1"&gt;// copy-to-clipboard.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Component&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;@glimmer/component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;tracked&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="s1"&gt;@glimmer/tracking&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;action&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="s1"&gt;@ember/object&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="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CopyToClipboard&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Component&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;tracked&lt;/span&gt; &lt;span class="nx"&gt;isCopied&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;action&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;copyToClipboard&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clipboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeText&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;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&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;isCopied&lt;/span&gt; &lt;span class="o"&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight handlebars"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- copy-to-clipboard.hbs --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;on&lt;/span&gt; &lt;span class="s1"&gt;'click'&lt;/span&gt; &lt;span class="nv"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;copyToClipboard&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;isCopied&lt;/span&gt; &lt;span class="s1"&gt;'Copied!'&lt;/span&gt; &lt;span class="s1"&gt;'Click to copy text'&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
&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;The Glimmer component can make use of decorators as it uses native class syntax. There is a clear separation between arguments passed to the component (here &lt;code&gt;text&lt;/code&gt;) and the local state (&lt;code&gt;isCopied&lt;/code&gt;). Regular assignment expressions can be used to update state that should trigger template rerenders thanks to Glimmer auto tracking. And there's &lt;a href="https://ember-learn.github.io/ember-octane-vs-classic-cheat-sheet/" rel="noopener noreferrer"&gt;a lot more improvements&lt;/a&gt; which aren't illustrated in this small example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why migrate to Glimmer components?
&lt;/h2&gt;

&lt;p&gt;Every code migration requires engineering time which can not be used to build new products to sell to customers. So for a business to invest into refactors and migrations there has to be another benefit. Classic components in Ember are still supported in the latest major version, so why upgrade? The following benefits for us made it worth the trade-off.&lt;/p&gt;

&lt;h3&gt;
  
  
  One way of doing things
&lt;/h3&gt;

&lt;p&gt;Glimmer components for new code became the standard practice since the release of Ember Octane. This caused our codebases to contain two component types. This adds extra mental overhead when working in codebases which contain both. You have to be aware of which type of component you’re working with and make changes accordingly. For people new to Ember this can be extra confusing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Closer to native JavaScript experience
&lt;/h3&gt;

&lt;p&gt;Glimmer components contain very little Ember specific code practices compared to classic components. This makes it easier for people to get started in Ember coming from a different background. Every JavaScript developer should be able to get started in our codebase and get up to speed relatively quickly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rendering performance
&lt;/h3&gt;

&lt;p&gt;The previous points are nice from a developers perspective. There’s, however, also a benefit for customers. Glimmer components render &lt;a href="https://stefankrause.net/js-frameworks-benchmark8/table.html" rel="noopener noreferrer"&gt;substantially faster&lt;/a&gt; than classic components.&lt;/p&gt;

&lt;h3&gt;
  
  
  TypeScript support
&lt;/h3&gt;

&lt;p&gt;TypeScript has proven it’s here to stay in the wider JavaScript ecosystem. It has risen in interest and kept its place as the &lt;a href="https://2022.stateofjs.com/" rel="noopener noreferrer"&gt;most popular JavaScript flavour&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In 2022 Ember acknowledged &lt;a href="https://rfcs.emberjs.com/id/0724-road-to-typescript/" rel="noopener noreferrer"&gt;official TypeScript support&lt;/a&gt; with a dedicated core team. Glimmer components unlock the full potential of TypeScript support in Ember. Template type checking with &lt;a href="https://github.com/typed-ember/glint" rel="noopener noreferrer"&gt;Glint&lt;/a&gt; is also under active development. Exciting!&lt;/p&gt;

&lt;h3&gt;
  
  
  Future-proof a codebase
&lt;/h3&gt;

&lt;p&gt;Ember.js development doesn’t stagnate. Progress is already being made for new improvements to the current component model. The RFC for &lt;a href="https://github.com/emberjs/rfcs/pull/779" rel="noopener noreferrer"&gt;first-class component templates&lt;/a&gt; has been accepted and merged in 2022 and will provide new benefits to Ember users. By first adopting Glimmer components, we’re prepared for what’s coming next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migration strategy
&lt;/h2&gt;

&lt;p&gt;While you could jump straight in and start migrating every component one by one, we decided to go for a different strategy. For smaller codebases migrating components one by one can be a feasible approach, but this can be cumbersome for large codebases (think 100K+ lines of code). This effort is way too large for a single person and has too many side effects. This is why we broke up our migration effort into nine milestones.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Native JavaScript class syntax for components
&lt;/h3&gt;

&lt;p&gt;Historically Ember used object syntax to define components. As class syntax matured in JavaScript in general, it also became the standard for Glimmer components. Classic components in Ember provide support for both object and class syntax. This makes switching to class syntax a great first step towards Glimmer components.&lt;/p&gt;

&lt;p&gt;Ember provides a &lt;a href="https://github.com/ember-codemods/ember-native-class-codemod" rel="noopener noreferrer"&gt;codemod&lt;/a&gt; to convert object syntax to class syntax. This has saved us a tremendous amount of time. By doing this our development experience also greatly improved.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. No implicit this
&lt;/h3&gt;

&lt;p&gt;Arguments in Glimmer components are bundled in the &lt;code&gt;args&lt;/code&gt; object. This avoids clashes with custom defined properties in the component’s own scope and creates a clear distinction between properties defined locally and passed arguments.&lt;/p&gt;

&lt;p&gt;Glimmer component templates reflect this by using the &lt;code&gt;@&lt;/code&gt; prefix when using arguments and the &lt;code&gt;this.&lt;/code&gt; prefixes when accessing properties of the backing class. This way of working is also supported in classic components, even though arguments are in the same scope as local properties. This means the migration is non blocking, and luckily there’s a &lt;a href="https://github.com/ember-codemods/ember-no-implicit-this-codemod" rel="noopener noreferrer"&gt;codemod&lt;/a&gt; available for this as well. The codemod however can’t make a distinction between arguments and local properties, and is something that will be cleaned up in a later phase.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Getting the simple components out of the way
&lt;/h3&gt;

&lt;p&gt;By reviewing all components and checking which used none or limited of the classic component features, we were able to identify a set of components which were easily migrated to Glimmer. Examples are components which did not have any JavaScript logic, as Glimmer introduced the concept of template-only components which work without an explicit backing class. This was low hanging fruit and by getting them out of the way directly we avoided unnecessary overhead of the other phases.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Remove outer HTML semantics
&lt;/h3&gt;

&lt;p&gt;Classic components have a &lt;a href="https://guides.emberjs.com/release/upgrading/current-edition/glimmer-components/#toc_outer-html" rel="noopener noreferrer"&gt;wrapping HTML element&lt;/a&gt; which doesn’t exist in Glimmer components. A first step to prepare for this removal was to get rid of all properties that have an impact on this wrapping element. In most cases this usage was the &lt;code&gt;classNames&lt;/code&gt; attribute which added CSS classes to the wrapping element.&lt;/p&gt;

&lt;p&gt;Converting was done by adding these properties directly in the template of the component.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Making components tagless
&lt;/h3&gt;

&lt;p&gt;Wrapping elements of classic components can be removed by setting the &lt;code&gt;tagName&lt;/code&gt; to an empty string, hence the name “tagless” components. The &lt;code&gt;@tagName&lt;/code&gt; decorator from the &lt;a href="https://ember-decorators.github.io/ember-decorators/docs/decorators#component-decorators" rel="noopener noreferrer"&gt;ember-decorators&lt;/a&gt; package can be used to do this. This makes it easy to spot and clean up in a later phase.&lt;/p&gt;

&lt;p&gt;Making the component tagless in this phase still introduces breaking changes which we fixed together with adding the decorator.&lt;/p&gt;

&lt;p&gt;A common pitfall we noticed was that attributes on an Ember component had no place to be set and were dropped. In Glimmer you explicitly need to tell where the passed attributes have to be placed. This can be done by using the &lt;code&gt;...attributes&lt;/code&gt; syntax. Often this caused styling bugs as classes or id’s weren’t set. Our visual tests came in useful to detect these issues. If you’re interested in how we set up visual testing, check out &lt;a href="https://www.youtube.com/watch?v=m90m9lVEFlY" rel="noopener noreferrer"&gt;our talk at EmberFest 2022&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A second issue was that lifecycle hooks that depended on this wrapping element no longer got invoked. Those lifecycle events contain the Element reference, e.g. &lt;code&gt;didInsertElement&lt;/code&gt;. To migrate these we made use of the &lt;a href="https://github.com/emberjs/ember-render-modifiers" rel="noopener noreferrer"&gt;render-modifiers&lt;/a&gt; package. Ever since Glimmer and Octane, there are new ways to encapsulate this logic like using the constructor and destructor, writing custom modifiers, or using resources. For the sake of limiting the scope we opted to keep this a separate effort.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Removing Mixins
&lt;/h3&gt;

&lt;p&gt;Mixins were a way to share common code across different components. In Glimmer they’re no longer supported. We reviewed our mixins and listed a way of restructuring them as in most cases mixins could be replaced with a more specific way of sharing code.&lt;/p&gt;

&lt;p&gt;Common cases were template formatting logic which could be made into a helper, shared constants which could be moved to a separate file, and utility functions which could be separated as they didn’t require the Ember context. For usages that didn’t fit nicely in any of the standard ways of sharing code, we opted for creating custom class decorators as described in &lt;a href="https://www.pzuraq.com/blog/do-you-need-ember-object" rel="noopener noreferrer"&gt;“Do You Need EmberObject?”&lt;/a&gt; by Chris Garrett.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Removing deprecated lifecycle events
&lt;/h3&gt;

&lt;p&gt;In phase 5 a subset of &lt;a href="https://guides.emberjs.com/release/upgrading/current-edition/glimmer-components/#toc_lifecycle-hooks--modifiers" rel="noopener noreferrer"&gt;deprecated lifecycle hooks&lt;/a&gt; were already removed. There are still others left which are not bound to the wrapping element, like &lt;code&gt;didRender&lt;/code&gt;, &lt;code&gt;willUpdate&lt;/code&gt; and others. Removing these lifecycle events can be done using a similar strategy as used in phase 5. Generally they can also be replaced with native getters.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Removing observers
&lt;/h3&gt;

&lt;p&gt;Usage of observers has been discouraged in Ember for a long time. They were often overused when a better alternative was available, could cause performance issues, and were hard to debug. With Glimmer components, the &lt;code&gt;@observes&lt;/code&gt; decorator is also no longer supported.&lt;/p&gt;

&lt;p&gt;Refactoring of observers can be non-trivial as it requires you to think of your state updates differently. Rather than reacting to changes, Glimmer introduced autotracking which allows marking of inputs which should trigger UI updates. Some usages can be replaced by working with getters and autotracking. In other cases the &lt;code&gt;did-update&lt;/code&gt; modifier of the &lt;code&gt;render-modifiers&lt;/code&gt; package can be used as a replacement. Writing custom modifiers is also an option here.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. And finally … extending from Glimmer!
&lt;/h3&gt;

&lt;p&gt;Now that all classic specific features have been removed, it is time to extend the Glimmer component base class instead of the Ember classic one.&lt;/p&gt;

&lt;p&gt;By making this change, arguments will move to the &lt;code&gt;args&lt;/code&gt; object instead of the component’s local scope. Usages in the backing class have to be adjusted to use this step in between. One edge case to take into account is that by the change of this scope, they no longer override default values in the component. This can be resolved by writing a native getter which returns the argument from the &lt;code&gt;args&lt;/code&gt; object and falls back to a default in case the argument is not passed.&lt;/p&gt;

&lt;p&gt;Likewise, argument usages in the template also have to be updated to indicate the difference in scope. The &lt;code&gt;@&lt;/code&gt; prefix has to be set for arguments as the codemod didn’t handle this like mentioned in phase 2.&lt;/p&gt;

&lt;p&gt;Finally, the &lt;code&gt;tagName&lt;/code&gt; decorator added in phase 5 can be removed as Glimmer components are always tagless.&lt;/p&gt;

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

&lt;p&gt;This article provided a strategy to migrate large Ember codebases from classic to Glimmer components. Following this component migration ensures codebases don’t get stuck in the past. Even better, they unlock modern features Ember provides and new ones being worked on at the very moment!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>discuss</category>
      <category>machinelearning</category>
    </item>
  </channel>
</rss>
