<?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: Rich Harris</title>
    <description>The latest articles on DEV Community by Rich Harris (@richharris).</description>
    <link>https://dev.to/richharris</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%2F183152%2F92d6b6d1-4d69-46fb-ae22-d6fd1501bef8.jpeg</url>
      <title>DEV Community: Rich Harris</title>
      <link>https://dev.to/richharris</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/richharris"/>
    <language>en</language>
    <item>
      <title>Stay alert</title>
      <dc:creator>Rich Harris</dc:creator>
      <pubDate>Tue, 10 Aug 2021 14:16:18 +0000</pubDate>
      <link>https://dev.to/richharris/stay-alert-d</link>
      <guid>https://dev.to/richharris/stay-alert-d</guid>
      <description>&lt;p&gt;A short while ago, Chrome broke the web by disabling &lt;code&gt;alert()&lt;/code&gt;, &lt;code&gt;confirm()&lt;/code&gt; and &lt;code&gt;prompt()&lt;/code&gt; dialogs from cross-origin iframes. The justification was that "the current UX is confusing, and has previously led to spoofs where sites pretend the message comes from Chrome or a different website"; removing the feature was deemed preferable to fixing the UX.&lt;/p&gt;

&lt;p&gt;But legitimate uses were affected too. Users of &lt;a href="https://codepen.io/"&gt;CodePen&lt;/a&gt;, the widely-used code-sharing site co-founded by Chris Coyier, &lt;a href="https://css-tricks.com/choice-words-about-the-upcoming-deprecation-of-javascript-dialogs"&gt;suddenly discovered&lt;/a&gt; that they were unable to use these functions in their projects, since CodePen runs your code inside a cross-origin iframe to &lt;a href="https://twitter.com/chriscoyier/status/1420033471376920578"&gt;guard against XSS attacks&lt;/a&gt;. Reports from other sites followed, and in the ensuing chaos the change was &lt;a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1065085#c72"&gt;rolled back until 2022&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Hidden in the replies to &lt;a href="https://twitter.com/chriscoyier/status/1420027533005836298"&gt;Coyier's tweet&lt;/a&gt; was a &lt;a href="https://twitter.com/domenic/status/1422647331804037120"&gt;surprising statement&lt;/a&gt; from Domenic Denicola, an engineer on the Chrome team:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It's best that such teaching sites be prepared for the eventual end state where these are removed from the web platform entirely.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Wait, what?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Reading the &lt;a href="https://groups.google.com/a/chromium.org/g/blink-dev/c/hTOXiBj3D6A/m/JtkdpDd1BAAJ"&gt;intent to remove&lt;/a&gt; thread confirms that this is indeed Chrome's stance: blocking dialogs (including &lt;code&gt;onbeforeunload&lt;/code&gt;) were a mistake, and their future removal is a &lt;em&gt;fait accompli&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;After I &lt;a href="https://twitter.com/Rich_Harris/status/1422930436850860033"&gt;tweeted&lt;/a&gt; about the situation last week, my notifications tab became a Boschian hellscape, so I'm hesitant to write this post. But there are several aspects to this story that are too important for us not to talk about. It's not just a story about unloved APIs, it's a story about power, standards design, and who owns the platform — and it makes me afraid for the future of the web.&lt;/p&gt;

&lt;h2&gt;
  
  
  Onramps
&lt;/h2&gt;

&lt;p&gt;Facebook's Dan Abramov &lt;a href="https://twitter.com/dan_abramov/status/1422601734153723908"&gt;pointed out&lt;/a&gt; that the changes nuked many programming tutorials. Google's Emily Stark &lt;a href="https://twitter.com/estark37/status/1422645947708760067"&gt;suggested&lt;/a&gt; they should use the &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; element instead. For the moment, we'll gloss over the fact that &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; is sufficiently flawed that Denicola &lt;a href="https://github.com/whatwg/html/pull/4184#issuecomment-440405059"&gt;floated&lt;/a&gt; removing it from the spec — or that &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog#javascript"&gt;MDN's suggested fallback&lt;/a&gt; for browsers that don't support it is none other than &lt;code&gt;alert&lt;/code&gt; — and instead consider what this would look like in real life.&lt;/p&gt;

&lt;p&gt;Often, when I'm teaching people web development, they begin learning JavaScript by building a simple number guessing game along these lines:&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;function&lt;/span&gt; &lt;span class="nx"&gt;game&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;number&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="nx"&gt;ceil&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="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;guess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Guess a number between 1 and 100`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;guess&lt;/span&gt; &lt;span class="o"&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;guess&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;guess&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;number&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;guess&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;guess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Too low! Guess again`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;guess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Too high! Guess again`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;guess&lt;/span&gt; &lt;span class="o"&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;guess&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`That's right! The number was &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;number&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="nx"&gt;game&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's pretty straightforward-looking stuff, but in the space of a few lines of code the students are exposed to many unfamiliar concepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data types (strings vs numbers, and converting between them)&lt;/li&gt;
&lt;li&gt;Functions, both built-in and the ones you write yourself&lt;/li&gt;
&lt;li&gt;Loops and if-else statements&lt;/li&gt;
&lt;li&gt;Operators&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's a popular lesson, and even foreshadows future discussions of algorithms (the smartest students soon intuit that they can 'win' by conducting a binary search), but it's hard — easily an hour's worth of material. Imagine now that before they could complete it they were required to learn about the DOM, event handling, and asynchronous programming. Educators gravitated towards blocking dialog APIs &lt;em&gt;for a reason&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/yoavweiss/status/1422655032273641479"&gt;Failing to understand&lt;/a&gt; why these APIs are so valuable in an educational context is inevitable if you &lt;a href="https://twitter.com/ag_dubs/status/1423323533036429315"&gt;don't consider teachers part of your constituency&lt;/a&gt; when designing standards. It's cliché (and only partly accurate) to say that the web used to have better onramps for developers, but there's truth behind the nostalgic grumbling: the web platform's learnability has long been essential to its success. We damage it at our peril.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hidden signals
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://docs.google.com/document/d/1RC-pBBvsazYfCNNUSkPqAVpSpNJ96U8trhNkfV0v9fk/edit#heading=h.mqfkui78vo5z"&gt;'primary signal'&lt;/a&gt; Chrome uses to determine whether something can safely be removed from the web platform is the number of page views impacted. A feature appearing on 0.001% of page views is considered 'small but non-trivial' usage. (Cross-origin &lt;code&gt;alert&lt;/code&gt; is at around &lt;a href="https://chromestatus.com/metrics/feature/timeline/popularity/1411"&gt;0.006%&lt;/a&gt;, significantly above this threshold; with same-origin the figure is &lt;a href="https://chromestatus.com/metrics/feature/timeline/popularity/950"&gt;50x higher&lt;/a&gt; still.)&lt;/p&gt;

&lt;p&gt;It's easy to overindex on the things you can quantify, especially if you're Google. But not all things that count as &lt;em&gt;uses&lt;/em&gt; of some feature show up in the data, when the data is predominantly public-facing production websites. Teaching is one such case. There are others.&lt;/p&gt;

