<?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: Benny Powers 🇮🇱🇨🇦</title>
    <description>The latest articles on DEV Community by Benny Powers 🇮🇱🇨🇦 (@bennypowers).</description>
    <link>https://dev.to/bennypowers</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%2F74157%2F1d0119c2-14e0-4dd6-a3ec-084d7b3c6323.png</url>
      <title>DEV Community: Benny Powers 🇮🇱🇨🇦</title>
      <link>https://dev.to/bennypowers</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bennypowers"/>
    <language>en</language>
    <item>
      <title>Introducing Backlit: Lit SSR for Drupal, Hold the Node</title>
      <dc:creator>Benny Powers 🇮🇱🇨🇦</dc:creator>
      <pubDate>Thu, 19 Mar 2026 13:38:06 +0000</pubDate>
      <link>https://dev.to/bennypowers/introducing-backlit-lit-ssr-for-drupal-hold-the-node-20om</link>
      <guid>https://dev.to/bennypowers/introducing-backlit-lit-ssr-for-drupal-hold-the-node-20om</guid>
      <description>&lt;p&gt;Two years ago I wrote about &lt;a href="https://bennypowers.dev/posts/drupal-lit-ssr/" rel="noopener noreferrer"&gt;server rendering Lit web components with Drupal&lt;/a&gt;. The approach worked, but it required a Node.js HTTP service running alongside Drupal in a container. That meant Docker/Podman, networking between containers, and an extra process to manage. It was a proof of concept, and it felt like one.&lt;/p&gt;

&lt;p&gt;Well, introducing &lt;a href="https://github.com/bennypowers/backlit" rel="noopener noreferrer"&gt;Backlit&lt;/a&gt; -- a Drupal module that server-renders &lt;a href="https://lit.dev" rel="noopener noreferrer"&gt;Lit&lt;/a&gt; web components with &lt;a href="https://html.spec.whatwg.org/multipage/scripting.html#attr-template-shadowrootmode" rel="noopener noreferrer"&gt;Declarative Shadow DOM&lt;/a&gt;. No Node.js, no containers, no external services. Two lines to install, zero infrastructure to maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Backlit does
&lt;/h2&gt;

&lt;p&gt;If your Drupal site uses Lit web components (from a design system, a custom theme, or anywhere else), those components normally render client-side: the browser downloads JavaScript, defines the custom elements, and paints the shadow DOM. Until that happens, users see a flash of unstyled content or an empty box.&lt;/p&gt;

&lt;p&gt;Backlit moves that rendering to the server. When Drupal finishes building a page, Backlit pipes the HTML through a binary that injects each component's shadow DOM directly into the response as a &lt;code&gt;&amp;lt;template shadowrootmode="open"&amp;gt;&lt;/code&gt; element. The browser attaches the shadow root during HTML parsing -- before any JavaScript runs. Users see styled, laid-out content on first paint.&lt;/p&gt;

&lt;p&gt;Disable JavaScript entirely. The components still render. That is the way of the Lit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick start
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require bennypowers/backlit
drush en backlit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Composer downloads the right binary for your platform (linux, macOS, Windows; x64 and arm64). Drush enables the module. Done.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's different from the Node.js approach
&lt;/h2&gt;

&lt;p&gt;The previous post drew three fair criticisms. Here's how Backlit addresses each.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No more service to maintain.&lt;/strong&gt; The Node.js sidecar is gone. Backlit's binary is a static executable -- it starts on demand inside the PHP-FPM worker and dies with it. Nothing to monitor, restart, or deploy separately. &lt;code&gt;composer update&lt;/code&gt; handles everything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Minimal overhead.&lt;/strong&gt; The Node.js approach added ~50ms and ~100MB per request. Backlit adds ~0.32ms per render after a one-time 350ms cold start per worker, with a much smaller memory footprint (the binary is ~9MB, the WASM instance lightweight). With Drupal's page cache enabled, subsequent requests skip the binary entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Safe failure and author control.&lt;/strong&gt; If the binary is unavailable or returns an empty response, Backlit returns the original HTML unchanged -- SSR failure is invisible to end users. Beyond that, Backlit adds an "Enable SSR" checkbox to content types, so authors can disable SSR per-page. Not every layout needs shadow roots injected, and editorial teams should have the final say.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding your components
&lt;/h2&gt;

&lt;p&gt;Backlit doesn't ship with any component definitions. You provide them as plain JavaScript files -- the same LitElement classes you'd write for the browser, minus the &lt;code&gt;import&lt;/code&gt; statements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where to put them
&lt;/h3&gt;

&lt;p&gt;Drop &lt;code&gt;.js&lt;/code&gt; files in any of these locations (checked in order):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;$settings['backlit']['components_dir']&lt;/code&gt;&lt;/strong&gt; in &lt;code&gt;settings.php&lt;/code&gt; -- explicit path&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your active theme's &lt;code&gt;components/&lt;/code&gt; directory&lt;/strong&gt; -- e.g., &lt;code&gt;themes/custom/my_theme/components/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Any custom module's &lt;code&gt;js/&lt;/code&gt; directory&lt;/strong&gt; -- e.g., &lt;code&gt;modules/custom/my_ds/js/&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Backlit auto-discovers element names from &lt;code&gt;customElements.define()&lt;/code&gt; calls. No further configuration needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  One caveat
&lt;/h3&gt;

&lt;p&gt;The JS files can't use &lt;code&gt;import&lt;/code&gt; statements that reference &lt;code&gt;'lit'&lt;/code&gt; or other npm packages -- the WASM engine has no filesystem or module resolver. Instead, Backlit provides &lt;code&gt;LitElement&lt;/code&gt;, &lt;code&gt;html&lt;/code&gt;, &lt;code&gt;css&lt;/code&gt;, &lt;code&gt;classMap&lt;/code&gt;, &lt;code&gt;styleMap&lt;/code&gt;, &lt;code&gt;repeat&lt;/code&gt;, and &lt;code&gt;unsafeHTML&lt;/code&gt; as globals. If your design system distributes pre-built single-file component definitions, they'll work as-is. If not, a quick &lt;code&gt;esbuild --bundle&lt;/code&gt; strips the imports and produces a self-contained file.&lt;/p&gt;

&lt;p&gt;The component source is sent with each render request, but the WASM engine only evaluates it on the first render -- after that, &lt;code&gt;customElements.get()&lt;/code&gt; confirms the elements are already registered and skips the eval. Definitions stay warm across all subsequent renders. No build step, no npm, no bundler at deploy time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Administering Backlit
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Updating components
&lt;/h3&gt;

&lt;p&gt;When your front-end team ships a new version of the design system:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Replace the JS files in your components directory with the updated versions&lt;/li&gt;
&lt;li&gt;Clear Drupal's cache: &lt;code&gt;drush cr&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The next request restarts the binary with the new definitions. That's the entire update process. No &lt;code&gt;npm install&lt;/code&gt;, no container rebuild, no deployment pipeline for a sidecar service.&lt;/p&gt;

&lt;h3&gt;
  
  
  Updating the binary
&lt;/h3&gt;

&lt;p&gt;When a new version of Backlit is released with engine improvements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer update bennypowers/backlit
drush cr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Composer downloads the updated binary. The cache rebuild ensures the old process is replaced.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monitoring
&lt;/h3&gt;

&lt;p&gt;Backlit logs errors to Drupal's watchdog. If the binary fails to start (missing file, wrong permissions, unsupported platform), you'll see it in the Drupal logs. The page still renders -- just without DSD.&lt;/p&gt;

&lt;h3&gt;
  
  
  Caching
&lt;/h3&gt;

&lt;p&gt;Backlit runs on every uncached page response. If you have Drupal's Internal Page Cache or a reverse proxy (Varnish, Cloudflare) in front of your site, the rendered HTML (with DSD) is cached at that layer. Subsequent requests never hit the binary. This is the recommended production setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Per-content control
&lt;/h3&gt;

&lt;p&gt;Backlit adds an "Enable SSR" checkbox to all content types. It defaults to on. Authors can disable it for individual pages where SSR causes issues -- for example, if a component relies on client-side state that SSR can't replicate.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use Backlit
&lt;/h2&gt;

&lt;p&gt;Backlit is useful when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your site uses a Lit-based design system (like &lt;a href="https://ux.redhat.com" rel="noopener noreferrer"&gt;RHDS&lt;/a&gt;, &lt;a href="https://shoelace.style" rel="noopener noreferrer"&gt;Shoelace&lt;/a&gt;, &lt;a href="https://lion-web.netlify.app/" rel="noopener noreferrer"&gt;Lion&lt;/a&gt;, or your own)&lt;/li&gt;
&lt;li&gt;You care about first-paint performance (LCP, CLS)&lt;/li&gt;
&lt;li&gt;You want web components to render without JavaScript (accessibility, SEO, slow connections)&lt;/li&gt;
&lt;li&gt;You don't want to run Node.js alongside Drupal&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How it works under the hood
&lt;/h2&gt;

&lt;p&gt;The binary embeds a WASM module containing &lt;a href="https://bellard.org/quickjs/" rel="noopener noreferrer"&gt;QuickJS&lt;/a&gt; (a lightweight JavaScript engine) running &lt;a href="https://www.npmjs.com/package/@lit-labs/ssr" rel="noopener noreferrer"&gt;&lt;code&gt;@lit-labs/ssr&lt;/code&gt;&lt;/a&gt;. On startup, it reads your component JS files and evaluates them in QuickJS, registering custom elements. Then it enters a read loop: Drupal writes HTML to the binary's stdin (NUL-terminated), the binary renders every known custom element with Declarative Shadow DOM, and writes the result to stdout (also NUL-terminated).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Drupal (PHP) --stdin: HTML\0--&amp;gt; lit-ssr-runtime binary --&amp;gt; stdout: HTML-with-DSD\0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The WASM instance stays warm across renders. The ~350ms cold start is paid once per PHP-FPM worker. Every subsequent render: ~0.32ms.&lt;/p&gt;

&lt;p&gt;The binary is built with &lt;a href="https://go.dev" rel="noopener noreferrer"&gt;Go&lt;/a&gt; and &lt;a href="https://wazero.io" rel="noopener noreferrer"&gt;wazero&lt;/a&gt; (a pure-Go WASM runtime -- no CGo, no system dependencies). It's statically linked and runs on any Linux, macOS, or Windows machine. Yes, we support Windows. No, we haven't tested it. Godspeed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Node.js sidecar (2024)&lt;/th&gt;
&lt;th&gt;Backlit (2026)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cold start&lt;/td&gt;
&lt;td&gt;~500ms (Node.js boot + module load)&lt;/td&gt;
&lt;td&gt;~350ms (WASM compile, once per worker)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per-render&lt;/td&gt;
&lt;td&gt;~50ms (HTTP roundtrip + render)&lt;/td&gt;
&lt;td&gt;~0.32ms (pipe I/O + render)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependencies&lt;/td&gt;
&lt;td&gt;Node.js, npm, Docker/Podman&lt;/td&gt;
&lt;td&gt;Single binary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory&lt;/td&gt;
&lt;td&gt;~100MB (Node.js heap)&lt;/td&gt;
&lt;td&gt;~9MB binary (not yet benchmarked at runtime)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These numbers are from the builtin mode (compiled-in components) Go benchmark. Runtime mode -- which Backlit uses by default -- adds overhead for JSON serialization and source transmission, though it skips JS evaluation after the first render. Runtime mode benchmarks are on the to-do list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;This is a v0 release. Some honest caveats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lit only.&lt;/strong&gt; The SSR engine is &lt;code&gt;@lit-labs/ssr&lt;/code&gt;, which understands Lit's template system. Vanilla custom elements, Stencil, FAST, or other frameworks won't render. If you're using a mixed component library, only the Lit-based elements get DSD.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@lit-labs/ssr&lt;/code&gt; is experimental.&lt;/strong&gt; The Lit team marks it as such. It doesn't support &lt;code&gt;@lit/context&lt;/code&gt;, and some component patterns (anything that calls DOM APIs during server render) will fail. Components need to be &lt;a href="https://lit.dev/docs/ssr/authoring/" rel="noopener noreferrer"&gt;SSR-compatible&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Component JS must not import from &lt;code&gt;'lit'&lt;/code&gt;.&lt;/strong&gt; The WASM engine provides Lit APIs (&lt;code&gt;LitElement&lt;/code&gt;, &lt;code&gt;html&lt;/code&gt;, &lt;code&gt;css&lt;/code&gt;, etc.) as globals. Components that use these globals directly work fine. Components with &lt;code&gt;import { LitElement } from 'lit'&lt;/code&gt; will fail because QuickJS has no module resolver for npm packages. If your design system distributes components as ES modules, strip the imports first (e.g., &lt;code&gt;esbuild --bundle&lt;/code&gt; or a simple regex).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source sent on every request.&lt;/strong&gt; The runtime CLI currently sends the full component JS source with each render payload. The WASM side skips re-evaluation, but the serialization overhead is still there. Easy fix for the next release: send source only on the first request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No streaming.&lt;/strong&gt; The entire page HTML is buffered, piped through the binary, and buffered back. For very large pages this adds latency. Streaming SSR is technically possible but not implemented.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single process.&lt;/strong&gt; One &lt;code&gt;lit-ssr-runtime&lt;/code&gt; process per PHP-FPM worker. Under high concurrency, this could be a bottleneck. A pool-per-worker model is straightforward to add.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Advanced: compiled mode
&lt;/h2&gt;

&lt;p&gt;For sites where every millisecond counts, you can build a custom WASM module with your components baked in, skipping JS evaluation entirely:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clone &lt;a href="https://github.com/bennypowers/lit-ssr-wasm" rel="noopener noreferrer"&gt;lit-ssr-wasm&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Write your components in &lt;code&gt;src/components/&lt;/code&gt; (standard TypeScript with decorators)&lt;/li&gt;
&lt;li&gt;Import them in &lt;code&gt;src/entry.ts&lt;/code&gt;, add tag names to &lt;code&gt;KNOWN_ELEMENTS&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm run build&lt;/code&gt; (requires &lt;a href="https://github.com/bytecodealliance/javy" rel="noopener noreferrer"&gt;Javy&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Build the CLI: &lt;code&gt;cd go &amp;amp;&amp;amp; make linux-x64&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Replace the binary in Backlit's &lt;code&gt;bin/&lt;/code&gt; directory&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is the "I have a build pipeline and I'm not afraid to use it" option. Most sites won't need it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try the demo
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/bennypowers/drupal-lit-ssr-wasm" rel="noopener noreferrer"&gt;drupal-lit-ssr-wasm&lt;/a&gt; repo has a fully working example with auto-install, demo content, and Docker Compose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/bennypowers/drupal-lit-ssr-wasm
&lt;span class="nb"&gt;cd &lt;/span&gt;drupal-lit-ssr-wasm
./scripts/download-binary.sh v0.0.2
podman compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first boot takes a few minutes (Drupal auto-installs via Drush). Browse to &lt;a href="http://localhost:8888" rel="noopener noreferrer"&gt;http://localhost:8888&lt;/a&gt;. The front page shows server-rendered web components. Disable JavaScript to verify.&lt;/p&gt;

&lt;h2&gt;
  
  
  Live demo
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://bennypowers.github.io/lit-ssr-wasm/compiled.html" rel="noopener noreferrer"&gt;lit-ssr-wasm demo&lt;/a&gt; runs the actual WASM modules directly in the browser using a minimal WASI shim. The compiled mode demo does not load any JavaScript definition of &lt;code&gt;&amp;lt;my-alert&amp;gt;&lt;/code&gt; on the page -- the styles you see come entirely from Declarative Shadow DOM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alternatives
&lt;/h2&gt;

&lt;p&gt;Backlit ships the CLI binary approach because it's the easiest to deploy. But I've considered a few other directions, and I'd be happy to explore any of them further if there's interest -- consider this a teaser of what Backlit &lt;em&gt;could&lt;/em&gt; become.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://extism.org" rel="noopener noreferrer"&gt;Extism&lt;/a&gt; PHP SDK&lt;/strong&gt; -- a WASM plugin framework with a Composer-installable PHP SDK. In-process execution without shelling out. Requires &lt;code&gt;libextism.so&lt;/code&gt; on the host, but removes the subprocess entirely.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/wasmerio/wasmer-php" rel="noopener noreferrer"&gt;wasmer-php&lt;/a&gt;&lt;/strong&gt; -- a PECL extension for running WASM directly in PHP. More complex to deploy (requires compiling the extension), but eliminates IPC overhead entirely.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;PHP FFI + Go shared library&lt;/strong&gt; -- build a &lt;code&gt;.so&lt;/code&gt; from the Go code using &lt;code&gt;-buildmode=c-shared&lt;/code&gt; and call it via PHP's FFI. More complex, less isolation, but potentially the lowest-latency path.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If one of these fits your deployment better than the binary approach, &lt;a href="https://github.com/bennypowers/backlit/issues" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt; and let me know -- that's exactly the kind of feedback that shapes what gets built next.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Backlit is deliberately minimal. Future directions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manage multiple &lt;code&gt;lit-ssr&lt;/code&gt; processes (one per PHP-FPM worker) for parallel rendering&lt;/li&gt;
&lt;li&gt;Add a Drush command for pre-warming the WASM instance on deploy&lt;/li&gt;
&lt;li&gt;Drupal admin UI for managing component sources&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://github.com/bennypowers/backlit" rel="noopener noreferrer"&gt;Backlit module&lt;/a&gt;, the &lt;a href="https://github.com/bennypowers/lit-ssr-wasm" rel="noopener noreferrer"&gt;lit-ssr-wasm engine&lt;/a&gt;, and a &lt;a href="https://github.com/bennypowers/drupal-lit-ssr-wasm" rel="noopener noreferrer"&gt;working Drupal demo&lt;/a&gt; are all on GitHub.&lt;/p&gt;

&lt;p&gt;This is early days. I'd love to hear what breaks, what's missing, and what you'd build with it. &lt;a href="https://github.com/bennypowers/backlit/issues" rel="noopener noreferrer"&gt;Open an issue&lt;/a&gt; on the Backlit repo, or on &lt;a href="https://github.com/bennypowers/lit-ssr-wasm/issues" rel="noopener noreferrer"&gt;lit-ssr-wasm&lt;/a&gt; for the engine itself. PRs welcome. Bug reports welcomed even more warmly.&lt;/p&gt;

&lt;p&gt;Thanks to &lt;a href="https://github.com/zeroedin" rel="noopener noreferrer"&gt;Steven Spriggs&lt;/a&gt; for talking through the idea and vetting the approach with me. Steven built his own Go-based Lit SSR pipeline, &lt;code&gt;golit&lt;/code&gt;, taking a different path to the same destination -- worth knowing about if Backlit's tradeoffs don't fit your setup.&lt;/p&gt;

&lt;p&gt;You know, it's funny; I built &lt;code&gt;lit-ssr-wasm&lt;/code&gt; to scratch a completely different itch -- a live preview feature in &lt;a href="https://github.com/bennypowers/cem" rel="noopener noreferrer"&gt;&lt;code&gt;cem serve&lt;/code&gt;&lt;/a&gt;. Backlit happened because, once the WASM module existed, the Drupal integration practically wrote itself. Two years of "this should be simpler" collapsed into an afternoon. That's the thing about building on standards. WASM is to backend runtimes what web components are to the browser: write it once, run it anywhere, watch it quietly solve problems you weren't even trying to solve. I'll take it.&lt;/p&gt;

</description>
      <category>drupal</category>
      <category>webcomponents</category>
      <category>lit</category>
      <category>ssr</category>
    </item>
    <item>
      <title>GNOME 2.20 but its Web Components</title>
      <dc:creator>Benny Powers 🇮🇱🇨🇦</dc:creator>
      <pubDate>Sun, 01 Mar 2026 13:48:22 +0000</pubDate>
      <link>https://dev.to/bennypowers/gnome-220-but-its-web-components-3dk6</link>
      <guid>https://dev.to/bennypowers/gnome-220-but-its-web-components-3dk6</guid>
      <description>&lt;p&gt;A couple of days ago, this youtube video rolled into my recommendations feed&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/tkUgOT22F5s"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;It's an essay by this youtuber called &lt;a href="https://onio.club/" rel="noopener noreferrer"&gt;onion boots&lt;/a&gt; on how websites used to be weird and playful. And I got to thinking about how I missed that old timey tinkerer's network. Then I thought at my website, and how it looks like... a website, and decided it wasn't weird enough.&lt;/p&gt;

&lt;p&gt;Well, its a few days later and I've turned my website into the GNOME 2 desktop circa 2002. It's built out of web components, using Lit. Specifically I used lit-ssr to generate DSD templates, and the newly baseline &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API" rel="noopener noreferrer"&gt;View Transitions&lt;/a&gt; API to make "focusing" (navigating) between "windows" (pages) feel more fluid and natural.&lt;/p&gt;

&lt;p&gt;I'm going for a classic feel here, so I designed the webmentions (which used to appear in a sidebar or under the post) UI to look like a &lt;a href="https://pidgin.im/" rel="noopener noreferrer"&gt;Pidgin IM&lt;/a&gt; session, and the slide decks page looks (kinda) like &lt;a href="https://www.openoffice.org/product/impress.html" rel="noopener noreferrer"&gt;OOO Impress&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It's got two themes. Clearlooks is based on the original CSS, and Clearlooks dark is "inspired" thereby. Maybe I'll add more themes down the line if I'm feeling up to it. I also shipped the default GNOME desktop wallpapers, so pick whichever one you like best.&lt;/p&gt;

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

&lt;p&gt;It's even got Minesweeper, and calculator!&lt;/p&gt;

&lt;p&gt;So yeah this is pretty silly but I'm also committed to the bit 🤣. Which app should I implement next? Send me a webmention to this page.&lt;/p&gt;

</description>
      <category>gnome</category>
      <category>webcomponents</category>
      <category>html</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Introducing The Custom Elements Dev Server</title>
      <dc:creator>Benny Powers 🇮🇱🇨🇦</dc:creator>
      <pubDate>Thu, 04 Dec 2025 14:00:00 +0000</pubDate>
      <link>https://dev.to/bennypowers/introducing-the-custom-elements-dev-server-5eog</link>
      <guid>https://dev.to/bennypowers/introducing-the-custom-elements-dev-server-5eog</guid>
      <description>&lt;p&gt;If you're building web components, you've probably used Storybook. It's great! But it's also framework-focused, requires writing stories in JavaScript/MDX, and adds a lot of overhead when all you really want is to see your HTML custom elements in a browser.&lt;/p&gt;

&lt;p&gt;That's why I built &lt;strong&gt;cem serve&lt;/strong&gt;: an opinionated development server designed specifically for web components and HTML.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fywr8fl9nahoj1y0qhnn7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fywr8fl9nahoj1y0qhnn7.png" alt="Screenshot of cem dev server with an rh-avatar demo and knobs visible" width="800" height="672"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is cem serve?
&lt;/h2&gt;

&lt;p&gt;Think &lt;strong&gt;Storybook for HTML&lt;/strong&gt;. It's a &lt;em&gt;custom-elements manifest-driven&lt;/em&gt; development server for your web components. You write short HTML partials which show off your components, and &lt;code&gt;cem serve&lt;/code&gt; wraps them up into a live-reloading dev server with auto-generated knobs.&lt;/p&gt;

&lt;p&gt;The server's UI and workflow are based on your &lt;a href="https://custom-elements-manifest.open-wc.org/" rel="noopener noreferrer"&gt;Custom Elements Manifest&lt;/a&gt;. It uses the same code as in the &lt;a href="https://bennypowers.dev/cem/docs/commands/generate/" rel="noopener noreferrer"&gt;&lt;code&gt;cem generate&lt;/code&gt;&lt;/a&gt; command to discover HTML demos according to your configuration, then it generates demo pages complete with &lt;strong&gt;interactive knobs&lt;/strong&gt;, based on your manifest metadata. When you edit demo files or their dependencies, the server reloads the page for you.&lt;/p&gt;

&lt;p&gt;One of the nicest features is the buildless development workflow: &lt;code&gt;cem serve&lt;/code&gt; automatically transforms TypeScript to JavaScript, and CSS module scripts (e.g. &lt;code&gt;import styles from './my-button.css' with { type: 'css' }&lt;/code&gt;), on-the-fly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;Write JSDoc, or HTML and CSS comments to document your components. If you're using Lit and TypeScript, &lt;a href="https://bennypowers.dev/cem/docs/commands/generate/" rel="noopener noreferrer"&gt;&lt;code&gt;cem generate&lt;/code&gt;&lt;/a&gt; will pick up on your &lt;code&gt;@property&lt;/code&gt; decorators and add them to the manifest as attributes. It also automatically detects slots in your &lt;code&gt;render()&lt;/code&gt; method, so you don't need to declare them in JSDoc.&lt;/p&gt;

&lt;p&gt;Tell &lt;code&gt;cem&lt;/code&gt; about your demo files by adding the &lt;a href="https://bennypowers.github.io/cem/docs/commands/generate/#demo-discovery" rel="noopener noreferrer"&gt;&lt;code&gt;generate.demoDiscovery&lt;/code&gt; setting&lt;/a&gt;, or by using the &lt;code&gt;@demo&lt;/code&gt; JSDoc tag.&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="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;css&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;lit&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;customElement&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="s1"&gt;lit/decorators.js&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;styles&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;./my-button.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * A button for performing actions
 * @demo demo/index.html
 */&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;customElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-button&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;class&lt;/span&gt; &lt;span class="nc"&gt;MyButton&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&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;styles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="cm"&gt;/** Button variant (primary, secondary, danger) */&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="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;primary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="cm"&gt;/** Button text */&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="nx"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="cm"&gt;/** Disabled state */&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;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Boolean&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="nx"&gt;disabled&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="nf"&gt;render&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="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
      &amp;lt;button ?disabled=&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;disabled&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;
        &amp;lt;!-- Text label, or additional content, like icons --&amp;gt;
        &amp;lt;slot&amp;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;label&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/slot&amp;gt;
      &amp;lt;/button&amp;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;Optionally, run the &lt;code&gt;cem generate&lt;/code&gt; command to create your &lt;code&gt;custom-elements.json&lt;/code&gt; file, with all your component metadata - attributes, properties, events, slots, everything.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cem generate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't worry about skipping this step, &lt;code&gt;cem serve&lt;/code&gt; will create one for you in-memory when you launch the server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Writing HTML Demos
&lt;/h3&gt;

&lt;p&gt;Demo files are HTML partials which users of your elements can copy and paste on to a page. &lt;code&gt;cem serve&lt;/code&gt; will wrap them up in a helpful UI, so don't worry about &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;, or &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; elements.&lt;/p&gt;

&lt;p&gt;Here's an example demo:&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;my-button&lt;/span&gt; &lt;span class="na"&gt;variant=&lt;/span&gt;&lt;span class="s"&gt;"primary"&lt;/span&gt;
           &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Click me"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/my-button&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="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@my-elements/my-button.js&lt;/span&gt;&lt;span class="dl"&gt;'&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The demo includes everything a user would need to drop the element on a page (assuming an import map is set up) You can also include inline style tags or link to stylesheets if you need to. The idea is to demonstrate a complete unit, in terms of simple HTML, CSS, and if necessary, JavaScript.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running the Dev Server
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @pwrs/cem serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;a href="http://localhost:8000" rel="noopener noreferrer"&gt;http://localhost:8000&lt;/a&gt; and you'll see a listing of all the demos in your project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Auto-Generated Knobs
&lt;/h3&gt;

&lt;p&gt;With &lt;code&gt;cem serve&lt;/code&gt;, knobs are &lt;strong&gt;auto-generated from your manifest&lt;/strong&gt;. Document your component once, get knobs for free, without defining them explicitly in code.&lt;/p&gt;

&lt;p&gt;Knobs are based on the entries in your manifest, so if your attributes and class fields are well-typed (via JSDoc or TypeScript typings), then the generated knobs will match: text input for strings, number input for numbers, select fields for enums.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it Matters
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;cem&lt;/code&gt; project in general pursues a &lt;em&gt;standards-based development model&lt;/em&gt;, where the least amount of magic and framework cruft is applied to the development loop.&lt;/p&gt;

&lt;p&gt;The addition of the dev server closes the gap, now &lt;code&gt;cem&lt;/code&gt; is a complete  development toolkit for web components, combining &lt;em&gt;manifest generation&lt;/em&gt;, &lt;em&gt;dev server&lt;/em&gt;, &lt;em&gt;lsp&lt;/em&gt; for editor support, and &lt;em&gt;mcp&lt;/em&gt; for AI support.  &lt;/p&gt;

&lt;h3&gt;
  
  
  Demos are Just HTML
&lt;/h3&gt;

&lt;p&gt;With &lt;code&gt;cem serve&lt;/code&gt;'s workflow, your demos aren't framework-specific stories, or a custom markdown flavour; they're &lt;strong&gt;pure HTML&lt;/strong&gt;. That means your users can &lt;strong&gt;copy them directly&lt;/strong&gt; from your docs. They also work in any environment (React, Vue, vanilla JS, whatever). Your demos don't require any special knowledge to read and understand, anyone who knows HTML, JS, and the DOM can read your demos. There's no framework version lock-in, and LLM&lt;br&gt;
coding tools can easily convert them from basic DOM to framework integrations, if needed, especially by leveraging &lt;a href="https://bennypowers.dev/cem/docs/mcp/" rel="noopener noreferrer"&gt;&lt;code&gt;cem mcp&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Does This Compare?
&lt;/h2&gt;

&lt;p&gt;Let's look at &lt;code&gt;cem serve&lt;/code&gt; alongside other popular development tools:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;cem serve&lt;/th&gt;
&lt;th&gt;Storybook&lt;/th&gt;
&lt;th&gt;Vite&lt;/th&gt;
&lt;th&gt;@web/dev-server&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Purpose&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Web component preview&lt;/td&gt;
&lt;td&gt;Component stories&lt;/td&gt;
&lt;td&gt;General dev server&lt;/td&gt;
&lt;td&gt;General dev server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Demo Format&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Plain HTML&lt;/td&gt;
&lt;td&gt;JSX/MDX stories&lt;/td&gt;
&lt;td&gt;HTML/Framework&lt;/td&gt;
&lt;td&gt;HTML&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Component Isolation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Built-in&lt;/td&gt;
&lt;td&gt;✅ Built-in&lt;/td&gt;
&lt;td&gt;❌ None&lt;/td&gt;
&lt;td&gt;❌ None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Interactive Knobs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Auto-generated&lt;/td&gt;
&lt;td&gt;✅ Manual setup&lt;/td&gt;
&lt;td&gt;❌ None&lt;/td&gt;
&lt;td&gt;❌ None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Demo Discovery&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Manifest-driven&lt;/td&gt;
&lt;td&gt;✅ Story files&lt;/td&gt;
&lt;td&gt;❌ Manual&lt;/td&gt;
&lt;td&gt;❌ Manual&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TypeScript&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ On-the-fly&lt;/td&gt;
&lt;td&gt;⚠️ Requires build&lt;/td&gt;
&lt;td&gt;✅ On-the-fly&lt;/td&gt;
&lt;td&gt;⚠️ Plugin needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reload Strategy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Full page&lt;/td&gt;
&lt;td&gt;HMR/Full page&lt;/td&gt;
&lt;td&gt;HMR&lt;/td&gt;
&lt;td&gt;Full page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Configuration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Low (manifest)&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  When to Use Each
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Use @web/dev-server&lt;/strong&gt; or Vite if you need a generic, fully customizable dev server. &lt;strong&gt;Use Storybook&lt;/strong&gt; if you're locked in to legacy frameworks, are hooked on their extensive addon ecosystem, or just have thing for MDX.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use cem serve&lt;/strong&gt; when you're building web components in isolation and want a hassle-free, streamlined development workflow.&lt;/p&gt;

&lt;p&gt;The sweet spot for &lt;code&gt;cem serve&lt;/code&gt; is &lt;strong&gt;component libraries&lt;/strong&gt; that need to work across frameworks. Your demos are HTML, so they're portable. Your knobs come from the manifest you're already generating. Zero configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&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; @pwrs/cem
npx @pwrs/cem serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;install &lt;/span&gt;bennypowers.dev/cem
cem serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Works best with &lt;code&gt;LitElement&lt;/code&gt; or vanilla custom elements. If you've already got a Custom Elements Manifest, you're golden.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/bennypowers/cem" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bennypowers.dev/cem/serve/" rel="noopener noreferrer"&gt;Serve Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Built with Go, TypeScript, PatternFly Design System, and Web Components. Licensed GPL-3.0-or-later.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Have questions or feedback? &lt;a href="https://github.com/bennypowers/cem/issues" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webcomponents</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Your Design System is Not a Solution</title>
      <dc:creator>Benny Powers 🇮🇱🇨🇦</dc:creator>
      <pubDate>Mon, 23 Sep 2024 15:12:17 +0000</pubDate>
      <link>https://dev.to/bennypowers/your-design-system-is-not-a-solution-52p8</link>
      <guid>https://dev.to/bennypowers/your-design-system-is-not-a-solution-52p8</guid>
      <description>&lt;p&gt;You've got a problem. Your painting fell, and you want to hang it back up.&lt;br&gt;
You grab a hammer and some nails, you get to swinging, you make a few&lt;br&gt;
adjustments, and &lt;em&gt;Hey Presto!&lt;/em&gt; before you know it, your problem is solved.&lt;/p&gt;

&lt;p&gt;You have implemented a &lt;strong&gt;solution&lt;/strong&gt;: you hung the painting.&lt;/p&gt;

&lt;p&gt;Just then, your neighbour knocks on the door. Wouldn't you know it? he &lt;em&gt;also&lt;/em&gt;&lt;br&gt;
has a problem. &lt;em&gt;His&lt;/em&gt; painting has fallen, too. He &lt;em&gt;also&lt;/em&gt; needs a solution. Being &lt;br&gt;
the magnanimous, good-looking, and preturnaturally charming neighbour that you &lt;br&gt;
are, you leap into motion, practically stuffing his arms with hammer and nails, &lt;br&gt;
and you send him on his way, richly satisfied by your own generosity.&lt;/p&gt;

&lt;p&gt;An hour later, he knocks again. You see — your house is built of timber and&lt;br&gt;
drywall, while your neighbour's house is built of cinderblocks and plaster. Your&lt;br&gt;
neighbour now has two problems — his painting is still on the floor, &lt;em&gt;and&lt;/em&gt; he &lt;br&gt;
now has an unsightly hole in the wall.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools, or Solutions
&lt;/h2&gt;

&lt;p&gt;Now, your solution needed a bunch of tools to get 'er done:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the hammer&lt;/li&gt;
&lt;li&gt;the nails&lt;/li&gt;
&lt;li&gt;maybe a level or a tape measure&lt;/li&gt;
&lt;li&gt;your skill at swinging the hammer&lt;/li&gt;
&lt;li&gt;barring that, a bag of frozen peas&lt;/li&gt;
&lt;li&gt;the impeccable taste of your living companions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some of those tools were essential to the solution, others less so. But they&lt;br&gt;
all have one thing in common:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;none of those tools is the solution to your problem&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;If you really needed to, you could probably hang that painting with an old shoe&lt;/p&gt;

&lt;p&gt;Consider, too, that your solution didn't work for your neighbour, who built with &lt;br&gt;
cinderblocks and plaster, and actually made things worse for him. What went &lt;br&gt;
wrong? Was it the tools you gave him? Not necessarily. He might have used the &lt;br&gt;
hammer you gave him to drive special cinderblock nails or those plastic-and-tack &lt;br&gt;
plaster hooks.&lt;/p&gt;

&lt;p&gt;The problem wasn't the tools — &lt;em&gt;the problem was seeing the tools as the &lt;br&gt;
solution&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Jackhammers require care in use. Thumbtacks are cheap, plentiful, and not &lt;br&gt;
particularly dangerous. Both are needed on a construction site — one to break &lt;br&gt;
large blocks of concrete and rock, one to hang plans and blueprints. Both tools &lt;br&gt;
are important — crucial even — in their own ways, but they have widely different &lt;br&gt;
costs, lifespans, and restrictions on their use.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;em&gt;Cui Bono&lt;/em&gt;?
&lt;/h2&gt;

&lt;p&gt;If you've been following along, you're probably waiting for the punchline,&lt;br&gt;
so here it is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Your design system is not a solution, it's a &lt;em&gt;toolkit&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So why do companies adopt design systems? Are they just spinning their wheels?&lt;br&gt;
Well, my desire to continue receiving a steady paycheque requires me to say, "No, &lt;br&gt;
they aren't! Design systems really do solve problems for companies!" But there's&lt;br&gt;
an important difference between the kind of problem which design systems solve&lt;br&gt;
and &lt;em&gt;your team&lt;/em&gt;'s design problem.&lt;/p&gt;

&lt;p&gt;Companies adopt design systems because they want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a "single source of truth"&lt;/li&gt;
&lt;li&gt;consistent visual style across large teams&lt;/li&gt;
&lt;li&gt;consistent standards of accessibility&lt;/li&gt;
&lt;li&gt;UX/UI recommendations lovingly crafted by specialists&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of these are fancy ways of saying they want to establish and enforce&lt;br&gt;
rules and guidelines for producing user interfaces. Design systems help solve &lt;br&gt;
that problem by centralizing the know-how for building the components of those &lt;br&gt;
interfaces in a way which can be easily ported to different teams'&lt;br&gt;
workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mowing the Neighbour's Lawn
&lt;/h2&gt;

&lt;p&gt;Design systems can't and shouldn't try to solve every problem for every team. &lt;br&gt;
This industry is by its nature prone to widely varied opinions and approaches, &lt;br&gt;
one often incompatible with the other, and establishing a baseline is hard &lt;br&gt;
enough without shipping bespoke implementations for each team that comes along.&lt;br&gt;
The reasons for this become immediately clear once you shift perspective from &lt;br&gt;
that of the product maintainer to that of the design system maintainer.&lt;/p&gt;

&lt;p&gt;The specialist skills needed to produce a design system are not the same as &lt;br&gt;
those needed to ship products. Product teams often need to move quickly to ship&lt;br&gt;
new features, built on top of the previous framework, and they often don't have&lt;br&gt;
the luxury of The Big Rewrite™.&lt;/p&gt;

&lt;p&gt;From the design system maintainer's point of view, the customer is actually the&lt;br&gt;
product owner/developer. That's not to excuse design systems from concerning &lt;br&gt;
themselves with end users — quite the opposite. But rather, as toolmakers, &lt;br&gt;
their primary responsibility is to produce quality tools for their fellow &lt;br&gt;
craftsmen.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you don't own the problem, you can't own the solution&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Since different projects have different toolchains, histories, and processes,&lt;br&gt;
they have different constraints as well. What appears to be a non-starter to one&lt;br&gt;
maintainer is a no-brainer to another. Because of this, design system engineers&lt;br&gt;
would do well to focus their effort on &lt;a href="https://infrequently.org/2020/06/platform-adjacency-theory/" rel="noopener noreferrer"&gt;platforms&lt;/a&gt; rather than&lt;br&gt;
&lt;a href="https://infrequently.org/series/reckoning/" rel="noopener noreferrer"&gt;frameworks&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech Debt up to Here
&lt;/h2&gt;

&lt;p&gt;So, your design system is now free to build on top of platform primitives, and&lt;br&gt;
there are greenfield projects and high-velocity teams reaping the benefits of &lt;br&gt;
platform-oriented development. &lt;em&gt;Lechaim&lt;/em&gt; — 🥃 Fancy bourbon all around! But that &lt;br&gt;
still doesn't solve the problem of legacy framework integration. Who's left &lt;br&gt;
holding the bag?&lt;/p&gt;

&lt;p&gt;Some organizations solve this by reimplementing their design system for every &lt;br&gt;
framework that comes along. To my taste, this practice is error-prone; remember &lt;br&gt;
our initial goals from above. Web development is complex enough when targeting &lt;br&gt;
the browser: duplicating that effort across multiple frameworks scales that &lt;br&gt;
complexity geometrically. It also requires more resources, effort, attention, &lt;br&gt;
and time.&lt;/p&gt;

&lt;p&gt;Moreover, tracking integration problems inside the design system repositories&lt;br&gt;
can lead to an awkward situation where greenfield users might mistakenly adopt&lt;br&gt;
the workarounds and second-bests in place of the happy path. There are costs to &lt;br&gt;
increasing API surface, and teams responsible for shipping tools to diverse&lt;br&gt;
projects shouldn't be burdened with the maintenance of those workarounds, when&lt;br&gt;
they have other customers who don't need — and shouldn't try — to adopt them.&lt;/p&gt;

&lt;p&gt;Design systems that rest on platform features like custom elements, shadow DOM,&lt;br&gt;
import maps, and plain-old CSS can adapt to multiple projects and frameworks.&lt;br&gt;
It remains true that legacy frameworks have varying degrees of capability when&lt;br&gt;
it comes to integrating with platform features newer than 2019, but it's not&lt;br&gt;
impossible by any stretch of the imagination.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solving Integration Problems
&lt;/h2&gt;

&lt;p&gt;Rather than being tasked with owning, implementing, and delivering solutions to&lt;br&gt;
legacy framework integration problems, design system teams should be empowered &lt;br&gt;
to &lt;em&gt;consult&lt;/em&gt; with and &lt;em&gt;advise&lt;/em&gt; those teams to solve the problems on their own&lt;br&gt;
terms.&lt;/p&gt;

&lt;p&gt;Design system teams can solve integration problems, but they can't own them.&lt;br&gt;
It's crucial for teams to communicate clearly and openly, and to understand the&lt;br&gt;
business needs that design systems solve. When approaching a difficult&lt;br&gt;
integration, everyone at the table needs to understand the boundaries and&lt;br&gt;
junctions between business needs, framework limitations, and design system&lt;br&gt;
priorities. Downstream teams have to be willing to work with design systems&lt;br&gt;
people to integrate solutions which serve the broader business, and design&lt;br&gt;
systems people have to be willing and able to cross the aisle to work with&lt;br&gt;
downstream on their turf.&lt;/p&gt;

&lt;p&gt;Clear boundaries and the application of &lt;a href="https://en.wikipedia.org/wiki/Robustness_principle" rel="noopener noreferrer"&gt;Postel's law&lt;/a&gt; are the&lt;br&gt;
fundamentals of fruitful large-scale software collaboration.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Be conservative in what you do, be liberal in what you accept from others.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Many thanks to my design-systems colleagues Steven Spriggs, Brian Ferry, and &lt;br&gt;
Greg Gibson for their helpful notes on earlier drafts of this post.&lt;/p&gt;

</description>
      <category>webcomponents</category>
      <category>designsystem</category>
    </item>
    <item>
      <title>Typescript Eleventy Config</title>
      <dc:creator>Benny Powers 🇮🇱🇨🇦</dc:creator>
      <pubDate>Tue, 03 Sep 2024 15:02:14 +0000</pubDate>
      <link>https://dev.to/bennypowers/typescript-eleventy-config-3m91</link>
      <guid>https://dev.to/bennypowers/typescript-eleventy-config-3m91</guid>
      <description>&lt;p&gt;There are a bunch of guides out there explaining how to write 11ty configs in typescript so that you can compile them prior to running 11ty. This isn't one of those. I'm here to explain how to run eleventy &lt;em&gt;on your typescript configs&lt;/em&gt;, with all the familiar editor ergonomics, without building your config files at all. Dev time, meet runtime.&lt;/p&gt;

&lt;p&gt;You'll need node.js 22.6 or later, which adds the &lt;a href="https://nodejs.org/en/blog/release/v22.6.0#experimental-typescript-support-via-strip-types" rel="noopener noreferrer"&gt;&lt;code&gt;--experimental-strip-types&lt;/code&gt;&lt;/a&gt; flag. What this does is tell node to just ignore typescript syntax, as if it was a comment. Node here is basically implementing the &lt;a href="https://tc39.es/proposal-type-annotations/" rel="noopener noreferrer"&gt;types as comments proposal&lt;/a&gt;, and I hope everyone else follows suit quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://example.com/posts/typescript-11ty-config/#doing-the-cool-kids'-homework" rel="noopener noreferrer"&gt;Doing the Cool Kids' Homework&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Now, 11ty follows the &lt;a href="https://dev.to/dakmor/type-safe-web-components-with-jsdoc-4icf"&gt;types-in-jsdoc&lt;/a&gt; approach, which is fine i guess whatever, but for some reason, they don't then ship those types in &lt;code&gt;.d.ts&lt;/code&gt; files, so we'll have to build them ourselves. Run this script in &lt;code&gt;postinstall&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;$&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;execa&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;Manifest&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;../package.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;`npx tsc node_modules/@11ty/eleventy/src/UserConfig.js
        --declaration
        --allowJs
        --emitDeclarationOnly
        --moduleResolution nodenext
        --module nodenext
        --target esnext`&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;pkg&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;Manifest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dependencies&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;pkg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@11ty&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;spec&lt;/span&gt; &lt;span class="o"&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;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pkg&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file://&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="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;`npx tsc &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
              --declaration
              --allowJs
              --emitDeclarationOnly
              --moduleResolution nodenext
              --module nodenext
              --target esnext`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&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;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&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="s2"&gt;`Wrote types for &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pkg&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="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;Shipping &lt;code&gt;.d.ts&lt;/code&gt; type declarations in your npm library is an accessibility issue. Your users with cognitive disabilities like ADHD can't afford to keep alt-tabbing back to your docs site. I hope the 11ty authors will integrate this into their build system soon.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://example.com/posts/typescript-11ty-config/#typescript%2C-ho!" rel="noopener noreferrer"&gt;TypeScript, Ho!&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;That having been accomplished, rename your config file and import the UserConfig type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mv &lt;/span&gt;eleventy.config.js eleventy.config.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;UserConfig&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;@11ty/eleventy&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;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eleventyConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;eleventyConfig&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;And watch those LSP-completion suggestions roll in.&lt;/p&gt;

&lt;p&gt;If you have split your config into multiple files, you need to enable &lt;code&gt;.ts&lt;/code&gt; extension in your &lt;code&gt;tsconfig.json&lt;/code&gt;. Also disable emit, because we're not actually running &lt;code&gt;tsc&lt;/code&gt; at any point, this is all for neovim.&lt;/p&gt;

&lt;p&gt;Here, crib mine:&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&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="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NodeNext"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"resolveJsonModule"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"allowSyntheticDefaultImports"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"moduleResolution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NodeNext"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"allowImportingTsExtensions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ESNext"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"noEmit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &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="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Last thing is to actually run it, and to do that we'll use the &lt;code&gt;NODE_OPTIONS&lt;/code&gt; environment variable&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="nl"&gt;"scripts"&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="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NODE_OPTIONS='--experimental-strip-types' eleventy --config=eleventy.config.ts --incremental"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NODE_OPTIONS='--experimental-strip-types' eleventy --config=eleventy.config.ts --serve --incremental"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;a href="https://example.com/posts/typescript-11ty-config/#gimmie-the-code" rel="noopener noreferrer"&gt;Gimmie the Code&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;And that's pretty much it! I accomplished the above on this site in commit &lt;a href="https://github.com/bennypowers/bennypowers.dev/commit/307e5cf8a9306fe8a0e08af209263b32a8051887" rel="noopener noreferrer"&gt;307e5cf8&lt;/a&gt;, which you're welcome to ogle over at Microsoft's loss-leader code scraping utility.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Turn a Micro:bit into a Dreidle</title>
      <dc:creator>Benny Powers 🇮🇱🇨🇦</dc:creator>
      <pubDate>Sun, 11 Dec 2022 22:45:38 +0000</pubDate>
      <link>https://dev.to/bennypowers/turn-a-microbit-into-a-dreidle-482k</link>
      <guid>https://dev.to/bennypowers/turn-a-microbit-into-a-dreidle-482k</guid>
      <description>&lt;p&gt;Let's use &lt;a href="https://makecode.microbit.org" rel="noopener noreferrer"&gt;makecode&lt;/a&gt; to turn a &lt;a href="https://microbit.org" rel="noopener noreferrer"&gt;micro:bit&lt;/a&gt; into a dreidle!&lt;/p&gt;

&lt;p&gt;Micro:bit is a tiny opensource device designed and sold by the BBC. It's made to teach computing and electical engineering concepts to students of all ages and it's a treat to hack around with.&lt;/p&gt;

&lt;p&gt;A dreidle is a four-sided top marked with the Hebrew letters נ, ג, ה, and פ. You use it to trick Seleucid Greek Gestapo into thinking you're gambling when you're really clandestinely learning Torah. &lt;/p&gt;

&lt;p&gt;Clearly, these two things were made to be one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sevivon, Sov Sov Sov
&lt;/h2&gt;

&lt;p&gt;Let's start a new project and flip to the JavaScript editor (which is actually a typescript editor but who's counting?), since posting code blocks is easier than taking screen shots 😄.&lt;/p&gt;