&lt;p&gt;For example, I've had several experiences in which a well-placed &lt;code&gt;alert&lt;/code&gt; was the only way to test hypotheses during debugging. In an ideal world we'd all have well-stocked device labs and be able to remotely inspect our code wherever it's running, no matter how imminent the deadline. Reality isn't always so accommodating.&lt;/p&gt;

&lt;p&gt;Even when my code is working as intended — it happens sometimes — I'm likely to reach for &lt;code&gt;alert&lt;/code&gt; before adding complex error handling, if I'm building something for myself or my coworkers and I expect errors to be rare occurrences.&lt;/p&gt;

&lt;p&gt;And security researchers frequently use &lt;code&gt;alert&lt;/code&gt; to demonstrate vulnerabilities. (Yes, in future they could use something less concise and less visible like &lt;code&gt;console.log&lt;/code&gt;, but in the meantime years' worth of literature would instantly fall out of date if &lt;code&gt;alert&lt;/code&gt; vanished.)&lt;/p&gt;

&lt;p&gt;All of these are legitimate uses, but none will affect the metric that determines whether they're important enough to be supported by Chrome. Even when we &lt;em&gt;do&lt;/em&gt; focus solely on production websites, usage doesn't necessarily correlate with importance, as &lt;a href="https://twitter.com/dan_abramov/status/1422943317512364037"&gt;noted by Dan Abramov&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Breakage
&lt;/h2&gt;

&lt;p&gt;According to Emily Stark, a security expert on the Chrome team, breakage is &lt;a href="https://twitter.com/estark37/status/1422694856544059396"&gt;something that happens often&lt;/a&gt; on the web.&lt;/p&gt;

&lt;p&gt;But if that's true, it's very largely &lt;em&gt;because&lt;/em&gt; of Chrome. For a long time, 'don't break the web' was considered something of a &lt;a href="https://twitter.com/BenLesh/status/1422659445126057989"&gt;prime directive&lt;/a&gt; in standards work. Recall #smooshgate: a proposal to add a &lt;code&gt;flatten&lt;/code&gt; method to &lt;code&gt;Array.prototype&lt;/code&gt; turned out to be a breaking change because an ancient version of MooTools, still in use by a handful of sites, added its own incompatible &lt;code&gt;flatten&lt;/code&gt;. &lt;a href="https://twitter.com/Rich_Harris/status/971753488739954688"&gt;Disappointingly&lt;/a&gt;, some developers argued that breaking the web was acceptable, but TC39 took its backwards compatibility responsibilities seriously and ended up renaming &lt;code&gt;flatten&lt;/code&gt; to &lt;code&gt;flat&lt;/code&gt; instead. Google's Mathias Bynens &lt;a href="https://developers.google.com/web/updates/2018/03/smooshgate"&gt;wrote&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;As it turns out, “don’t break the Web” is the number one &lt;a href="https://www.w3.org/TR/html-design-principles/#support-existing-content"&gt;design principle&lt;/a&gt; for HTML, CSS, JavaScript, and any other standard that’s widely used on the Web.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This time around, the approach was rather more cavalier.&lt;/p&gt;

&lt;p&gt;Reasonable people can disagree about the balance of priorities when considering breaking changes, but it's good to be clear-eyed about what 'breakage' means. One of the many anecdotes I heard in the wake of the cross-origin alert changes &lt;a href="https://twitter.com/bigtimebuddy/status/1422953235111825416"&gt;stood out&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I was attempting to delete my recurring payments account from my local waste management's super old-school site. I was bit by the cross-domain confirm() in Chrome 92. I switched to Firefox to complete.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What if Firefox was no longer an option, either because a cash-strapped Mozilla had stopped developing it, or because they had implemented the &lt;a href="https://github.com/whatwg/html/pull/6297"&gt;now standardized&lt;/a&gt; spec changes? We're not talking about the Space Jam website rendering incorrectly, we're talking about people being unable to use &lt;a href="https://twitter.com/NOVALISTIC/status/1423184584686981120"&gt;essential services&lt;/a&gt; on the web. A frequent implication in the discussion last week was that website owners could simply re-engineer their apps to not use blocking dialogs, regardless of the cost of doing so. But many sites are no longer maintained, and they're no less valuable because of it.&lt;/p&gt;

&lt;p&gt;We can't normalise the attitude that collateral damage is the price of progress, even if we accept the premise — which I don't — that removing APIs like &lt;code&gt;alert&lt;/code&gt; represents progress. For all its flaws, the web is generally agreed to be a stable platform, where investments made today will stand the test of time. A world in which websites are treated as inherently transient objects, where APIs we commonly rely on today could be cast aside as unwanted baggage by tomorrow's spec wranglers, is a world in which the web has already lost.&lt;/p&gt;

&lt;h2&gt;
  
  
  What if alert is... good, actually?
&lt;/h2&gt;

&lt;p&gt;We're often reminded to use the web's built-in form elements instead of recreating checkboxes and buttons with a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; salad. Not only are they &lt;a href="https://drewdevault.com/2021/06/27/You-cant-capture-the-nuance.html"&gt;more accessible than what you'd likely build yourself&lt;/a&gt;, the visual consistency makes your app easier for users to navigate even if you consider the default appearance 'ugly'.&lt;/p&gt;

&lt;p&gt;Yet when it comes to dialogs, the ugly default is treated as a bug rather than a feature. Why? As Heydon Pickering &lt;a href="https://twitter.com/heydonworks/status/1423565599947513856"&gt;puts it&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Using alert(), prompt(), and confirm() in an MVP is the closest most devs will get to providing accessible dialogs. Chrome removing them just cuts out that step. Devs can go straight onto building their own underperforming, inaccessible dialogs&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the bad old days, the behaviour of &lt;code&gt;alert&lt;/code&gt; was somewhat obnoxious — it would focus the tab in question, and prevent you from navigating away. Thanks to &lt;a href="https://docs.google.com/document/d/1wtV5rmLhbf1OZkbg7crtCt6h1mMtig_ctTQt3BLLEIU/edit#heading=h.uivr1mgjyut2"&gt;years of hard work&lt;/a&gt;, that's no longer the case, to the extent that I'd argue &lt;code&gt;alert&lt;/code&gt; is in many cases better than whatever you'd have cobbled together yourself.&lt;/p&gt;

&lt;p&gt;There &lt;em&gt;are&lt;/em&gt; security issues with cross-origin iframes. I remain unconvinced that removal is a better solution than improving the design in a way that makes their provenance clearer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who owns the web?
&lt;/h2&gt;

&lt;p&gt;A common response to last week's kerfuffle was 'use Firefox'. But that's not a solution. Even though the change was proposed by Chromium (the &lt;a href="https://groups.google.com/a/chromium.org/g/blink-dev/c/hTOXiBj3D6A/m/JtkdpDd1BAAJ"&gt;intent to remove&lt;/a&gt; preceded any discussion with other browser vendors), Firefox ultimately supported it. That's all it takes for something to become a 'standard' — support from two vendors, and stated opposition from none.&lt;/p&gt;

&lt;p&gt;Put differently: when it comes to web standards, browsers call the shots exclusively.&lt;/p&gt;

&lt;p&gt;Whenever I've questioned the wisdom of this or that proposal, I've been told I should simply get involved in the standards discussions — they're right there on GitHub! But openness means nothing without the power to effect change, and browsers have all the power. This should strike us as odd — the W3C's &lt;a href="https://www.w3.org/TR/html-design-principles/#priority-of-constituencies"&gt;priority of constituencies&lt;/a&gt; explicitly states that the needs of users and authors (i.e. developers) should be treated as higher priority than those of implementors (i.e. browser vendors), yet the higher priority constituencies are at the mercy of the lower priority ones. (&lt;a href="https://twitter.com/yoavweiss/status/1423380609968353280"&gt;Chrome developers argue&lt;/a&gt; that they are acting in the interests of users in this case, but this &lt;a href="https://twitter.com/mikesherov/status/1423271360357351432"&gt;thread from Mike Sherov&lt;/a&gt; makes a convincing case that this is a fig leaf for the real motivation, which is technical debt.)&lt;/p&gt;

&lt;p&gt;Meanwhile, we don't seem to be learning from the past. If &lt;code&gt;alert&lt;/code&gt; is fair game for removal, then so is every API we add to the platform if the web's future stewards deem it harmful. Given that, you'd think we'd expand the platform's surface area with extreme caution; instead, we're &lt;a href="https://web-confluence.appspot.com/#!/confluence"&gt;adding APIs at breakneck speed&lt;/a&gt;, to the almost-guaranteed detriment of its future stability.&lt;/p&gt;

&lt;p&gt;Given Chrome's near-monopoly control of the browser market, I'm genuinely concerned about what this all means for the future of the web. An ad company shouldn't have this much influence over something that belongs to all of us. I don't know how to fix the standards process so that it's more representative of the diversity of the web's stakeholders, but I'm increasingly convinced that we need to figure it out.&lt;/p&gt;

</description>
      <category>javascript</category>
    </item>
    <item>
      <title>In defense of the modern web</title>
      <dc:creator>Rich Harris</dc:creator>
      <pubDate>Fri, 15 May 2020 12:29:56 +0000</pubDate>
      <link>https://dev.to/richharris/in-defense-of-the-modern-web-2nia</link>
      <guid>https://dev.to/richharris/in-defense-of-the-modern-web-2nia</guid>
      <description>&lt;p&gt;I expect I'll annoy everyone with this post: the anti-JavaScript crusaders, justly aghast at how much of the stuff we slather onto modern websites; the people arguing the web is a broken platform for interactive applications &lt;em&gt;anyway&lt;/em&gt; and we should start over; React users; the old guard with their artisanal JS and hand authored HTML; and &lt;a href="https://twitter.com/tmcw" rel="noopener noreferrer"&gt;Tom MacWright&lt;/a&gt;, someone I've admired from afar since I first became aware of his work on Mapbox many years ago. But I guess that's the price of having opinions.&lt;/p&gt;

&lt;p&gt;Tom recently posted &lt;a href="https://macwright.org/2020/05/10/spa-fatigue.html" rel="noopener noreferrer"&gt;Second-guessing the modern web&lt;/a&gt;, and it took the front end world by storm. You should read it, or at the very least the &lt;a href="https://twitter.com/tmcw/status/1259600386094030848" rel="noopener noreferrer"&gt;CliffsNotes&lt;/a&gt;. There's a lot of stuff I agree with to varying degrees:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There is a sweet spot of React: in moderately interactive interfaces ... But there’s a lot on either side of that sweet spot.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's absolutely the case that running React in the client for a largely static site is overkill. It's also true that you have to avoid React if your app is very heavily interactive — it's widely understood that if you want 60fps animation, you will likely have to bypass the React update cycle and do things in a more imperative fashion (indeed, this is what libraries like &lt;a href="https://www.react-spring.io/" rel="noopener noreferrer"&gt;react-spring&lt;/a&gt; do). But while all this is true of React, it's much less true of component frameworks &lt;em&gt;in general&lt;/em&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;User sessions are surprisingly long: someone might have your website open in a tab for weeks at a time. I’ve seen it happen. So if they open the ‘about page’, keep the tab open for a week, and then request the ‘home page’, then the home page that they request is dictated by &lt;em&gt;the index bundle that they downloaded last week&lt;/em&gt;. This is a deeply weird and under-discussed situation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's an excellent point that isn't really being addressed, though (as Tom acknowledges) it's really just exacerbating a problem that was always there. I think there &lt;em&gt;are&lt;/em&gt; solutions to it — we can iterate on the 'index bundle' approach, we could include the site version in a cookie and use that to show actionable feedback if there's a mismatch — but we do need to spend time on it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It’s your startup’s homepage, and it has a “Sign up” button, but until the JavaScript loads, that button doesn’t do anything. So you need to compensate.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is indeed very annoying, though it's easy enough to do this sort of thing — we just need to care enough:&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sign-up&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;disabled&lt;/span&gt;&lt;span class="o"&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;is_browser&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;is_browser&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sign up&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;Loading&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But I'm not sure what this has to do with React-style frameworks — this issue exists &lt;em&gt;whatever&lt;/em&gt; form your front end takes, unless you make it work without JS (which you should!).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Your formerly-lightweight application server is now doing quite a bit of labor, running React &amp;amp; making API requests in order to do this pre-rendering.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Again, this is true but more React-specific than anything. React's approach to server-side rendering — constructing a component tree, then serialising it — involves overhead that isn't shared by frameworks that, for example, compile your components (&lt;a href="https://svelte.dev" rel="noopener noreferrer"&gt;hi!&lt;/a&gt;) to functions that just concatenate strings for SSR, which is faster by a dramatic amount. And those API requests were going to have to get made anyway, so it makes sense to do them as early as possible, especially if your app server and API server are close to each other (or even the same thing).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The dream of APIs is that you have generic, flexible endpoints upon which you can build any web application. That idea breaks down pretty fast.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Amen. &lt;a href="https://macwright.org/2020/05/10/spa-fatigue.html" rel="noopener noreferrer"&gt;Just go and read&lt;/a&gt; the whole 'APIs' section several times.&lt;/p&gt;




&lt;p&gt;Minor quibbles aside, Tom identifies some real problems with the state of the art in web development. But I think the article reaches a dangerous conclusion.&lt;/p&gt;

&lt;p&gt;Let's start by dissecting this statement:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I can, for example, guarantee that this blog is faster than any Gatsby blog (and much love to the Gatsby team) because there is nothing that a React static site can do that will make it faster than a non-React static site.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With all due respect to those involved, I don't think Gatsby is a particularly relevant benchmark. The &lt;code&gt;gatsby new my-site&lt;/code&gt; starter app executes 266kB of minified JavaScript for a completely static page in production mode; for &lt;a href="https://gatsbyjs.org" rel="noopener noreferrer"&gt;gatsbyjs.org&lt;/a&gt; it's 808kB. Honestly, these are not impressive numbers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F1162160%2F82001896-43e38b00-962a-11ea-9ae7-bda853a8cec1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F1162160%2F82001896-43e38b00-962a-11ea-9ae7-bda853a8cec1.png" alt="The Lighthouse performance score for gatsbyjs.org"&gt;&lt;/a&gt; &lt;/p&gt;
The &lt;a href="https://webpagetest.org/lighthouse.php?test=200515_N4_a92cbdd5b87d402522f710a8d82d2228&amp;amp;run=1" rel="noopener noreferrer"&gt;Lighthouse score&lt;/a&gt; for Gatsby's homepage, obtained via &lt;a href="https://webpagetest.org/easy" rel="noopener noreferrer"&gt;webpagetest.org/easy&lt;/a&gt;.



&lt;p&gt;Leaving that aside, I disagree with the premise. When I tap on a link on Tom's JS-free website, the browser first waits to confirm that it was a tap and not a brush/swipe, then makes a request, and then we have to wait for the response. With a framework-authored site with client-side routing, we can start to do more interesting things. We can make informed guesses based on analytics about which things the user is likely to interact with and preload the logic and data for them. We can kick off requests as soon as the user first &lt;em&gt;touches&lt;/em&gt; (or hovers) the link instead of waiting for confirmation of a tap — worst case scenario, we've loaded some stuff that will be useful later if they &lt;em&gt;do&lt;/em&gt; tap on it. We can provide better visual feedback that loading is taking place and a transition is about to occur. And we don't need to load the entire contents of the page — often, we can make do with a small bit of JSON because we already have the JavaScript for the page. This stuff gets fiendishly difficult to do by hand.&lt;/p&gt;

&lt;p&gt;Beyond that, vanilla static sites are not an ambitious enough goal. Take transitions for example. Web developers are currently trapped in a mindset of discrete pages with jarring transitions — click a link, see the entire page get replaced whether through client-side routing or a page reload — while native app developers are thinking on another level:&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1186675229621248000-697" src="https://platform.twitter.com/embed/Tweet.html?id=1186675229621248000"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1186675229621248000-697');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1186675229621248000&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;It will take more than technological advancement to get the web there; it will take a cultural shift as well. But we certainly can't get there if we abandon our current trajectory. Which is exactly what Tom &lt;em&gt;seems&lt;/em&gt; to be suggesting.&lt;/p&gt;




&lt;p&gt;I'm not aware of any other platform where you're expected to write the logic for your initial render using a different set of technologies than the logic for subsequent interactions. The very idea sounds daft. But on the web, with its unique history, that was the norm for many years — we'd generate some HTML with PHP or Rails or whatever, and then 'sprinkle some jQuery' on it.&lt;/p&gt;

&lt;p&gt;With the advent of Node, that changed. The fact that we can do server-side rendering and communicate with databases and what-have-you &lt;em&gt;using a language native to the web&lt;/em&gt; is a wonderful development.&lt;/p&gt;

&lt;p&gt;There are problems with this model. Tom identifies some of them. Another major issue he doesn't discuss is that the server-rendered SPA model typically 'hydrates' the entire initial page in a way that requires you to duplicate a ton of data — once in the HTML, once in the JSON blob that's passed to the client version of the app to produce the exact same result — and can block the main thread during the period the user is starting to interact with the app.&lt;/p&gt;

&lt;p&gt;But we can fix those problems. &lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;Next&lt;/a&gt; is doing amazing innovation around (for example) mixing static and dynamic pages within a single app, so you get the benefits of the purely static model without ending up finding yourself constrained by it. &lt;a href="https://medium.com/@mlrawlings/maybe-you-dont-need-that-spa-f2c659bc7fec" rel="noopener noreferrer"&gt;Marko&lt;/a&gt; does intelligent component-level hydration, something I expect other frameworks to adopt. &lt;a href="https://sapper.svelte.dev/" rel="noopener noreferrer"&gt;Sapper&lt;/a&gt;, the companion framework to &lt;a href="https://svelte.dev/" rel="noopener noreferrer"&gt;Svelte&lt;/a&gt;, has a stated goal of eventually not sending any JS other than the (tiny) router itself for pages that don't require it.&lt;/p&gt;

&lt;p&gt;The future I want — the future I &lt;em&gt;see&lt;/em&gt; — is one with tooling that's accessible to the greatest number of people (including designers), that can intelligently move work between server and client as appropriate, that lets us build experiences that compete with native on UX (yes, even for blogs!), and where upgrading part of a site to 'interactive' or from 'static' to 'dynamic' doesn't involve communication across disparate teams using different technologies. We can only get there by committing to the paradigm Tom critiques — the JavaScript-ish component framework server-rendered SPA. (Better names welcomed.)&lt;/p&gt;

&lt;p&gt;The modern web has flaws, and we should talk about them. But let's not give up on it.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>react</category>
      <category>svelte</category>
      <category>webdev</category>
    </item>
    <item>
      <title>A new technique for making responsive, JavaScript-free charts</title>
      <dc:creator>Rich Harris</dc:creator>
      <pubDate>Fri, 31 Jan 2020 16:02:19 +0000</pubDate>
      <link>https://dev.to/richharris/a-new-technique-for-making-responsive-javascript-free-charts-gmp</link>
      <guid>https://dev.to/richharris/a-new-technique-for-making-responsive-javascript-free-charts-gmp</guid>
      <description>&lt;p&gt;There are countless libraries for generating charts on the web. Each serves a slightly different niche, but all of them have one thing in common: they require JavaScript.&lt;/p&gt;

&lt;p&gt;That makes sense, of course — often your charts will depend on data that must be fetched over the network with JS, or will be rendered to a &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; element. But it's not ideal. &lt;a href="http://kryogenix.org/code/browser/everyonehasjs.html" rel="noopener noreferrer"&gt;Not everyone has JS&lt;/a&gt;, and in any case relying on it means that you'll be left with a chart-shaped hole in the page until it loads, which you can only really get away with if all your dataviz is tucked away below the fold.&lt;/p&gt;

&lt;p&gt;Another more subtle problem is that &lt;em&gt;fluid&lt;/em&gt; charts — those that adapt to the width of their containers — must be redrawn upon resize to avoid potentially breaking. That can mean more work for the developer (particularly if the developer is using a low-level library like &lt;a href="https://d3js.org/" rel="noopener noreferrer"&gt;D3&lt;/a&gt;), and certainly more work for the browser.&lt;/p&gt;

&lt;p&gt;For a recent &lt;a href="https://www.nytimes.com/interactive/2019/09/28/us/child-sex-abuse.html" rel="noopener noreferrer"&gt;New York Times article&lt;/a&gt;, I wanted to see if it was possible to create SVG charts that would work without JS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F1162160%2F72925342-894d1700-3d20-11ea-9200-102b586ee3f3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F1162160%2F72925342-894d1700-3d20-11ea-9200-102b586ee3f3.png" alt="CyberTipline reports vs funding"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Well, it is. I haven't seen the same combination of techniques used elsewhere, so I figured I'd write up the process. I've also created an experimental &lt;a href="https://pancake-charts.surge.sh/" rel="noopener noreferrer"&gt;Svelte component library called Pancake&lt;/a&gt; to make these techniques easier to use.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Creating an SVG line chart (we'll come to other chart types later) is actually rather simple. Suppose we have a series 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;const&lt;/span&gt; &lt;span class="nx"&gt;data&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="na"&gt;x&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="na"&gt;y&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&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="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;36&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;49&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&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="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;81&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&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 a 300px by 100px chart. If we multiply the &lt;code&gt;x&lt;/code&gt; values by 30, and subtract the &lt;code&gt;y&lt;/code&gt; values from 100, we'll get coordinates that fill the space:&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;polyline&lt;/span&gt; &lt;span class="na"&gt;points=&lt;/span&gt;&lt;span class="s"&gt;"
  0,0
  30,99
  60,96
  90,91
  120,84
  150,75
  180,64
  210,51
  240,36
  270,19
  300,0
"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/polyline&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Typically, of course, you'd use a scaling function rather than calculating the coordinates manually:&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;function&lt;/span&gt; &lt;span class="nf"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;range&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;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;range&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="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;domain&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;num&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;range&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="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;num&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;domain&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="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;scale&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="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&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="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;d&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&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;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;scale&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="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&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="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;))],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;100&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;points&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="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;x&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="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;y&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="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
&amp;lt;svg width="300" height="100"&amp;gt;
  &amp;lt;polyline points="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;points&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;&amp;lt;/polyline&amp;gt;