&lt;p&gt;The plan is to use the RNG to simulate a drey (spin) of the dreidle, picking a number from 0-3, then depending on which number we get, we'll display a different letter on the screen.&lt;/p&gt;

&lt;p&gt;Let's start by generating a random number whenever we shake the micro:bit, and storing it in a variable called &lt;code&gt;pan&lt;/code&gt;, which means "face" in Hebrew, as in the face the dreidle landed facing up.&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;drey&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;pan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onGesture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Gesture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Shake&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;drey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That having been accomplished, let's then display a given glyph. Since we're using the JavaScript editor we're less encumbered, &lt;del&gt;so we can use a number of JavaScript constructs to pick and show our glyph&lt;/del&gt; LOL nope, we still have to pass string literals to &lt;code&gt;showLeds&lt;/code&gt;, so our JS code will still be very congruent with blocks code. If we were using the blocks editor, we'd need to use the &lt;code&gt;if / then / else&lt;/code&gt; block and drag in and duplicate "Show LEDs" blocks, which we'll also more-or-less be doing here, but at least we'll get to use &lt;code&gt;switch&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;drey&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;pan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;basic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;showLeds&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="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;basic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;showLeds&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="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;basic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;showLeds&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="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;basic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;showLeds&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="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;Honestly, if someone sent you a PR like that... I'm telling you those Greeks won't know what hit 'em. 🤔 I wonder if we could hack around makecode's requirement to use a template literal by passing a tagged template literal?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzpadah86uxta2vx1enpf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzpadah86uxta2vx1enpf.png" alt="Only image literals (string literals) supported here; TaggedTemplateExpression" width="629" height="235"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hmm, guess not. 🤷.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Sounds
&lt;/h2&gt;

&lt;p&gt;Fine, the dreidle spins, but where's the &lt;em&gt;pathos&lt;/em&gt;? Let's play a crushing dirge when we lose a throw, and the twinkling melody of gold coins when we take the pot:&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;case&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playSoundEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sad&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SoundExpressionPlayMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;InBackground&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 typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playSoundEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;twinkle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SoundExpressionPlayMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;InBackground&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now comes the fun part. When we tap the micro:bit's logo, let's play a little ditty to remind ourselves (and any adults who happen to be in earshot) precisely which holiday is approaching, in case it wasn't already clear to them.&lt;/p&gt;

&lt;p&gt;I haven't figured out how to define your own melodies using the keyboard inputs, so let's just play the notes one by one:&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sevivon&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playTone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;D&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playTone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;D&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playTone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;E&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playTone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;F&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playTone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;F&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playTone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;E&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playTone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playTone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playTone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;G&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playTone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;F&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playTone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;F&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playTone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;E&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onLogoEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TouchButtonEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Pressed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sevivon&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way, if any of Antiochus' Nazi thugs try to interrupt our holy game of craps, we'll just tap the logo again and again until they're driven away by sheer &lt;em&gt;bloopety&lt;/em&gt; &lt;em&gt;bleepety&lt;/em&gt; annoyance. Brilliance!&lt;/p&gt;

&lt;p&gt;
  Grab the final code here 👇
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sevivon&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playTone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;D&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playTone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;D&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playTone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;E&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playTone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;F&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playTone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;F&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playTone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;E&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playTone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playTone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playTone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;G&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playTone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;F&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playTone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;F&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playTone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Note&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;E&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;480&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="nf"&gt;drey&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playSoundEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sad&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SoundExpressionPlayMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;InBackground&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;basic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;showLeds&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="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nx"&gt;music&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playSoundEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;twinkle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SoundExpressionPlayMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;InBackground&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;basic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;showLeds&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="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;basic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;showLeds&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="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;basic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;showLeds&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onGesture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Gesture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Shake&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;drey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onLogoEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TouchButtonEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Pressed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sevivon&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;/p&gt;

&lt;p&gt;Take it for a spin (🥁) in the simulator:&lt;/p&gt;

&lt;p&gt;Or take a look at a &lt;a href="https://makecode.microbit.org/_iEzhbLePKbD3" rel="noopener noreferrer"&gt;blocks version for younger kids&lt;/a&gt;.&lt;/p&gt;

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

</description>
      <category>gratitude</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Form-Associated Custom Elements</title>
      <dc:creator>Benny Powers 🇮🇱🇨🇦</dc:creator>
      <pubDate>Tue, 15 Nov 2022 09:49:39 +0000</pubDate>
      <link>https://dev.to/bennypowers/form-associated-custom-elements-35f2</link>
      <guid>https://dev.to/bennypowers/form-associated-custom-elements-35f2</guid>
      <description>&lt;p&gt;Form-Associated Custom Elements are a new web standard by which to build custom interactive form controls like buttons, inputs, checkboxes, etc. They present a path forward for design-systems and other custom element authors to more deeply integrate with the web platform. In this post, we'll build a simple FACE to get a feel for the APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Does this Help?
&lt;/h2&gt;

&lt;p&gt;FACE adds crucial &lt;strong&gt;accessibility&lt;/strong&gt; and &lt;strong&gt;interactivity&lt;/strong&gt; features to web components, closing gaps between web components, framework components, and native browser controls. Before FACE, web component authors had to apply one of a number of workarounds each with their own trade-offs.&lt;/p&gt;

&lt;p&gt;Teams &lt;em&gt;developing&lt;/em&gt; FACEs can now implement accessible custom controls with simpler HTML APIs while retaining the benefits of Shadow DOM.&lt;/p&gt;

&lt;p&gt;But before we get to the code, some history:&lt;/p&gt;

&lt;p&gt;skip the history bit&lt;/p&gt;

&lt;h2&gt;
  
  
  How we Got Here
&lt;/h2&gt;

&lt;p&gt;The web components v1 standard originally defined two kinds of custom elements. The most popular kind is called and &lt;em&gt;&lt;a href="https://html.spec.whatwg.org/multipage/custom-elements.html#autonomous-custom-element" rel="noopener noreferrer"&gt;autonomous custom element&lt;/a&gt;&lt;/em&gt;, and&lt;br&gt;
it's what most people think about when they think of web components. The other kind is called a &lt;em&gt;&lt;a href="https://html.spec.whatwg.org/multipage/custom-elements.html#customized-built-in-element" rel="noopener noreferrer"&gt;customized built-in element&lt;/a&gt;&lt;/em&gt;, and they 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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;XButton&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HTMLButtonElement&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;is&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-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;}&lt;/span&gt;
&lt;span class="nx"&gt;customElements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;XButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;is&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;XButton&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;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;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You use CBIEs 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;button&lt;/span&gt; &lt;span class="na"&gt;is=&lt;/span&gt;&lt;span class="s"&gt;"x-button"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;I'm an XButton&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;Notice the big differences here: &lt;code&gt;XButton&lt;/code&gt; the customized built-in extends &lt;code&gt;HTMLButtonElement&lt;/code&gt;, not &lt;code&gt;HTMLElement&lt;/code&gt;, and when you register it, you have to pass both the custom element name &lt;code&gt;x-button&lt;/code&gt; as well as the &lt;code&gt;localName&lt;/code&gt; of the &lt;code&gt;button&lt;/code&gt; element it extends. When using it in HTML, the &lt;code&gt;localName&lt;/code&gt; of the element is &lt;code&gt;button&lt;/code&gt; and the &lt;code&gt;is&lt;/code&gt; attribute determines which subclass of &lt;code&gt;HTMLButtonElement&lt;/code&gt; to upgrade with.&lt;/p&gt;

&lt;p&gt;The chief advantage of customized built-ins was that they came with all the original features of their base elements, well, built-in. So a custom-element author wouldn't need to implement a bunch of stuff to make their custom textfield go, rather they could just extend the existing &lt;code&gt;HTMLInputElement&lt;/code&gt; class and get all the necessary and expected functionality (especially crucial accessibility features) for free. Typical OOP stuff. So if customized built-ins are so great, how come this post isn't about them and how come we rarely see them? &lt;/p&gt;

&lt;p&gt;Unfortunately, although customized built-ins remain a part of the spec, &lt;strong&gt;you should not build them&lt;/strong&gt;. The reason for this is discouraging: despite the spec's ratification, Apple's WebKit team &lt;a href="https://b.webkit.org/show_bug.cgi?id=182671" rel="noopener noreferrer"&gt;stated&lt;/a&gt; that they would decline to implement customized built-ins.&lt;/p&gt;

&lt;p&gt;Since WebKit enjoys an &lt;a href="https://open-web-advocacy.org/" rel="noopener noreferrer"&gt;artificial monopoly&lt;/a&gt; on iOS devices, the WebKit team's decision has an outsized effect on the industry. Think "US Electoral College", but for web browsers. Their decision not to implement makes customized built-ins a non-starter. Some prominent web developers (most notably Andrea Giammarchi) have advocated permanently adopting a polyfill, but the broader web components community has generally acquiesced to WebKit's decision.&lt;/p&gt;

&lt;p&gt;Which is how FACE came to be, it's the alternative to CBIEs that the WebKit team championed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Workarounds
&lt;/h2&gt;

&lt;p&gt;Before FACE, page authors using custom elements had two options to submit forms with data from their web components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The "decorator pattern" - slotting native controls into autonomous custom elements&lt;/li&gt;
&lt;li&gt;Using JavaScript to manually submit form data&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each of these had their pros and cons.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Decorator Pattern
&lt;/h3&gt;

&lt;p&gt;The most versatile workaround for autonomous custom controls involves slotting native controls into the custom element.&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;x-checkbox&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;"checkbox"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/x-checkbox&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The advantages to this approach include &lt;code&gt;&amp;lt;noscript&amp;gt;&lt;/code&gt; support and hassle-free form participation. The disadvantages include HTML noise and awkward styling due to the current limitations of &lt;code&gt;::slotted()&lt;/code&gt;. This is compounded by the requirement to &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; elements, leading to stricter HTML requirements, copying nodes into the shadow root, producing hidden light DOM nodes, or other workarounds-for-the-workaround.&lt;/p&gt;

&lt;h3&gt;
  
  
  Manually Submitting Forms
&lt;/h3&gt;

&lt;p&gt;Developers working on SPAs might opt instead to put their native inputs in the shadow DOM and use JavaScript to submit the form data to a JSON API. Here's a simple example of how that might work:&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="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&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="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&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;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;somehowCollectFormValuesFromCustomControls&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="kd"&gt;const&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="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;fetch&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&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;Given the right abstractions this approach could be quite productive for developers, but ties the controls to JavaScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a FACE
&lt;/h2&gt;

&lt;p&gt;Form-Associated Custom Elements solves one of the problems that &lt;code&gt;is&lt;/code&gt; and customized built-in elements would have solved, namely, allowing your web component to participate in native web forms.&lt;/p&gt;

&lt;p&gt;We create a FACE by setting the static &lt;code&gt;formAssociated&lt;/code&gt; boolean flag and registering the custom element.&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;class&lt;/span&gt; &lt;span class="nc"&gt;XCheckbox&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HTMLElement&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;formAssociated&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="nx"&gt;customElements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-checkbox&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;XCheckbox&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A Free Lunch
&lt;/h3&gt;

&lt;p&gt;So what does this give us? Well, right off the bat, that one static class boolean adds a number of form-related behaviours to our otherwise plain element. The &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;form&lt;/code&gt;, and &lt;code&gt;disabled&lt;/code&gt; attributes now work the same as native &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; elements, and the presence of the &lt;code&gt;readonly&lt;/code&gt; attribute will prevent the browser from trying to validate your field, although you're still responsible to make the control &lt;em&gt;actually&lt;/em&gt; non-editable. Naming your FACE and specifying a form (by child composition or via &lt;code&gt;form&lt;/code&gt; attribute) adds it to the form's &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement" rel="noopener noreferrer"&gt;&lt;code&gt;HTMLFormControlsCollection&lt;/code&gt;&lt;/a&gt;, as well, if the element or it's containing &lt;code&gt;&amp;lt;formset&amp;gt;&lt;/code&gt; has the &lt;code&gt;disabled&lt;/code&gt; attribute, it will gain the CSS state &lt;code&gt;:disabled&lt;/code&gt;.&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&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;fieldset&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"xcheck"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Check?&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;x-checkbox&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"xcheck"&lt;/span&gt;
                &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"checkit"&lt;/span&gt;
                &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"checkit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/x-checkbox&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/fieldset&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;In the above snippet, our custom checkbox is disabled on account of its containing fieldset, and the form submits with its value on &lt;code&gt;checkit&lt;/code&gt;. Removing &lt;code&gt;disabled&lt;/code&gt; from the fieldset also unsets it from the element, without the element author needing to apply any extra code.&lt;/p&gt;

&lt;p&gt;We also get some new lifecycle callbacks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;formAssociatedCallback(form: HTMLFormElement)&lt;/code&gt; runs when our element is associated with a &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt;, either by being it's child or by setting the element's &lt;code&gt;form&lt;/code&gt; attribute to the id of the form.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;formDisabledCallback(state: boolean)&lt;/code&gt; runs when the element's &lt;code&gt;disabled&lt;/code&gt; state changes, either because it or it's containing fieldset's &lt;code&gt;disabled&lt;/code&gt; attribute changed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;formResetCallback()&lt;/code&gt; runs when the element's associated form has it's &lt;code&gt;reset()&lt;/code&gt; method called. You can use this e.g. to reset to a default value.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;formStateRestoreCallback(reason: 'autocomplete'|'restore')&lt;/code&gt; runs when the browser autofills the form. It takes a single argument of type &lt;code&gt;'autocomplete'|'restore'&lt;/code&gt;, depending on whether the browser called it because of an autocomplete or a navigation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of that comes for free, even before implementing any actual custom control behaviour. So let's add in the actual checkbox stuff now, just like we would have done before the new standards.&lt;/p&gt;