&amp;lt;/svg&amp;gt;
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Throw in some axes and some styling, and &lt;a href="https://svelte.dev/repl/e4c7b25da03b40b7a1cbe2b75840a185?version=3.17.2" rel="noopener noreferrer"&gt;we have a chart&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F1162160%2F72927852-f5317e80-3d24-11ea-85f1-8e6979c4b991.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F1162160%2F72927852-f5317e80-3d24-11ea-85f1-8e6979c4b991.png" alt="Simple line chart"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That logic could all live inside a Node.js script, meaning this chart could easily be created without any client-side JS. &lt;/p&gt;

&lt;p&gt;But it won't adapt to the size of its container — it will always be a 300px by 100px chart. On most websites, that's a problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution (part one)
&lt;/h2&gt;

&lt;p&gt;SVG has an attribute called &lt;code&gt;viewBox&lt;/code&gt; that defines a coordinate system that is independent of the size of the &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; element itself. Ordinarily the aspect ratio of the viewBox is preserved regardless of the aspect ratio of the &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; element, but we can disable that with &lt;code&gt;preserveAspectRatio="none"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We can pick a simple coordinate system, 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;svg&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 100 100"&lt;/span&gt; &lt;span class="na"&gt;preserveAspectRatio=&lt;/span&gt;&lt;span class="s"&gt;"none"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...and project our data into it. Now, &lt;a href="https://svelte.dev/repl/04cfcfa195fc46a59f352e8d56f468c1?version=3.17.2" rel="noopener noreferrer"&gt;our chart adapts fluidly to its environment&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F1162160%2F72930444-f2855800-3d29-11ea-9575-d0ea05c9cc7c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F1162160%2F72930444-f2855800-3d29-11ea-9575-d0ea05c9cc7c.png" alt="Fluid-but-broken charts"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But it's obviously broken in two important ways. Firstly, the text is horribly scaled, to the point of being illegible in some cases. Secondly, the line strokes are stretched along with the lines themselves, which looks dreadful.&lt;/p&gt;