&lt;h3&gt;
  
  
  Customizing the UI
&lt;/h3&gt;

&lt;p&gt;Let's start by writing an accessor pair to link our element's &lt;code&gt;checked&lt;/code&gt; property to the corresponding HTML attribute:&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;get&lt;/span&gt; &lt;span class="nf"&gt;checked&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checked&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="kd"&gt;set&lt;/span&gt; &lt;span class="nf"&gt;checked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&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="nf"&gt;toggleAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checked&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;x&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;Built-in checkboxes set their value DOM property to either the &lt;code&gt;value&lt;/code&gt; attribute's value or the string &lt;code&gt;on&lt;/code&gt;, so let's do that too:&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;get&lt;/span&gt; &lt;span class="nf"&gt;value&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="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;'&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;on&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="kd"&gt;set&lt;/span&gt; &lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&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="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v&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;We'll add &lt;code&gt;checked&lt;/code&gt; and &lt;code&gt;value&lt;/code&gt; to our &lt;code&gt;observedAttributes&lt;/code&gt; list, then call our &lt;code&gt;connectedCallback&lt;/code&gt; (providing a highly &lt;em&gt;aesthetic&lt;/em&gt; UX), in &lt;code&gt;attributeChangedCallback&lt;/code&gt;.&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="nf"&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="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&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;checked&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;✅&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;❌&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="nf"&gt;attributeChangedCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checked&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;checked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;value&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;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;break&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="nf"&gt;connectedCallback&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;And last we'll add some keyboard and pointer interaction&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="nf"&gt;constructor&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&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="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;onClick&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="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keydown&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="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;onKeydown&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="nf"&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="s1"&gt;open&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="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;XCheckbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cloneNode&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="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;onClick&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="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;onKeydown&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="k"&gt;switch &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="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&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="nf"&gt;preventDefault&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="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;toggle&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="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;toggle&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;checked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&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;checked&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 that our checkbox looks and feels like a checkbox, the last thing to do is to hook into the browser's HTML form lifecycle with another new standard, &lt;a href="https://html.spec.whatwg.org/multipage/custom-elements.html#the-elementinternals-interface" rel="noopener noreferrer"&gt;&lt;code&gt;ElementInternals&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Form Interactions
&lt;/h3&gt;

&lt;p&gt;Along with FACE, &lt;code&gt;ElementInternals&lt;/code&gt; gives custom element authors new capabilities. Specifically, element internals are a standard place to implement things like form control validation and accessibility. &lt;code&gt;ElementInternals&lt;/code&gt; is designed as a catch-all bag of properties and methods for working with custom elements. We can expect expansions to its capabilities in the future, but for now it contains three parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A reference to the element's shadow root, if it exists&lt;/li&gt;
&lt;li&gt;Form-related properties&lt;/li&gt;
&lt;li&gt;Accessibility-related properties&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;HTMLElement&lt;/code&gt; get a new standard method called &lt;code&gt;attachInternals()&lt;/code&gt; which returns an &lt;code&gt;ElementInternals&lt;/code&gt; object. This method may only be called on autonomous custom elements and will throw if called on built-ins, customized or otherwise. You hook your control's custom implementation into it's associated form with &lt;code&gt;ElementInternals&lt;/code&gt;' form properties.&lt;/p&gt;

&lt;p&gt;Let's create our &lt;code&gt;ElementInternals&lt;/code&gt; object by calling&lt;code&gt;attachInternals&lt;/code&gt;, and store it on a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields" rel="noopener noreferrer"&gt;private class field&lt;/a&gt;.&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="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;internals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attachInternals&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 &lt;code&gt;connectedCallback&lt;/code&gt;, we'll apply the checkbox' value to it's &lt;br&gt;
&lt;code&gt;FormData&lt;/code&gt; entry:&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;internals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setFormValue&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;checked&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;value&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;setFormValue&lt;/code&gt; call is part of the &lt;code&gt;ElementInternals&lt;/code&gt; secret sauce. Calling it with a non-nullish value adds our control's value to the form's &lt;code&gt;FormData&lt;/code&gt; object, whereas calling it with &lt;code&gt;null&lt;/code&gt; removes the value.&lt;/p&gt;

&lt;p&gt;We can also implement form validation in our custom controls with the following internals properties and methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;willValidate(): boolean&lt;/code&gt; checks whether the element will be validated when the form submits&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;setValidity()&lt;/code&gt; sets the element's form validity state&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;checkValidity()&lt;/code&gt; and &lt;code&gt;reportValidity()&lt;/code&gt; work just like their native counterparts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Custom validations are a big topic so let's save their more in-depth explanation for another day.&lt;/p&gt;

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

&lt;p&gt;The other major feature of &lt;code&gt;ElementInternals&lt;/code&gt; are it's a11y-related properties &lt;code&gt;role&lt;/code&gt; and &lt;code&gt;aria*&lt;/code&gt;. Part of the &lt;a href="https://wicg.github.io/aom/explainer.html" rel="noopener noreferrer"&gt;AOM&lt;/a&gt;, we can now set &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/" rel="noopener noreferrer"&gt;ARIA&lt;/a&gt; properties imperatively without needing to set &lt;code&gt;aria-&lt;/code&gt; attributes. These are critical capabilities which previously only had partial workarounds.&lt;/p&gt;

&lt;p&gt;Let's start by setting the &lt;code&gt;role&lt;/code&gt; so that screen readers announce our element as a checkbutton. Note that as of this writing Firefox (107) has not yet implemented role reflection, so we'll do some feature detection&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;role&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;ElementInternals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&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="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;internals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checkbox&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;role&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;checkbox&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;We'll update our &lt;code&gt;connectedCallback&lt;/code&gt; to render to the a11y tree as well as the DOM. Like role reflection, we'll apply a workaround for Firefox:&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ariaChecked&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;ElementInternals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&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="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;internals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ariaChecked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;String&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;checked&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aria-checked&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&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;checked&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Putting it all together, our custom checkbox:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;implements it's own bespoke UI&lt;/li&gt;
&lt;li&gt;participates in HTML forms like a native input&lt;/li&gt;
&lt;li&gt;is accessible to users of assistive technologies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://bennypowers.dev/posts/form-associated-custom-elements#simple-checkbox-example" rel="noopener noreferrer"&gt;Check out the demo on the original post&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Browser Support and Polyfills
&lt;/h2&gt;

&lt;p&gt;As of initial publication, Chromium (Google Chrome, Microsoft Edge, Brave, Arc) supports the full range of APIs described here. Firefox supports &lt;code&gt;attachInternals&lt;/code&gt; and &lt;code&gt;formAssociated&lt;/code&gt; but does not support ARIA and role reflection. WebKit does not support any of the new APIs, but the commits to add support have been merged, so the next Safari Technology Preview is likely to add support.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Engine&lt;/th&gt;
&lt;th&gt;FACE&lt;/th&gt;
&lt;th&gt;&lt;code&gt;ElementInternals&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;AOM Reflection&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Chromium&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Firefox&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WebKit&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The inimitable Caleb D. Williams has kindly published an &lt;a href="https://github.com/calebdwilliams/element-internals-polyfill" rel="noopener noreferrer"&gt;ElementInternals polyfill&lt;/a&gt; which weighs in at &lt;a href="https://unpkg.com/element-internals-polyfill" rel="noopener noreferrer"&gt;~6kb over-the-wire&lt;/a&gt;. Since the spec involves hooking into browser stuff which is otherwise unavailable to developers, the polyfill is not 100% spec compliant. For example, ARIA reflection is implemented by adding &lt;code&gt;aria-&lt;/code&gt; attributes to the host element, where the spec states that they should not be added. The polyfill also adds a workaround for the &lt;a href="https://wicg.github.io/custom-state-pseudo-class/#dom-elementinternals-states" rel="noopener noreferrer"&gt;custom state&lt;/a&gt; part of the spec, which was not covered here.&lt;/p&gt;

&lt;p&gt;Thoughts? Corrections? Comments? Let me know on &lt;a href="https://social.bennypowers.dev/@i" rel="noopener noreferrer"&gt;mastodon&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webcomponents</category>
      <category>html</category>
      <category>javascript</category>
      <category>a11y</category>
    </item>
    <item>
      <title>Let's Write a Redux Controller for Web Components</title>
      <dc:creator>Benny Powers 🇮🇱🇨🇦</dc:creator>
      <pubDate>Thu, 06 Jan 2022 22:55:21 +0000</pubDate>
      <link>https://dev.to/bennypowers/lets-write-a-redux-controller-for-web-components-4edl</link>
      <guid>https://dev.to/bennypowers/lets-write-a-redux-controller-for-web-components-4edl</guid>
      <description>&lt;p&gt;Elliott Marquez challenged me to write a redux controller on the &lt;a href="https://lit.dev/slack-invite/" rel="noopener noreferrer"&gt;Lit &amp;amp; Friends slack&lt;/a&gt;. &lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__137797"&gt;
    &lt;a href="/elliott" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F137797%2F51b85172-df02-46c5-86af-499fcc94aee9.jpeg" alt="elliott image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/elliott"&gt;Elliott&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/elliott"&gt;I work on Developer experiences at Chrome and closely with the web platform&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;So let's get cracking!&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 0: The Setup
&lt;/h2&gt;

&lt;p&gt;First step let's make a new project and import some dependencies to help us develop.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;controllers
&lt;span class="nb"&gt;cd &lt;/span&gt;controllers
git init
npm init &lt;span class="nt"&gt;--yes&lt;/span&gt;
npm i &lt;span class="nt"&gt;-D&lt;/span&gt; typescript lit
&lt;span class="nb"&gt;touch &lt;/span&gt;reducer.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok next we'll set up the controller class in reducer.ts&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="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ReactiveController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ReactiveControllerHost&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;lit&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;class&lt;/span&gt; &lt;span class="nc"&gt;ReducerController&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;ReactiveController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ReactiveControllerHost&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="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addController&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="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;hostUpdate&lt;/span&gt;&lt;span class="p"&gt;()?:&lt;/span&gt; &lt;span class="k"&gt;void&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;That &lt;code&gt;hostUpdate&lt;/code&gt; signature is just to keep typescript from complaining. 🤷.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Reducers
&lt;/h2&gt;

&lt;p&gt;Our controller essentially bolts some statefullness onto a function which takes some state &lt;code&gt;T&lt;/code&gt; and some action &lt;code&gt;A&lt;/code&gt; and returns some other or the same state &lt;code&gt;T&lt;/code&gt;. So let's formalize that:&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Reducer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&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="nx"&gt;A&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The controller should take that reducer, along with some initial state, and pin them to the class instance.&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;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReducerController&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;ReactiveController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ReactiveControllerHost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;reducer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Reducer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;A&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;public&lt;/span&gt; &lt;span class="nx"&gt;initialState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addController&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;initialState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;hostUpdate&lt;/span&gt;&lt;span class="p"&gt;?():&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Actions
&lt;/h2&gt;

&lt;p&gt;Believe it or not we're pretty much done. The last piece we need is to implement a &lt;code&gt;dispatch&lt;/code&gt; method which takes an action &lt;code&gt;A&lt;/code&gt; and updates the host.&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="nf"&gt;dispatch&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="nx"&gt;A&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&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;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reducer&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;state&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requestUpdate&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;And, as Chef John would say, &lt;em&gt;that's it&lt;/em&gt;!&lt;/p&gt;

&lt;p&gt;If we want to use our controller, we just create it on a compatible host (like LitElement) and we're off to the races:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/bennyp/embed/abLjmqW?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://lit.dev/playground/#project=W3sibmFtZSI6ImNvdW50ZXItZWxlbWVudC50cyIsImNvbnRlbnQiOiJpbXBvcnQge2h0bWwsIGNzcywgTGl0RWxlbWVudH0gZnJvbSAnbGl0JztcbmltcG9ydCB7Y3VzdG9tRWxlbWVudCwgcHJvcGVydHl9IGZyb20gJ2xpdC9kZWNvcmF0b3JzLmpzJztcbmltcG9ydCB7UmVkdWNlckNvbnRyb2xsZXJ9IGZyb20gJy4vcmVkdWNlci5qcyc7XG5cbkBjdXN0b21FbGVtZW50KCdjb3VudGVyLWVsZW1lbnQnKVxuZXhwb3J0IGNsYXNzIENvdW50ZXJFbGVtZW50IGV4dGVuZHMgTGl0RWxlbWVudCB7XG4gIHByaXZhdGUgY291bnQgPSBuZXcgUmVkdWNlckNvbnRyb2xsZXIodGhpcywgZnVuY3Rpb24gcmVkdWNlcihzdGF0ZSwgYWN0aW9uOiBDb3VudEFjdGlvbikge1xuICAgIHN3aXRjaCAoYWN0aW9uLnR5cGUpIHtcbiAgICAgIGNhc2UgJ3Jlc2V0JzpcbiAgICAgICAgcmV0dXJuIDA7XG4gICAgICBjYXNlICdpbmNyZW1lbnQnOlxuICAgICAgICByZXR1cm4gc3RhdGUgKyAxO1xuICAgICAgY2FzZSAnZGVjcmVtZW50JzpcbiAgICAgICAgcmV0dXJuIHN0YXRlIC0gMTtcbiAgICB9XG4gIH0sIDApO1xuXG4gIHJlbmRlcigpIHtcbiAgICByZXR1cm4gaHRtbGBcbiAgICAgIDxidXR0b24gQGNsaWNrPSR7KCkgPT4gdGhpcy5jb3VudC5kaXNwYXRjaCh7IHR5cGU6ICdpbmNyZW1lbnQnIH0pfT4rPC9idXR0b24-XG4gICAgICA8b3V0cHV0PiR7dGhpcy5jb3VudC5zdGF0ZX08L291dHB1dD5cbiAgICAgIDxidXR0b24gQGNsaWNrPSR7KCkgPT4gdGhpcy5jb3VudC5kaXNwYXRjaCh7IHR5cGU6ICdkZWNyZW1lbnQnIH0pfT4rPC9idXR0b24-XG4gICAgYDtcbiAgfVxufVxuIn0seyJuYW1lIjoiaW5kZXguaHRtbCIsImNvbnRlbnQiOiI8IURPQ1RZUEUgaHRtbD5cbjxoZWFkPlxuICA8c2NyaXB0IHR5cGU9XCJtb2R1bGVcIiBzcmM9XCIuL3NpbXBsZS1ncmVldGluZy5qc1wiPjwvc2NyaXB0PlxuPC9oZWFkPlxuPGJvZHk-XG4gIDxjb3VudGVyLWVsZW1lbnQ-PC9jb3VudGVyLWVsZW1lbnQ-XG48L2JvZHk-XG4ifSx7Im5hbWUiOiJwYWNrYWdlLmpzb24iLCJjb250ZW50Ijoie1xuICBcImRlcGVuZGVuY2llc1wiOiB7XG4gICAgXCJsaXRcIjogXCJeMi4wLjBcIixcbiAgICBcIkBsaXQvcmVhY3RpdmUtZWxlbWVudFwiOiBcIl4xLjAuMFwiLFxuICAgIFwibGl0LWVsZW1lbnRcIjogXCJeMy4wLjBcIixcbiAgICBcImxpdC1odG1sXCI6IFwiXjIuMC4wXCJcbiAgfVxufSIsImhpZGRlbiI6dHJ1ZX0seyJuYW1lIjoicmVkdWNlci50cyIsImNvbnRlbnQiOiJpbXBvcnQgdHlwZSB7IFJlYWN0aXZlQ29udHJvbGxlciwgUmVhY3RpdmVDb250cm9sbGVySG9zdCB9IGZyb20gJ2xpdCc7XG5cbmV4cG9ydCB0eXBlIFJlZHVjZXI8VCwgQT4gPSAoc3RhdGU6IFQsIGFjdGlvbjogQSkgPT4gVDtcblxuZXhwb3J0IGNsYXNzIFJlZHVjZXJDb250cm9sbGVyPFQgPSB1bmtub3duLCBBID0gdW5rbm93bj4gaW1wbGVtZW50cyBSZWFjdGl2ZUNvbnRyb2xsZXIge1xuICBwdWJsaWMgc3RhdGU6IFQ7XG5cbiAgY29uc3RydWN0b3IoXG4gICAgcHJpdmF0ZSBob3N0OiBSZWFjdGl2ZUNvbnRyb2xsZXJIb3N0LFxuICAgIHB1YmxpYyByZWR1Y2VyOiBSZWR1Y2VyPFQsIEE-LFxuICAgIHB1YmxpYyBpbml0aWFsU3RhdGU6IFQsXG4gICkge1xuICAgIHRoaXMuaG9zdC5hZGRDb250cm9sbGVyKHRoaXMpO1xuICAgIHRoaXMuc3RhdGUgPSBpbml0aWFsU3RhdGU7XG4gIH1cblxuICBkaXNwYXRjaChhY3Rpb246IEEpOiB2b2lkIHtcbiAgICB0aGlzLnN0YXRlID0gdGhpcy5yZWR1Y2VyKHRoaXMuc3RhdGUsIGFjdGlvbik7XG4gICAgdGhpcy5ob3N0LnJlcXVlc3RVcGRhdGUoKTtcbiAgfVxuXG4gIGhvc3RVcGRhdGU_KCk6dm9pZFxufVxuIn1d" rel="noopener noreferrer"&gt;Live Demo&lt;/a&gt;&lt;/p&gt;