&lt;p&gt;The second of these issues is straightforward enough to solve with a little-known CSS property — &lt;code&gt;vector-effect: non-scaling-stroke&lt;/code&gt; — &lt;a href="https://svelte.dev/repl/85524526766f4126b307e913924d6571?version=3.17.2" rel="noopener noreferrer"&gt;applied to every element&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F1162160%2F72930794-b4d4ff00-3d2a-11ea-8941-fc39dac301c1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F1162160%2F72930794-b4d4ff00-3d2a-11ea-8941-fc39dac301c1.png" alt="Fluid-and-slightly-less-broken charts"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But the first issue can't, to my knowledge, be solved within SVG.&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution (part two)
&lt;/h2&gt;

&lt;p&gt;Instead of using SVG elements for the axes, we can use HTML elements and position them with CSS. Because we're using a percentage-based coordinate system, it's very easy to keep the HTML layer and the SVG layer glued together.&lt;/p&gt;

&lt;p&gt;Recreating the axes above with HTML is as simple as 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="c"&gt;&amp;lt;!-- x axis --&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;"x axis"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"top: 100%; width: 100%; border-top: 1px solid black;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"left: 0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"left: 20%"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;2&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"left: 40%"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;4&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"left: 60%"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;6&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"left: 80%"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;8&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"left: 100%"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;10&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- y axis --&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;"y axis"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"height: 100%; border-left: 1px solid black;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"top: 100%"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"top: 50%"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;50&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"top: 0%"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;100&lt;span class="nt"&gt;&amp;lt;/span&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;style&amp;gt;&lt;/span&gt;
  &lt;span class="nc"&gt;.axis&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.axis&lt;/span&gt; &lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;line-height&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="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.x.axis&lt;/span&gt; &lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.5em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-50%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.y.axis&lt;/span&gt; &lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-0.5em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-100%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;-50%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our charts are &lt;a href="https://svelte.dev/repl/5c0aefaa582b4594a920cf321477c219?version=3.17.2" rel="noopener noreferrer"&gt;no longer borked&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F1162160%2F72931953-cc14ec00-3d2c-11ea-940e-a7dd8ea3afad.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F1162160%2F72931953-cc14ec00-3d2c-11ea-940e-a7dd8ea3afad.png" alt="Non-borked fluid line charts"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Another benefit of using HTML elements is that they automatically snap to the nearest pixel, meaning you don't get the 'fuzzy' effect that tends to happen with SVG elements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Packaging it up