</description>
      <category>redux</category>
      <category>webcomponents</category>
      <category>lit</category>
    </item>
    <item>
      <title>Clearing Rows with CSS Grid</title>
      <dc:creator>Benny Powers 🇮🇱🇨🇦</dc:creator>
      <pubDate>Thu, 06 Jan 2022 19:14:26 +0000</pubDate>
      <link>https://dev.to/bennypowers/clearing-rows-with-css-grid-49c1</link>
      <guid>https://dev.to/bennypowers/clearing-rows-with-css-grid-49c1</guid>
      <description>&lt;p&gt;Today I completed a fun little challenge using CSS Grid.&lt;/p&gt;

&lt;p&gt;The goal was to update a layout that relied on container elements and flexbox.&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;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"cards"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;pfe-card&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"a-1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/pfe-card&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;pfe-card&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"a-2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/pfe-card&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;pfe-card&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"a-3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/pfe-card&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;pfe-card&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"a-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/pfe-card&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"cards"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;pfe-card&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"b-1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/pfe-card&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;pfe-card&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"b-2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/pfe-card&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;pfe-card&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"b-3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/pfe-card&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;pfe-card&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"b-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/pfe-card&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;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 css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.cards&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;flex-flow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="n"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.cards&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin-right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;min-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;25%&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;32px&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;My first step was to remove the container elements and 'lift up' the flex properties into the grid container&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;pfe-card&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"a-1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/pfe-card&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;pfe-card&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"a-2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/pfe-card&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;pfe-card&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"a-3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/pfe-card&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;pfe-card&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"a-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/pfe-card&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;pfe-card&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"b-1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/pfe-card&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;pfe-card&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"b-2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/pfe-card&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;pfe-card&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"b-3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/pfe-card&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;pfe-card&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"b-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/pfe-card&amp;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 css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:host&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;auto-fill&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;minmax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="m"&gt;200px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;25%&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;32px&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
  &lt;span class="py"&gt;grid-auto-rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;max-content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15px&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;This did most of the job on its own, but there was one issue: in the original layout, card &lt;code&gt;b-1&lt;/code&gt; appeared in a new 'row' below the first set of cards. How could I emulate this 'row-clearing' behaviour? Using &lt;code&gt;grid-column&lt;/code&gt;, I specified the position of the first card in the second set:&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="nf"&gt;#b-1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;grid-column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="m"&gt;2&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, that one specific card 'resets' it's whole row, replicating the original behaviour, but with fewer non-semantic elements, and less CSS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxwu3aivsmjqa0g898mbj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxwu3aivsmjqa0g898mbj.png" alt="Browser Screenshot showing rows of cards. The second row ends with two empty cells, and the third row begins with the desired element" width="800" height="697"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;nice&lt;/em&gt;&lt;/p&gt;

</description>
      <category>html</category>
      <category>css</category>
    </item>
    <item>
      <title>🕎 8 Days of Web Components Tips</title>
      <dc:creator>Benny Powers 🇮🇱🇨🇦</dc:creator>
      <pubDate>Sun, 05 Dec 2021 18:10:05 +0000</pubDate>
      <link>https://dev.to/bennypowers/8-days-of-web-components-tips-39o5</link>
      <guid>https://dev.to/bennypowers/8-days-of-web-components-tips-39o5</guid>
      <description>&lt;p&gt;In honour of &lt;a href="https://www.chabad.org/holidays/chanukah/article_cdo/aid/102911/jewish/What-Is-Hanukkah.htm" rel="noopener noreferrer"&gt;Hannukah&lt;/a&gt; this year, I undertook to write 8 web components tips, one for each night of the festival. Tonight is the 8th and final night of the festival. The mystics said that this night combines and contains aspects of each of the seven previous nights, so I'd like to share a compilation of those tips with the dev community.&lt;/p&gt;

&lt;p&gt;Wishing you and yours a fully Lit Hannukah!&lt;/p&gt;

&lt;h2&gt;
  
  
  1st night: Adding Controllers via TypeScript Decorators 🕯
&lt;/h2&gt;

&lt;p&gt;Did you know you can add reactive controllers to an element via a class or field decorator? You don't even need to assign it to an instance property!&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="cm"&gt;/**
 * Adds a given class to a ReactiveElement when it upgrades
 */&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;classy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;classString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;ClassDecorator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;klass&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="nf"&gt;isReactiveElementClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`@classy may only decorate ReactiveElements.`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addInitializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Define and add an ad-hoc controller!&lt;/span&gt;
      &lt;span class="c1"&gt;// Look, mah! No instance property!&lt;/span&gt;
      &lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addController&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="nf"&gt;hostConnected&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;instance&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="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;classString&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;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="nd"&gt;customElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pirsumei-nissa&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="nd"&gt;classy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;al-hanissim&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;class&lt;/span&gt; &lt;span class="nc"&gt;PirsumeiNissa&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;LitElement&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  2nd night: Adding Controllers Inside Other Controllers 🕯🕯
&lt;/h2&gt;

&lt;p&gt;Like a delicious &lt;a href="https://www.wikiwand.com/en/Sufganiyah" rel="noopener noreferrer"&gt;&lt;em&gt;sufganya&lt;/em&gt;&lt;/a&gt; (traditional holiday donut) with many fillings, a Lit component can have multiple reactive controllers, and controllers can even add other controllers&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;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MutationController&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;E&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;ReactiveElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;ReactiveController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;mo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MutationObserver&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;onMutation&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;E&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;Options&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;E&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Add another controller&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;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Logger&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;host&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addController&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="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;onMutation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MutationRecord&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;logger&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Mutation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;records&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;options&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;onMutation&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;hostConnected&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;mo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observe&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;host&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;options&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;init&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;attributes&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="nx"&gt;childList&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="nf"&gt;hostDisconnected&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;mo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disconnect&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;h2&gt;
  
  
  3rd night: Web Component Context API 🕯🕯🕯
&lt;/h2&gt;

&lt;p&gt;Did you know web components can have context? The protocol is based on composed events. Define providers &amp;amp; consumers, &amp;amp; share data across the DOM.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/webcomponents-cg/community-protocols/blob/main/proposals/context.md" rel="noopener noreferrer"&gt;https://github.com/webcomponents-cg/community-protocols/blob/main/proposals/context.md&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  4th night: Using SASS, PostCSS, etc. 🕯🕯🕯🕯
&lt;/h2&gt;

&lt;p&gt;Building #webcomponents with #SASS? (You probably don't need it but if you can't resist…) you can develop using a buildless workflow with &lt;a href="//modern-web.dev/"&gt;Web Dev Server&lt;/a&gt; and &lt;a href="http://npm.im/esbuild-plugin-lit-css" rel="noopener noreferrer"&gt;esbuild-plugin-lit-css&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Want to use #PostCSS instead for sweet-sweet future CSS syntax? &lt;a href="https://github.com/bennypowers/lit-css/tree/main/packages/esbuild-plugin-lit-css#usage-with-sass-less-postcss-etc" rel="noopener noreferrer"&gt;No problem&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  5th night: Stacking Slots 🕯🕯🕯🕯🕯
&lt;/h2&gt;

&lt;p&gt;Who doesn't like a piping hot stack of latkes?&lt;/p&gt;

&lt;p&gt;Stack slots to toggle component states. Adding content into the outer slot automatically 'disables' the inner slot&lt;/p&gt;

&lt;p&gt;State management in HTML! 🤯&lt;/p&gt;

&lt;p&gt;Check out &lt;a class="mentioned-user" href="https://dev.to/westbrook"&gt;@westbrook&lt;/a&gt;'s blog on the topic:&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/westbrook" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F82953%2F3c83b5f5-fd0e-4cdc-a681-d84b6953699b.jpeg" alt="westbrook"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/westbrook/who-doesnt-love-some-s-3de0" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Who doesn't love some `&amp;lt;slot /&amp;gt;`s?&lt;/h2&gt;
      &lt;h3&gt;Westbrook Johnson ・ Nov 24 '21&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#html&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#shadowdom&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#customelements&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#a11y&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  6th night: Better TypeScript Imports 🕯🕯🕯🕯🕯🕯
&lt;/h2&gt;

&lt;p&gt;In #TypeScript 4.5, if you set &lt;code&gt;preserveValueImports&lt;/code&gt;, you can import the class definitions of your element dependencies without worrying that TS will elide the side-effecting value.&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="s1"&gt;lit&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;customElement&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="s1"&gt;lit/decorators.js&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="nd"&gt;customElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lit-candle&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;class&lt;/span&gt; &lt;span class="nc"&gt;LitCandle&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&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;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="nx"&gt;lit&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="nf"&gt;render&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lit&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🕯&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; &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;/code&gt;&lt;/pre&gt;

&lt;/div&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="nx"&gt;html&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;lit&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;customElement&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="nx"&gt;query&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;lit/decorators.js&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;LitCandle&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;./lit-candle.js&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="nd"&gt;customElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lit-menorah&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;class&lt;/span&gt; &lt;span class="nc"&gt;LitMenorah&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&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;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="nx"&gt;night&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Although the value of `LitCandle` isn't used, only the type&lt;/span&gt;
  &lt;span class="c1"&gt;// with `preserveValueImports`, TS 4.5 won't strip the import&lt;/span&gt;
  &lt;span class="c1"&gt;// So you can be sure that `&amp;lt;lit-candle&amp;gt;` will upgrade&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lit-candle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;candles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NodeListOf&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;LitCandle&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;render&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="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
      &amp;lt;lit-candle ?lit="&lt;/span&gt;&lt;span class="p"&gt;${(&lt;/span&gt;&lt;span class="nx"&gt;i&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;&amp;lt;=&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;night&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;&amp;lt;/lit-candle&amp;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;&lt;a href="https://lit.dev/playground/#project=W3sibmFtZSI6ImxpdC1tZW5vcmFoLnRzIiwiY29udGVudCI6ImltcG9ydCB7IExpdEVsZW1lbnQsIGh0bWwgfSBmcm9tICdsaXQnO1xuaW1wb3J0IHsgY3VzdG9tRWxlbWVudCwgcHJvcGVydHksIHF1ZXJ5IH0gZnJvbSAnbGl0L2RlY29yYXRvcnMuanMnO1xuaW1wb3J0IHsgTGl0Q2FuZGxlIH0gZnJvbSAnLi9saXQtY2FuZGxlLmpzJztcbmltcG9ydCAnLi9saXQtY2FuZGxlLmpzJztcblxuQGN1c3RvbUVsZW1lbnQoJ2xpdC1tZW5vcmFoJylcbmV4cG9ydCBjbGFzcyBMaXRNZW5vcmFoIGV4dGVuZHMgTGl0RWxlbWVudCB7XG4gIEBwcm9wZXJ0eSh7IHR5cGU6IE51bWJlciB9KSBuaWdodCA9IDY7XG4gIFxuICAvLyBBbHRob3VnaCB0aGUgdmFsdWUgb2YgYExpdENhbmRsZWAgaXNuJ3QgdXNlZCwgb25seSB0aGUgdHlwZVxuICAvLyB3aXRoIGBwcmVzZXJ2ZVZhbHVlSW1wb3J0c2AsIFRTIDQuNSB3b24ndCBzdHJpcCB0aGUgaW1wb3J0XG4gIC8vIFNvIHlvdSBjYW4gYmUgc3VyZSB0aGF0IGA8bGl0LWNhbmRsZT5gIHdpbGwgdXBncmFkZVxuICBAcXVlcnkoJ2xpdC1jYW5kbGUnKSBjYW5kbGVzOiBOb2RlTGlzdE9mPExpdENhbmRsZT47XG4gIFxuICByZW5kZXIoKSB7XG4gICAgcmV0dXJuIEFycmF5LmZyb20oeyBsZW5ndGg6IDggfSwgKF8sIGkpID0-IGh0bWxgXG4gICAgICA8bGl0LWNhbmRsZSA_bGl0PVwiJHsoaSArIDEpIDw9IHRoaXMubmlnaHR9XCI-PC9saXQtY2FuZGxlPlxuICAgIGApO1xuICB9XG59In0seyJuYW1lIjoiaW5kZXguaHRtbCIsImNvbnRlbnQiOiI8IURPQ1RZUEUgaHRtbD5cbjxoZWFkPlxuICA8c2NyaXB0IHR5cGU9XCJtb2R1bGVcIiBzcmM9XCIuL2xpdC1tZW5vcmFoLmpzXCI-PC9zY3JpcHQ-XG48L2hlYWQ-XG48Ym9keT5cbiAgPGxpdC1tZW5vcmFoIG5pZ2h0PVwiNlwiPjwvbGl0LW1lbm9yYWg-XG48L2JvZHk-XG4ifSx7Im5hbWUiOiJwYWNrYWdlLmpzb24iLCJjb250ZW50Ijoie1xuICBcImRlcGVuZGVuY2llc1wiOiB7XG4gICAgXCJsaXRcIjogXCJeMi4wLjBcIixcbiAgICBcIkBsaXQvcmVhY3RpdmUtZWxlbWVudFwiOiBcIl4xLjAuMFwiLFxuICAgIFwibGl0LWVsZW1lbnRcIjogXCJeMy4wLjBcIixcbiAgICBcImxpdC1odG1sXCI6IFwiXjIuMC4wXCJcbiAgfVxufSIsImhpZGRlbiI6dHJ1ZX0seyJuYW1lIjoibGl0LWNhbmRsZS50cyIsImNvbnRlbnQiOiJpbXBvcnQgeyBMaXRFbGVtZW50IH0gZnJvbSAnbGl0JztcbmltcG9ydCB7IGN1c3RvbUVsZW1lbnQsIHByb3BlcnR5IH0gZnJvbSAnbGl0L2RlY29yYXRvcnMuanMnO1xuXG5AY3VzdG9tRWxlbWVudCgnbGl0LWNhbmRsZScpXG5leHBvcnQgY2xhc3MgTGl0Q2FuZGxlIGV4dGVuZHMgTGl0RWxlbWVudCB7XG4gIEBwcm9wZXJ0eSh7IHR5cGU6IEJvb2xlYW4gfSkgbGl0ID0gZmFsc2U7XG4gIFxuICByZW5kZXIoKSB7XG4gICAgcmV0dXJuIHRoaXMubGl0ID8gJ_Cfla8nIDogJyAnO1xuICB9XG59In1d" rel="noopener noreferrer"&gt;live demo&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  7th night: GraphQL Web Components 🕯🕯🕯🕯🕯🕯🕯