&lt;/h2&gt;

&lt;p&gt;This solves the problem, but there's a lot of manual busywork involved, hence &lt;a href="https://pancake-charts.surge.sh" rel="noopener noreferrer"&gt;Pancake&lt;/a&gt;. With Pancake, the chart above would look &lt;a href="https://svelte.dev/repl/d93a510d0d5c4eb7a925e99e81009c62?version=3.17.2" rel="noopener noreferrer"&gt;something like this&lt;/a&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;script&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Pancake&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;@sveltejs/pancake&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;points&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="na"&gt;x&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="na"&gt;y&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&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="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;36&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;49&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&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="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;81&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&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;"chart"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Pancake.Chart&lt;/span&gt; &lt;span class="na"&gt;x1=&lt;/span&gt;&lt;span class="s"&gt;{0}&lt;/span&gt; &lt;span class="na"&gt;x2=&lt;/span&gt;&lt;span class="s"&gt;{10}&lt;/span&gt; &lt;span class="na"&gt;y1=&lt;/span&gt;&lt;span class="s"&gt;{0}&lt;/span&gt; &lt;span class="na"&gt;y2=&lt;/span&gt;&lt;span class="s"&gt;{100}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Pancake.Box&lt;/span&gt; &lt;span class="na"&gt;x2=&lt;/span&gt;&lt;span class="s"&gt;{10}&lt;/span&gt; &lt;span class="na"&gt;y2=&lt;/span&gt;&lt;span class="s"&gt;{100}&lt;/span&gt;&lt;span class="nt"&gt;&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;"axes"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/Pancake.Box&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;Pancake.Grid&lt;/span&gt; &lt;span class="na"&gt;vertical&lt;/span&gt; &lt;span class="na"&gt;count=&lt;/span&gt;&lt;span class="s"&gt;{5}&lt;/span&gt; &lt;span class="na"&gt;let:value&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"x label"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{value}&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/Pancake.Grid&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;Pancake.Grid&lt;/span&gt; &lt;span class="na"&gt;horizontal&lt;/span&gt; &lt;span class="na"&gt;count=&lt;/span&gt;&lt;span class="s"&gt;{3}&lt;/span&gt; &lt;span class="na"&gt;let:value&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"y label"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{value}&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/Pancake.Grid&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;Pancake.Svg&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Pancake.SvgLine&lt;/span&gt; &lt;span class="na"&gt;data=&lt;/span&gt;&lt;span class="s"&gt;{points}&lt;/span&gt; &lt;span class="na"&gt;let:d&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"data"&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="na"&gt;d&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/Pancake.SvgLine&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/Pancake.Svg&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Pancake.Chart&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;style&amp;gt;&lt;/span&gt;
  &lt;span class="nc"&gt;.chart&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3em&lt;/span&gt; &lt;span class="m"&gt;2em&lt;/span&gt; &lt;span class="m"&gt;2em&lt;/span&gt; &lt;span class="m"&gt;3em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;box-sizing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;border-box&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.axes&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="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="no"&gt;black&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="no"&gt;black&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.y.label&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-2.5em&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="m"&gt;2em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;right&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-0.5em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.x.label&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&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="m"&gt;4em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-2em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-22px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="nc"&gt;.data&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;stroke&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;red&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;stroke-linejoin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;round&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;stroke-linecap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;round&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;stroke-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because we're using Svelte, this chart can easily be rendered at build time with Node.js, or be injected into the DOM using client-side JS. For charts that have some interactivity (such as the big example chart on the &lt;a href="https://pancake-charts.surge.sh" rel="noopener noreferrer"&gt;Pancake homepage&lt;/a&gt;), you might want to do &lt;em&gt;both&lt;/em&gt; — serve the basic chart with your HTML, then progressively enhance it with interactivity by &lt;em&gt;hydrating&lt;/em&gt; the initial DOM. This is something that's rather difficult to do without a component framework like Svelte.&lt;/p&gt;