&lt;/h2&gt;

&lt;p&gt;Looking to add #GraphQL to your frontend? Give &lt;a href="https://apolloelements.dev" rel="noopener noreferrer"&gt;Apollo Elements&lt;/a&gt; a try. Use Apollo reactive controllers with lit+others, or try a 'functional' library like &lt;a href="https://apolloelements.dev/api/libraries/atomic" rel="noopener noreferrer"&gt;atomic&lt;/a&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ApolloQueryController&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;@apollo-elements/core&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;LitElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;html&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;lit&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;customElement&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;lit/decorators.js&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;HelloQuery&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;./Hello.query.graphql&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="nd"&gt;customElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello-query&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;class&lt;/span&gt; &lt;span class="nc"&gt;HelloQueryElement&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;LitElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ApolloQueryController&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;HelloQuery&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;render&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="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
      &amp;lt;article class=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;classMap&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;skeleton&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;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt; &lt;span class="p"&gt;})}&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;
        &amp;lt;p id="error" ?hidden=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="o"&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;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;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;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/p&amp;gt;
        &amp;lt;p&amp;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;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;greeting&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello&lt;/span&gt;&lt;span class="dl"&gt;'&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Friend&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
        &amp;lt;/p&amp;gt;
      &amp;lt;/article&amp;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;h2&gt;
  
  
  8th night: Component Interop 🕯🕯🕯🕯🕯🕯🕯🕯
&lt;/h2&gt;

&lt;p&gt;You don't need to use only #lit components in your #lit app&lt;/p&gt;

&lt;p&gt;Mix old-school #Polymer 3 components with #vue js web components. Put #stencil js Microsoft's #FAST UI on the same page&lt;/p&gt;

&lt;p&gt;It's your party!&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="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&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.61/dist/themes/light.css"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&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/@ionic/core/css/ionic.bundle.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.61/dist/shoelace.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&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/@ionic/core/dist/ionic/ionic.esm.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&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://unpkg.com/@microsoft/fast-components"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&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://unpkg.com/@patternfly/pfe-datetime@1.12.2/dist/pfe-datetime.js?module"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&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://unpkg.com/@material/mwc-button?module"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;sl-card&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;pfe-datetime&lt;/span&gt; &lt;span class="na"&gt;slot=&lt;/span&gt;&lt;span class="s"&gt;"header"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"relative"&lt;/span&gt; &lt;span class="na"&gt;datetime=&lt;/span&gt;&lt;span class="s"&gt;"Mon Jan 2 15:04:05 EST 2010"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/pfe-datetime&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ion-img&lt;/span&gt; &lt;span class="na"&gt;slot=&lt;/span&gt;&lt;span class="s"&gt;"image"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://placekitten.com/300/200"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/ion-img&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;fast-progress-ring&lt;/span&gt; &lt;span class="na"&gt;min=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt; &lt;span class="na"&gt;max=&lt;/span&gt;&lt;span class="s"&gt;"100"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"75"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/fast-progress-ring&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;mwc-button&lt;/span&gt; &lt;span class="na"&gt;slot=&lt;/span&gt;&lt;span class="s"&gt;"footer"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;More Info&lt;span class="nt"&gt;&amp;lt;/mwc-button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/sl-card&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>webcomponents</category>
      <category>html</category>
      <category>javascript</category>
      <category>hannukah</category>
    </item>
    <item>
      <title>The Next Evolution of GraphQL Front Ends</title>
      <dc:creator>Benny Powers 🇮🇱🇨🇦</dc:creator>
      <pubDate>Mon, 02 Aug 2021 14:32:20 +0000</pubDate>
      <link>https://dev.to/bennypowers/the-next-evolution-of-graphql-front-ends-egf</link>
      <guid>https://dev.to/bennypowers/the-next-evolution-of-graphql-front-ends-egf</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally posted on the &lt;a href="https://apolloelements.dev/blog/next-evolution/" rel="noopener noreferrer"&gt;Apollo Elements blog&lt;/a&gt;. Read there to enjoy interactive demos.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Apollo Elements has come a long way since its first release as &lt;code&gt;lit-apollo&lt;/code&gt; in 2017. What started as a way to build GraphQL-querying LitElements has blossomed into a &lt;a href="https://apolloelements.dev/api/libraries/" rel="noopener noreferrer"&gt;multi-library&lt;/a&gt;, multi-paradigm project with &lt;a href="https://apolloelements.dev/api/" rel="noopener noreferrer"&gt;extensive docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Today we're releasing the next version of Apollo Elements' packages, including a major change: introducing GraphQL Controllers, and GraphQL HTML Elements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reactive GraphQL Controllers
&lt;/h2&gt;

&lt;p&gt;The latest version of &lt;a href="https://lit.dev" rel="noopener noreferrer"&gt;Lit&lt;/a&gt; introduced a concept called "reactive controllers". They're a way to pack up reusable functionality in JavaScript classes that you can share between elements. If you've use JavaScript class mixins before (&lt;em&gt;not&lt;/em&gt; the same as React mixins), they you're familiar with sharing code between elements. Controllers go one-better by being sharable and composable without requiring you to apply a mixin to the host element, as long as it implements the &lt;a href="https://lit.dev/docs/composition/controllers/#controller-host-api" rel="noopener noreferrer"&gt;&lt;code&gt;ReactiveControllerHost&lt;/code&gt;&lt;/a&gt; interface.&lt;/p&gt;

&lt;p&gt;You can even have multiple copies of the same controller active on a given host. In the words of the Lit team, controllers represent a "has a _" relationship to the host element, where mixins represent an "is a _" relationship.&lt;/p&gt;

&lt;p&gt;For Apollo Elements, it means now you can add many GraphQL operations to one component, like multiple queries or a query and a mutation. Here's an interactive example of the latter:&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="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;TextField&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;@material/mwc-textfield&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;ApolloQueryController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ApolloMutationController&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;@apollo-elements/core&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;LitElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;html&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;lit&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;customElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;query&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;lit/decorators.js&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;UsersQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;AddUserMutation&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;./graphql.documents.js&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;style&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;./Users.css.js&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="nd"&gt;customElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users-view&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UsersView&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;LitElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mwc-textfield&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;nameField&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ApolloQueryController&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;UsersQuery&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;addUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ApolloMutationController&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;AddUserMutation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;awaitRefetchQueries&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="na"&gt;refetchQueries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UsersQuery&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;onSubmit&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;addUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mutate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&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;nameField&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&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;render&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;users&lt;/span&gt; &lt;span class="o"&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;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&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;loading&lt;/span&gt; &lt;span class="o"&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;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt; &lt;span class="o"&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;addUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
      &amp;lt;form&amp;gt;
        &amp;lt;h2&amp;gt;Add a New User&amp;lt;/h2&amp;gt;
        &amp;lt;mwc-textfield label="Name" ?disabled="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;&amp;lt;/mwc-textfield&amp;gt;
        &amp;lt;mwc-linear-progress indeterminate ?closed="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;&amp;lt;/mwc-linear-progress&amp;gt;
        &amp;lt;mwc-button label="Submit" ?disabled="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" @click="&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;onSubmit&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;&amp;lt;/mwc-button&amp;gt;
      &amp;lt;/form&amp;gt;
      &amp;lt;h2&amp;gt;All Users&amp;lt;/h2&amp;gt;
      &amp;lt;mwc-list&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
        &amp;lt;mwc-list-item noninteractive graphic="avatar"&amp;gt;
          &amp;lt;img slot="graphic" ?hidden="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;picture&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" .src="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;picture&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" role="presentation"/&amp;gt;
          &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
        &amp;lt;/mwc-list-item&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;
      &amp;lt;/mwc-list&amp;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;&lt;a href="https://apolloelements.dev/blog/next-evolution/#multiple-controllers" rel="noopener noreferrer"&gt;&lt;em&gt;View a Live Demo of this snippet&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Controllers are great for lots of reasons. One reason we've found while developing and testing Apollo Elements is that unlike the class-based API of e.g. &lt;code&gt;@apollo-elements/lit-apollo&lt;/code&gt; or &lt;code&gt;@apollo-elements/mixins&lt;/code&gt;, when using controllers there's no need to pass in type parameters to the host class. By passing a &lt;a href="https://github.com/dotansimha/graphql-typed-document-node" rel="noopener noreferrer"&gt;TypedDocumentNode&lt;/a&gt; object as the argument to the controller, you'll get that typechecking and autocomplete you know and love in your class template and methods, without awkward &lt;code&gt;&amp;lt;DataType, VarsType&amp;gt;&lt;/code&gt; class generics.&lt;/p&gt;

&lt;p&gt;If you're working on an existing app that uses Apollo Elements' base classes, not to worry, you can still &lt;code&gt;import { ApolloQuery } from '@apollo-elements/lit-apollo'&lt;/code&gt;, We worked hard to keep the breaking changes to a minimum. Those base classes now use the controllers at their heart, so go ahead: mix-and-match query components with controller-host components in your app, it won't bloat your bundles.&lt;/p&gt;

&lt;p&gt;We hope you have as much fun using Apollo Elements controllers as we've had writing them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dynamic GraphQL Templates in HTML
&lt;/h2&gt;

&lt;p&gt;The previous major version of &lt;code&gt;@apollo-elements/components&lt;/code&gt; included &lt;code&gt;&amp;lt;apollo-client&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;apollo-mutation&amp;gt;&lt;/code&gt;. Those are still here and they're better than ever, but now they're part of a set with &lt;code&gt;&amp;lt;apollo-query&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;apollo-subscription&amp;gt;&lt;/code&gt; as well.&lt;/p&gt;

&lt;p&gt;With these new elements, and their older sibling &lt;code&gt;&amp;lt;apollo-mutation&amp;gt;&lt;/code&gt;, you can write entire GraphQL apps in nothing but HTML. You read that right, declarative, data-driven GraphQL apps in HTML. You still have access to the Apollo Client API, so feel free to sprinkle in a little JS here and there for added spice.&lt;/p&gt;

&lt;p&gt;This is all made possible by a pair of libraries from the Lit team's Justin Fagnani called &lt;a href="https://github.com/justinfagnani/stampino/" rel="noopener noreferrer"&gt;Stampino&lt;/a&gt; and &lt;a href="https://github.com/justinfagnani/jexpr/" rel="noopener noreferrer"&gt;jexpr&lt;/a&gt;. Together, they let you define dynamic parts in HTML &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; elements, filling them with JavaScript expressions based on your GraphQL data.&lt;/p&gt;

&lt;p&gt;Here's the demo app from above, but written in 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="nt"&gt;&amp;lt;apollo-client&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;apollo-query&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;"application/graphql"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"Users.query.graphql"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Add a New User&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;apollo-mutation&lt;/span&gt; &lt;span class="na"&gt;refetch-queries=&lt;/span&gt;&lt;span class="s"&gt;"Users"&lt;/span&gt; &lt;span class="na"&gt;await-refetch-queries&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;"application/graphql"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"AddUser.mutation.graphql"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;mwc-textfield&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Name"&lt;/span&gt;
                       &lt;span class="na"&gt;slot=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;
                       &lt;span class="na"&gt;data-variable=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;
                       &lt;span class="na"&gt;.disabled=&lt;/span&gt;&lt;span class="s"&gt;"{{ loading }}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/mwc-textfield&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;mwc-button&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Submit"&lt;/span&gt;
                    &lt;span class="na"&gt;trigger&lt;/span&gt;
                    &lt;span class="na"&gt;slot=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;
                    &lt;span class="na"&gt;.disabled=&lt;/span&gt;&lt;span class="s"&gt;"{{ loading }}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/mwc-button&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;form&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;slot&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/slot&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;mwc-linear-progress&lt;/span&gt; &lt;span class="na"&gt;indeterminate&lt;/span&gt; &lt;span class="na"&gt;.closed=&lt;/span&gt;&lt;span class="s"&gt;"{{ !loading }}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/mwc-linear-progress&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;slot&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/slot&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/apollo-mutation&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;All Users&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;mwc-list&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"repeat"&lt;/span&gt; &lt;span class="na"&gt;repeat=&lt;/span&gt;&lt;span class="s"&gt;"{{ data.users ?? [] }}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;mwc-list-item&lt;/span&gt; &lt;span class="na"&gt;noninteractive&lt;/span&gt; &lt;span class="na"&gt;graphic=&lt;/span&gt;&lt;span class="s"&gt;"avatar"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;.src=&lt;/span&gt;&lt;span class="s"&gt;"{{ item.picture }}"&lt;/span&gt; &lt;span class="na"&gt;slot=&lt;/span&gt;&lt;span class="s"&gt;"graphic"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
            {{ item.name }}
          &lt;span class="nt"&gt;&amp;lt;/mwc-list-item&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/mwc-list&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/apollo-query&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/apollo-client&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;"components.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;&lt;a href="https://apolloelements.dev/blog/next-evolution/#html-components" rel="noopener noreferrer"&gt;&lt;em&gt;View a Live Demo of this snippet&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There's a tonne of potential here and we're very keen to see what you come up with using these new components. Bear in mind that the stampino API isn't stable yet: there may be changes coming down the pipe in the future, but we'll do our best to keep those changes private.&lt;/p&gt;

&lt;h2&gt;
  
  
  More Flexible HTML Mutations
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;&amp;lt;apollo-mutation&amp;gt;&lt;/code&gt; component lets you declare GraphQL mutations in HTML. Now, the latest version gives you more options to layout your pages. Add a stampino template to render the mutation result into the light or shadow DOM. Use the &lt;code&gt;variable-for="&amp;lt;id&amp;gt;"&lt;/code&gt; and &lt;code&gt;trigger-for="&amp;lt;id&amp;gt;"&lt;/code&gt; attributes on sibling elements to better integrate with 3rd-party components, and specify the event which triggers the mutation by specifying a value to the &lt;code&gt;trigger&lt;/code&gt; attribute.&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://unpkg.com/@shoelace-style/shoelace@2.0.0-beta.47/dist/themes/base.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;src=&lt;/span&gt;&lt;span class="s"&gt;"https://unpkg.com/@shoelace-style/shoelace@2.0.0-beta.47/dist/shoelace.js?module"&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;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;sl-button&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"toggle"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Add a User&lt;span class="nt"&gt;&amp;lt;/sl-button&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;sl-dialog&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Add User"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;sl-input&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"What is your name?"&lt;/span&gt;
            &lt;span class="na"&gt;variable-for=&lt;/span&gt;&lt;span class="s"&gt;"add-user-mutation"&lt;/span&gt;
            &lt;span class="na"&gt;data-variable=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/sl-input&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;sl-button&lt;/span&gt; &lt;span class="na"&gt;slot=&lt;/span&gt;&lt;span class="s"&gt;"footer"&lt;/span&gt;
             &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"primary"&lt;/span&gt;
             &lt;span class="na"&gt;trigger-for=&lt;/span&gt;&lt;span class="s"&gt;"add-user-mutation"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Add&lt;span class="nt"&gt;&amp;lt;/sl-button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/sl-dialog&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;apollo-mutation&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"add-user-mutation"&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;"application/graphql"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"AddUser.mutation.graphql"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;sl-alert&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"primary"&lt;/span&gt; &lt;span class="na"&gt;duration=&lt;/span&gt;&lt;span class="s"&gt;"3000"&lt;/span&gt; &lt;span class="na"&gt;closable&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="na"&gt;open=&lt;/span&gt;&lt;span class="s"&gt;"{{ data }}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;sl-icon&lt;/span&gt; &lt;span class="na"&gt;slot=&lt;/span&gt;&lt;span class="s"&gt;"icon"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"info-circle"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/sl-icon&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Added {{ data.addUser.name }}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/sl-alert&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/apollo-mutation&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;"imports.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&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="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toggle&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;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;toggle&lt;/span&gt;&lt;span class="dl"&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;dialog&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;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sl-dialog&lt;/span&gt;&lt;span class="dl"&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;mutation&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;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;add-user-mutation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;dialog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="nx"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mutation-completed&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;dialog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hide&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Demonstrating how to use &lt;code&gt;&amp;lt;apollo-mutation&amp;gt;&lt;/code&gt; with &lt;a href="https://shoelace.style" rel="noopener noreferrer"&gt;Shoelace&lt;/a&gt; web components. &lt;a href="https://apolloelements.dev/blog/next-evolution/#shoelace" rel="noopener noreferrer"&gt;&lt;em&gt;View a Live Demo of this snippet&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Atomico support