&lt;p&gt;Notice that Pancake isn't actually creating the &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;path&amp;gt;&lt;/code&gt; nodes that comprise the chart. Rather, the components are primarily &lt;em&gt;logical&lt;/em&gt; — you bring the markup, meaning you have fine-grained control over the appearance of chart elements. &lt;/p&gt;

&lt;h2&gt;
  
  
  Taking it further
&lt;/h2&gt;

&lt;p&gt;We can do much more than simple line charts:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F1162160%2F73493317-84bbda80-4380-11ea-92ca-da53f3b3d9c0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F1162160%2F73493317-84bbda80-4380-11ea-92ca-da53f3b3d9c0.png" alt="Different Pancake chart types"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://svelte.dev/repl/da70a84eb31c4ddda94122ae17768c19?version=3.17.2" rel="noopener noreferrer"&gt;Scatterplots&lt;/a&gt; are particularly interesting. Because we can't use &lt;code&gt;&amp;lt;circle&amp;gt;&lt;/code&gt; elements — they would stretch, like the line and text elements earlier — we have to get slightly creative. The &lt;code&gt;&amp;lt;Pancake.Scatterplot&amp;gt;&lt;/code&gt; component generates a path of disconnected arcs with a radius of zero. By rendering that path with a stroke width, we can make it look as though we're plotting circles.&lt;/p&gt;

&lt;p&gt;Because we're in a Svelte component, we can easily introduce motion into our charts, as in &lt;a href="https://svelte.dev/repl/8899c7b5e5dd4e0eb3b47f0ef63e9cc5?version=3.17.2" rel="noopener noreferrer"&gt;this small multiples example&lt;/a&gt;. We can also add things like declarative transitions with a minimum of fuss.&lt;/p&gt;