&lt;/h2&gt;

&lt;p&gt;On the heels of the controllers release, we're happy to add a new package to the roster. Apollo Elements now has first-class support for &lt;a href="https://atomico.gitbook.io" rel="noopener noreferrer"&gt;Atomico&lt;/a&gt;, a new hooks-based web components library with JSX or template-string templating.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&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;useQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&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;@apollo-elements/atomico&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;LaunchesQuery&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;./Launches.query.graphql.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Launches&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;LaunchesQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&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;launches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;launchesPast&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="k"&gt;return &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;host&lt;/span&gt; &lt;span class="na"&gt;shadowDom&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;link&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"launches.css"&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;ol&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;launches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;li&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;article&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;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mission_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&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;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;links&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mission_patch_small&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Badge"&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"presentation"&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;article&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;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ol&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;host&lt;/span&gt;&lt;span class="p"&gt;&amp;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;customElements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;spacex-launches&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Launches&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  FAST Behaviors
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://fast.design" rel="noopener noreferrer"&gt;FAST&lt;/a&gt; is an innovative web component library and design system from Microsoft. Apollo Elements added support for FAST in 2020, in the form of &lt;code&gt;Apollo*&lt;/code&gt; base classes. The latest release transitions to FAST &lt;a href="https://www.fast.design/docs/api/fast-element.controller.addbehaviors" rel="noopener noreferrer"&gt;Behaviors&lt;/a&gt;, which are analogous to Lit &lt;code&gt;ReactiveControllers&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="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;customElement&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserProfile&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;FASTElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ApolloQueryBehavior&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;MyProfileQuery&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;updateProfile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ApolloMutationBehavior&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;UpdateProfileMutation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeQuery&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MyProfileQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;updateProfile&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The FAST team were instrumental in getting this feature over the line, so many thanks to them.&lt;/p&gt;

&lt;p&gt;If you're already using &lt;code&gt;@apollo-elements/fast&lt;/code&gt;, we recommend migrating your code to behaviors as soon as you're able, but you can continue to use the element base classes, just change your import paths to &lt;code&gt;/bases&lt;/code&gt;. These may be removed in the &lt;em&gt;next&lt;/em&gt; major release, though.&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="gd"&gt;-  import { ApolloQuery } from '@apollo-elements/fast/apollo-query';
&lt;/span&gt;&lt;span class="gi"&gt;+  import { ApolloQuery } from '@apollo-elements/fast/bases/apollo-query';
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  New and Improved Docs
&lt;/h2&gt;

&lt;p&gt;It wouldn't be an Apollo Elements release without some docs goodies. This time, in addition to new and updated docs and guides for components and controllers, we've replaced our webcomponents.dev iframes with &lt;code&gt;&amp;lt;playground-ide&amp;gt;&lt;/code&gt; elements. All the "Edit Live" demos on this site, including the ones in this blog post, are running locally in your browser via a service worker. Talk about serverless, &lt;em&gt;amirite&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;The docs also got a major upgrade care of Pascal Schilp's untiring work in the &lt;a href="https://www.w3.org/community/webcomponents/" rel="noopener noreferrer"&gt;Webcomponents Community Group&lt;/a&gt; to get the custom elements manifest v1 published. This latest iteration of the API docs generates package manifests directly from source code, and converts them to API docs via &lt;a href="https://rocket.modern-web.dev" rel="noopener noreferrer"&gt;Rocket&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  SSR
&lt;/h2&gt;

&lt;p&gt;As part of the release, we updated our demo apps &lt;a href="https://leeway.apolloelements.dev" rel="noopener noreferrer"&gt;leeway&lt;/a&gt; and &lt;a href="https://launchctl.apolloelements.dev" rel="noopener noreferrer"&gt;LaunchCTL&lt;/a&gt;. In the case of leeway, we took the opportunity to implement extensive SSR with the help of a new browser standard called &lt;a href="https://web.dev/declarative-shadow-dom/" rel="noopener noreferrer"&gt;Declarative Shadow DOM&lt;/a&gt;. It's early days for this technique but it's already looking very promising. You can try it out in any chromium browser (Chrome, Brave, Edge, Opera) by disabling JavaScript and visiting &lt;a href="https://leeway.apolloelements.dev" rel="noopener noreferrer"&gt;https://leeway.apolloelements.dev&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Behind the Scenes
&lt;/h2&gt;

&lt;p&gt;Bringing this release into the light involved more than just refactoring and updating the &lt;code&gt;apollo-elements/apollo-elements&lt;/code&gt; repo. It represents work across many projects, including PRs to&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/justinfagnani/stampino/pulls?q=is%3Apr+is%3Aclosed+merged%3A%3C2021-06-01+merged%3A%3E2021-01+" rel="noopener noreferrer"&gt;Stampino&lt;/a&gt; and &lt;a href="https://github.com/justinfagnani/jexpr/pulls?q=is%3Apr+is%3Aclosed+merged%3A%3E2021-01" rel="noopener noreferrer"&gt;jexpr&lt;/a&gt;, to iron out bugs, decrease bundle size, and add features&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/hybridsjs/hybrids/pull/167" rel="noopener noreferrer"&gt;Hybrids&lt;/a&gt;, to add support for reactive controllers&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/atomicojs/hooks/pull/7" rel="noopener noreferrer"&gt;Atomico&lt;/a&gt; and &lt;a href="https://github.com/matthewp/haunted/pull/239" rel="noopener noreferrer"&gt;Haunted&lt;/a&gt;, to add the &lt;code&gt;useController&lt;/code&gt; hook which underlies &lt;code&gt;useQuery&lt;/code&gt; and co.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additionally, here in apollo-elements, we added the &lt;a href="https://dev.to/api/libraries/mixins/controller-host-mixin/"&gt;&lt;code&gt;ControllerHostMixin&lt;/code&gt;&lt;/a&gt; as a way to maintain the previous element-per-graphql-document API without breaking backwards (too much). You can use this generic mixin to add controller support to any web component.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixes and Enhancements
&lt;/h2&gt;

&lt;p&gt;The last release included support for the web components hooks library &lt;a href="https://github.com/matthewp/haunted" rel="noopener noreferrer"&gt;haunted&lt;/a&gt;, but that support hid a dirty little secret within. Any time you called a hook inside a Haunted function component, apollo elements would sneakily mix the GraphQL interface onto the custom element's prototype. It was a good hack as long as you only call one hook per component, but would break down as soon as you compose multiple operations.&lt;/p&gt;

&lt;p&gt;With controllers at the core, and the &lt;a href="https://github.com/matthewp/haunted#usecontroller" rel="noopener noreferrer"&gt;&lt;code&gt;useController&lt;/code&gt;&lt;/a&gt; hook, you can use as many Apollo hooks as you want in your elements without clobbering each other or polluting the element interface.&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;useQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;component&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;@apollo-elements/haunted&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;client&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;./client.js&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;FruitsQuery&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;./Fruits.query.graphql.js&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;VeggiesQuery&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;./Veggies.query.graphql.js&lt;/span&gt;&lt;span class="dl"&gt;'&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="nf"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;healthy-snack&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;component&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;HealthySnack&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fruits&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;FruitsQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;veggies&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;VeggiesQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;client&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;snack&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="nx"&gt;fruits&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;fruits&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="nx"&gt;veggies&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;veggies&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
    &amp;lt;link rel="stylesheet" href="healthy-snack.css"/&amp;gt;
    &amp;lt;ul&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;snack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;li&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/li&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/ul&amp;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;Demonstrating how to use multiple GraphQL hooks in a &lt;a href="https://hauntedhooks.netlify.app/" rel="noopener noreferrer"&gt;haunted&lt;/a&gt; component. &lt;a href="https://apolloelements.dev/blog/next-evolution/#haunted-multiple-hooks" rel="noopener noreferrer"&gt;&lt;em&gt;View a Live Demo of this snippet&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The same is true of the &lt;a href="https://hybrids.js.org" rel="noopener noreferrer"&gt;hybrids&lt;/a&gt; support, it now uses the controllers underneath the hood, letting you mix multiple operations in a single hybrid.&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;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;html&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="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@apollo-elements/hybrids&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;client&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;./client.js&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;FruitsQuery&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;./Fruits.query.graphql.js&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;VeggiesQuery&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;./Veggies.query.graphql.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;healthy-snack&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="na"&gt;fruits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;FruitsQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;veggies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;VeggiesQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;host&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;snack&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="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fruits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;fruits&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="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;veggies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;veggies&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
      &amp;lt;link rel="stylesheet" href="healthy-snack.css"/&amp;gt;
      &amp;lt;ul&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;snack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;li&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/li&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/ul&amp;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;Demonstrating how to use multiple GraphQL hooks in an &lt;a href="https://atomico.gitbook.io/" rel="noopener noreferrer"&gt;atomico&lt;/a&gt; component. &lt;a href="https://apolloelements.dev/blog/next-evolution/#haunted-multiple-hooks" rel="noopener noreferrer"&gt;&lt;em&gt;View a Live Demo of this snippet&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it Out
&lt;/h2&gt;

&lt;p&gt;Apollo Elements next is available in prerelease on &lt;a href="https://npm.im/@apollo-elements/core" rel="noopener noreferrer"&gt;npm&lt;/a&gt;. We hope you enjoy using it and look forward to seeing what you come up with.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Are you using Apollo Elements at work? Consider &lt;a href="https://opencollective.com/apollo-elements" rel="noopener noreferrer"&gt;sponsoring the project via Open Collective&lt;/a&gt; to receive perks like priority support.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>graphql</category>
      <category>webcomponents</category>
      <category>html</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Narrowing the Type of Class Accessors</title>
      <dc:creator>Benny Powers 🇮🇱🇨🇦</dc:creator>
      <pubDate>Tue, 27 Jul 2021 15:03:09 +0000</pubDate>
      <link>https://dev.to/bennypowers/narrowing-the-type-of-class-accessors-bi8</link>
      <guid>https://dev.to/bennypowers/narrowing-the-type-of-class-accessors-bi8</guid>
      <description>&lt;p&gt;Javascript &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get" rel="noopener noreferrer"&gt;class accessors&lt;/a&gt; let you define a function to get or set a particular value on an object.&lt;/p&gt;

&lt;p&gt;Why would you want to do that? One popular reason is to run "side effects" in a setter. So for example say you want to make an HTTP request every time a user sets the &lt;code&gt;options&lt;/code&gt; property on your object:&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Fetcher&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;RequestInit&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="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;RequestInit&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;options&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;RequestInit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="nf"&gt;options&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RequestInit&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="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;val&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="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;fetch&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;fetch&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;url&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;options&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;By defining the &lt;code&gt;options&lt;/code&gt; property with an accessor pair, we can run our side effects (in this case, calling &lt;code&gt;fetch&lt;/code&gt;) in the setter.&lt;/p&gt;

&lt;p&gt;But now let's say we want to write a &lt;code&gt;Poster&lt;/code&gt; class that only makes POST requests. It makes sense to extend Fetcher so that we don't duplicate our work. We want to &lt;em&gt;narrow the type&lt;/em&gt; of options, however, to only allow options where the &lt;code&gt;method&lt;/code&gt; is &lt;code&gt;POST&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PostInit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;RequestInit&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Poster&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Fetcher&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PostInit&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;What we're essentially saying with the &lt;code&gt;declare&lt;/code&gt; keyword is "This class is exactly the same as it's parent, except that TypeScript should limit the options property to only accept PostInit objects". This &lt;em&gt;should&lt;/em&gt; work, but...&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;'options' is defined as an accessor in class 'Fetcher', but is overridden here in 'Poster' as an instance property.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Oops! TypeScript complains. This &lt;a href="https://github.com/microsoft/TypeScript/pull/37894" rel="noopener noreferrer"&gt;change was introduced in 2020&lt;/a&gt; in order to make TypeScript behave more like plain JavaScript. Indeed, TC39 decreed that &lt;a href="https://github.com/tc39/proposal-class-fields#public-fields-created-with-objectdefineproperty" rel="noopener noreferrer"&gt;class fields should have "define semantics"&lt;/a&gt; instead of "set semantics", which means that if we merely stripped these files of typescript syntax, our Poster class would break.&lt;/p&gt;

&lt;p&gt;The debate over TC39's decision to use define semantics continues (even though the decision was made already), so we won't get deeper into it here, but when using typescript and "ambient declarations" like we did above with the &lt;code&gt;declare&lt;/code&gt; keyword, we don't have the problem of our class field overriding the accessors, particularly if we don't have &lt;code&gt;useDefineForClassFields&lt;/code&gt; turned on (which is probably a good choice anyways).&lt;/p&gt;

&lt;p&gt;In fact, one of the three proposals which aim to fix this behaviour in typescript calls to allow fields to override accessors if they use the &lt;code&gt;declare&lt;/code&gt; keyword.&lt;/p&gt;

&lt;p&gt;But until that proposal, or a similar fix, is accepted, what can we do?&lt;/p&gt;

&lt;h2&gt;
  
  
  A Workaround Using Decorators
&lt;/h2&gt;

&lt;p&gt;TypeScript field decorators have access to the class prototype. We can use one then to define our property with get and set functions. Since we're using a class private field for storage, and those are only available within a class body, let's define our decorator as a static method:&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Fetcher&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/** @internal */&lt;/span&gt;
  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;o&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;proto&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Fetcher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defineProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;proto&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;options&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="nf"&gt;get&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;;&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="nx"&gt;val&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="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;val&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="nf"&gt;fetch&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;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;RequestInit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Fetcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RequestInit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;RequestInit&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="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;fetch&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;fetch&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;url&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;options&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;If you squint, you can still see the original outline of the class, and this version does not error when we &lt;code&gt;declare&lt;/code&gt; our narrow type on the subclass.&lt;/p&gt;

&lt;p&gt;Really, this is a (kind of ugly) loophole to tide us over until TypeScript decides which proposal (if any) to adopt for this pattern. Here's hoping they don't close it before then.&lt;/p&gt;

&lt;h2&gt;
  
  
  Footnotes
&lt;/h2&gt;

&lt;p&gt;Eagle-eyed readers might object to my example: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Poster&lt;/code&gt; should implement it's own setter to only allow &lt;code&gt;POST&lt;/code&gt; at run time!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;They'd be right to do so. We could come up with other examples where the options object doesn't have runtime validation, or where that validation is done in other methods. In those cases, it would be right-handy to declare the narrower type on the subclass.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