&lt;p&gt;Interactivity can also be handled declaratively within a Pancake chart. For example, we can create a quadtree (&lt;a href="https://github.com/d3/d3-quadtree" rel="noopener noreferrer"&gt;borrowing heavily from D3&lt;/a&gt;) that lets you find the nearest point to the mouse:&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;Pancake.SvgScatterplot&lt;/span&gt; &lt;span class="na"&gt;data=&lt;/span&gt;&lt;span class="s"&gt;{points}&lt;/span&gt; &lt;span class="na"&gt;let:d&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"data"&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="na"&gt;d&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Pancake.SvgScatterplot&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;Pancake.Quadtree&lt;/span&gt; &lt;span class="na"&gt;data=&lt;/span&gt;&lt;span class="s"&gt;{points}&lt;/span&gt; &lt;span class="na"&gt;let:closest&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  {#if closest}
    &lt;span class="nt"&gt;&amp;lt;Pancake.SvgPoint&lt;/span&gt; &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;{closest.x}&lt;/span&gt; &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;{closest.y}&lt;/span&gt; &lt;span class="na"&gt;let:d&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"highlight"&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="na"&gt;d&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/Pancake.SvgPoint&amp;gt;&lt;/span&gt;
  {/if}
&lt;span class="nt"&gt;&amp;lt;/Pancake.Quadtree&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the New York Times we're using a very similar technique to create &lt;a href="https://www.nytimes.com/interactive/2020/world/asia/china-wuhan-coronavirus-maps.html" rel="noopener noreferrer"&gt;JS-less maps tracking the coronavirus outbreak&lt;/a&gt;. There's a bit more to do, but it's likely that this work will be folded into Pancake eventually.&lt;/p&gt;

&lt;p&gt;In future, the library will likely add support for rendering to a canvas layer (both 2D and WebGL). Charts that use &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; will have a hard dependency on JS, but it's necessary in cases where you have more data than can be rendered with SVG in a performant way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveats
&lt;/h2&gt;

&lt;p&gt;This is still somewhat experimental; it hasn't been battle-tested to anything like the degree that existing charting libraries have.&lt;/p&gt;

&lt;p&gt;Its focus is on managing the coordinate system for two dimensional charts. That's enough for line charts and bar charts and scatterplots and stacked area charts and what-have-you, but if you need to make pie charts you will have to look elsewhere.&lt;/p&gt;

&lt;p&gt;For now, there's no documentation, but the &lt;a href="https://pancake-charts.surge.sh" rel="noopener noreferrer"&gt;homepage&lt;/a&gt; has examples you can crib from. It's possible that APIs will change as we encounter more real world problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Acknowledgments
&lt;/h2&gt;

&lt;p&gt;The name 'Pancake' comes from the fact that charts are built by stacking layers on top of each other. I'm deeply indebted to &lt;a href="https://twitter.com/mhkeller" rel="noopener noreferrer"&gt;Michael Keller&lt;/a&gt; for creating &lt;a href="https://layercake.graphics/" rel="noopener noreferrer"&gt;Layer Cake&lt;/a&gt;, which Pancake draws a lot of inspiration from, and from where I ripped off some of the example charts linked above. Michael also reported the story linked above, giving me a reason to create Pancake in the first place.&lt;/p&gt;

&lt;p&gt;I'm also indebted to &lt;a href="https://twitter.com/mbostock" rel="noopener noreferrer"&gt;Mike Bostock&lt;/a&gt;, of &lt;a href="https://d3js.org/" rel="noopener noreferrer"&gt;D3&lt;/a&gt; and &lt;a href="https://observablehq.com/" rel="noopener noreferrer"&gt;Observable&lt;/a&gt; fame, for sharing the insights, examples and code that make projects like this one possible. The handful of examples on the Pancake homepage are shamelessly copied from the &lt;a href="https://github.com/d3/d3/wiki/Gallery" rel="noopener noreferrer"&gt;D3 examples page&lt;/a&gt;, which is a goldmine for anyone looking to test out a new charting library.&lt;/p&gt;

</description>
      <category>dataviz</category>
      <category>svelte</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Why I don't use web components</title>
      <dc:creator>Rich Harris</dc:creator>
      <pubDate>Thu, 20 Jun 2019 12:30:24 +0000</pubDate>
      <link>https://dev.to/richharris/why-i-don-t-use-web-components-2cia</link>
      <guid>https://dev.to/richharris/why-i-don-t-use-web-components-2cia</guid>
      <description>&lt;p&gt;For my first post on dev.to I thought I'd write about a nice, safe topic that's free of controversy: web components.&lt;/p&gt;

&lt;p&gt;I'm mostly writing this for my future self, so that I have something to point to next time someone asks why I'm a web component skeptic, and why &lt;a href="https://svelte.dev" rel="noopener noreferrer"&gt;Svelte&lt;/a&gt; doesn't compile to custom elements by default. (It &lt;em&gt;can&lt;/em&gt; compile to CEs, and it can consume CEs as evidenced by its perfect score on &lt;a href="https://custom-elements-everywhere.com/" rel="noopener noreferrer"&gt;Custom Elements Everywhere&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;None of this should be taken as criticism of the hard work that has been done on web components. It's possible that I have made some errors in this post, in which case I'd welcome corrections.&lt;/p&gt;

&lt;p&gt;Nor am I saying that you shouldn't use web components. They &lt;em&gt;do&lt;/em&gt; have valid use cases. I'm just explaining why &lt;em&gt;I&lt;/em&gt; don't.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Progressive enhancement
&lt;/h2&gt;

&lt;p&gt;This may be an increasingly old-fashioned view, but I think that websites should work without JavaScript wherever possible. Web components don't.&lt;/p&gt;

&lt;p&gt;That's fine for things that are intrinsically interactive, like a custom form element (&lt;code&gt;&amp;lt;cool-datepicker&amp;gt;&lt;/code&gt;), but it's not fine for your nav bar. Or consider a simple &lt;code&gt;&amp;lt;twitter-share&amp;gt;&lt;/code&gt; element that encapsulates all the logic for constructing a &lt;a href="https://developer.twitter.com/en/docs/twitter-for-websites/tweet-button/guides/web-intent.html" rel="noopener noreferrer"&gt;Twitter web intent&lt;/a&gt; URL. I could &lt;a href="https://svelte.dev/repl/98aa20d4cb3d40dabfef7d8dae183b85?version=3.5.2" rel="noopener noreferrer"&gt;build it in Svelte&lt;/a&gt; and it would generate server-rendered HTML like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;target=&lt;/span&gt;&lt;span class="s"&gt;"_blank"&lt;/span&gt; &lt;span class="na"&gt;noreferrer&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"svelte-1jnfxx"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Tweet this
&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In other words, a bog-standard &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; element, in all its accessible glory.&lt;/p&gt;

&lt;p&gt;With JavaScript enabled, it progressively enhances — rather than opening a new tab, it opens a small popup window instead. But without, it still works fine.&lt;/p&gt;

&lt;p&gt;By contrast, the web component HTML would look something like this...&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;twitter-share&lt;/span&gt; &lt;span class="na"&gt;text=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt; &lt;span class="na"&gt;url=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt; &lt;span class="na"&gt;via=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;...which is useless and inaccessible, if JS is disabled or somehow broken, or the user is on an older browser.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;class="svelte-1jnfxx"&lt;/code&gt; is what enables encapsulated styles without Shadow DOM. Which brings me onto my next point:&lt;/p&gt;

&lt;h2&gt;
  
  
  2. CSS in, err... JS
&lt;/h2&gt;

&lt;p&gt;If you want to use Shadow DOM for style encapsulation, you have to include your CSS in a &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; element. The only practical way to do so, at least if you want to avoid FOUC, is to have the CSS in a string in the JavaScript module that defines the custom element.&lt;/p&gt;

&lt;p&gt;This runs counter to the performance advice we've been given, which can be summarised as 'less JavaScript, please'. The CSS-in-JS community in particular has been criticised for not putting CSS in &lt;code&gt;.css&lt;/code&gt; files, and yet here we are.&lt;/p&gt;

&lt;p&gt;In future, we may be able to use &lt;a href="https://github.com/w3c/webcomponents/issues/759" rel="noopener noreferrer"&gt;CSS Modules&lt;/a&gt; alongside &lt;a href="https://developers.google.com/web/updates/2019/02/constructable-stylesheets" rel="noopener noreferrer"&gt;Constructable Stylesheets&lt;/a&gt; to solve this problem. And we may be able to use &lt;code&gt;::theme&lt;/code&gt; and &lt;code&gt;::part&lt;/code&gt; to style things inside Shadow DOM. But these aren't free of problems either.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Platform fatigue
&lt;/h2&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1141404066704232448-837" src="https://platform.twitter.com/embed/Tweet.html?id=1141404066704232448"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1141404066704232448-837');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1141404066704232448&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;At the time of writing, there are 61,000 open issues on &lt;a href="https://crbug.com" rel="noopener noreferrer"&gt;https://crbug.com&lt;/a&gt;, the Chromium bug tracker, which reflects the enormous complexity of building a modern web browser.&lt;/p&gt;

&lt;p&gt;Every time we add a new feature to the platform, we increase that complexity — creating new surface area for bugs, and making it less and less likely that a new competitor to Chromium could ever emerge.&lt;/p&gt;

&lt;p&gt;It also creates complexity for developers, who are encouraged to learn these new features (some of which, like HTML Imports or the original Custom Elements spec, never catch on outside Google and end up being removed again.)&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Polyfills
&lt;/h2&gt;

&lt;p&gt;It doesn't help that you need to use polyfills if you want to support all browsers. It &lt;em&gt;really&lt;/em&gt; doesn't help that the literature on &lt;a href="https://developers.google.com/web/updates/2019/02/constructable-stylesheets" rel="noopener noreferrer"&gt;Constructable Stylesheets&lt;/a&gt;, written by a Googler (hi Jason!), doesn't mention that they're a Chrome-only feature (&lt;em&gt;edit: this has been fixed after I opened a &lt;a href="https://github.com/google/WebFundamentals/pull/8212" rel="noopener noreferrer"&gt;pull request&lt;/a&gt;&lt;/em&gt;). The &lt;a href="https://wicg.github.io/construct-stylesheets/" rel="noopener noreferrer"&gt;three spec editors&lt;/a&gt; are all Googlers. Webkit &lt;a href="https://github.com/mozilla/standards-positions/issues/103#issuecomment-494181931" rel="noopener noreferrer"&gt;seem to have some doubts&lt;/a&gt; about some aspects of the design.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Composition
&lt;/h2&gt;

&lt;p&gt;It's useful for a component to be able to control when (or whether) its slotted content is rendered. Suppose we wanted to use the &lt;a href="https://github.com/justinfagnani/html-include-element" rel="noopener noreferrer"&gt;&lt;code&gt;&amp;lt;html-include&amp;gt;&lt;/code&gt; element&lt;/a&gt; to show some documentation from the network when it became visible:&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;p&amp;gt;&lt;/span&gt;Toggle the section for more info:&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;toggled-section&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;html-include&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"./more-info.html"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/toggled-section&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Surprise! Even though you didn't toggle the section open yet, the browser already requested &lt;code&gt;more-info.html&lt;/code&gt;, along with whatever images and other resources it links to.&lt;/p&gt;

&lt;p&gt;That's because slotted content renders &lt;em&gt;eagerly&lt;/em&gt; in custom elements. It turns out that most of the time you want slotted content to render &lt;em&gt;lazily&lt;/em&gt;. Svelte v2 adopted the eager model in order to align with web standards, and it turned out to be a major source of frustration — we couldn't create an equivalent to React Router, for example. In Svelte v3 we abandoned the custom element composition model and never looked back.&lt;/p&gt;

&lt;p&gt;Unfortunately this is just a fundamental characteristic of the DOM. Which brings us to...&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Confusion between props and attributes
&lt;/h2&gt;

&lt;p&gt;Props and attributes are basically the same thing, right?&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="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="nx"&gt;button&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;disabled&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;disabled&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="nx"&gt;button&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;disabled&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;

&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;disabled&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// false&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;I mean, almost:&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;typeof&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 'boolean'&lt;/span&gt;
&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;button&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;disabled&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 'object'&lt;/span&gt;

&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;disabled&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="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;button&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;disabled&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 'string'&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And then there are the names that don't match...&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;div&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;div&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;class&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;one&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 'one'&lt;/span&gt;

&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;two&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;div&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;class&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 'two'&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;...and the ones that just don't seem to correspond at all:&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;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input&lt;/span&gt;&lt;span class="dl"&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;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="c1"&gt;// null&lt;/span&gt;
&lt;span class="nx"&gt;input&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;one&lt;/span&gt;&lt;span class="dl"&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;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="c1"&gt;// null&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;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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;two&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 'one'&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;But we can live with those quirks, because &lt;em&gt;of course&lt;/em&gt; some things will be lost in translation between a string format (HTML) and the DOM. There's a finite number of them, and they're documented, so at least you can learn about them given enough time and patience.&lt;/p&gt;

&lt;p&gt;Web components change that. Not only are there no longer any guarantees about the relationship between attributes and props, but as a web component author, you're (presumably?) supposed to support both. Which means you see this sort of thing:&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;MyThing&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="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;observedAttributes&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="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;foo&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;bar&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;baz&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;get&lt;/span&gt; &lt;span class="nf"&gt;foo&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;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;foo&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;foo&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;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;foo&lt;/span&gt;&lt;span class="dl"&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="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;bar&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;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;bar&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;bar&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;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;bar&lt;/span&gt;&lt;span class="dl"&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="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;baz&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;baz&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;baz&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;if &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;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;baz&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="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&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;removeAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;baz&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="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;oldValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newValue&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;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;foo&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="c1"&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;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;bar&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="c1"&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;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;baz&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="c1"&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;Sometimes you see things go the other way — &lt;code&gt;attributeChangedCallback&lt;/code&gt; invoking the property accessors instead. Either way, the ergonomics are disastrous.&lt;/p&gt;

&lt;p&gt;Frameworks, by contrast, have a simple and unambiguous way to pass data into a component.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Leaky design
&lt;/h2&gt;

&lt;p&gt;This point is a bit more nebulous, but it weirds me out that &lt;code&gt;attributeChangedCallback&lt;/code&gt; is just a method on the element instance. You can literally do this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;element&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;my-thing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attributeChangedCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;w&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;t&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;f&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;No attribute changed, but it will behave as though it did. Of course, JavaScript has always provided plenty of opportunities for mischief, but when I see implementation details poke through like that I always feel as though they're trying to tell us that the design isn't quite right.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. The DOM is bad
&lt;/h2&gt;

&lt;p&gt;Ok, we've already established that the DOM is bad. But it's hard to overstate what an awkward interface it is for building interactive applications.&lt;/p&gt;

&lt;p&gt;A couple of months back, I wrote an article called &lt;a href="https://svelte.dev/blog/write-less-code" rel="noopener noreferrer"&gt;Write less code&lt;/a&gt;, intended to illustrate how Svelte allows you to build components more efficiently than frameworks like React and Vue. But I didn't compare it against the DOM. I should have.&lt;/p&gt;

&lt;p&gt;To recap, here's a simple &lt;code&gt;&amp;lt;Adder a={1} b={2}/&amp;gt;&lt;/code&gt; component:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;let&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;export&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"number"&lt;/span&gt; &lt;span class="na"&gt;bind:value=&lt;/span&gt;&lt;span class="s"&gt;{a}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"number"&lt;/span&gt; &lt;span class="na"&gt;bind:value=&lt;/span&gt;&lt;span class="s"&gt;{b}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;{a} + {b} = {a + b}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That's the whole thing. Now, let's build the same thing as a web component:&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;Adder&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="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;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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shadowRoot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
      &amp;lt;input type="number"&amp;gt;
      &amp;lt;input type="number"&amp;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;inputs&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;shadowRoot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input&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;p&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;shadowRoot&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;p&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="nf"&gt;update&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;inputs&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="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;input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&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="k"&gt;this&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="o"&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;target&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&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;input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&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;target&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="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;observedAttributes&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="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a&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;b&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;get&lt;/span&gt; &lt;span class="nf"&gt;a&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="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;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;a&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;a&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;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;a&lt;/span&gt;&lt;span class="dl"&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="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;b&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="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;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;b&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;b&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;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;b&lt;/span&gt;&lt;span class="dl"&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="nf"&gt;attributeChangedCallback&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;update&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="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;inputs&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="nx"&gt;value&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;a&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;inputs&lt;/span&gt;&lt;span class="p"&gt;[&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;value&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;b&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;p&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="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;a&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;b&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;a&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;b&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;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;my-adder&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Adder&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Yeah.&lt;/p&gt;

&lt;p&gt;Note also that if you change &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; in the same instant, it will result in two separate updates. Frameworks don't generally suffer from this issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Global namespace
&lt;/h2&gt;

&lt;p&gt;We don't need to dwell on this one too much; suffice it to say that the dangers of having a single shared namespace have been well understood for some time.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. These are all solved problems
&lt;/h2&gt;

&lt;p&gt;The biggest frustration of all is that we already have really good component models. We're still learning, but the basic problem — keep the view in sync with some state by manipulating the DOM in a component-oriented fashion — has been solved for years.&lt;/p&gt;

&lt;p&gt;Yet we're adding new features to the platform just to bring web components to &lt;em&gt;parity&lt;/em&gt; with what we can already do in userland. &lt;/p&gt;

&lt;p&gt;Given finite resources, time spent on one task means time not spent on another task. Considerable energy has been expended on web components despite a largely indifferent developer population. What could the web have achieved if that energy had been spent elsewhere?&lt;/p&gt;

</description>
      <category>html</category>
      <category>webcomponents</category>
    </item>
  </channel>
</rss>
